AtteanX-Store-LMDB

 view release on metacpan or  search on metacpan

lib/AtteanX/Store/LMDB.pm  view on Meta::CPAN


=head1 SYNOPSIS

 use AtteanX::Store::LMDB;

=head1 DESCRIPTION

AtteanX::Store::LMDB provides a persistent quad-store based on LMDB.

=cut

use v5.14;
use warnings;

package AtteanX::Store::LMDB {
our $VERSION	= '0.003';
use Moo;
use Type::Tiny::Role;
use Types::Standard qw(Bool Str InstanceOf HashRef);
use LMDB_File qw(:flags :cursor_op);
use Digest::SHA qw(sha256 sha256_hex);
use Scalar::Util qw(refaddr reftype blessed);
use Math::Cartesian::Product;
use List::Util qw(any all first);
use File::Path qw(make_path);
use DateTime::Format::W3CDTF;
use Devel::Peek;
use Encode qw(encode_utf8 decode_utf8);
use namespace::clean;

with 'Attean::API::QuadStore';
with 'Attean::API::MutableQuadStore';

=head1 METHODS

Beyond the methods documented below, this class inherits methods from the
L<Attean::API::QuadStore> class.

=over 4

=item C<< new () >>

Returns a new LMDB-backed store object.

=cut

has initialize => (is => 'ro', isa => Bool, default => 0);
has filename => (is => 'ro', isa => Str, required => 1);
has env		=> (is => 'rw', isa => InstanceOf['LMDB::Env']);
has indexes	=> (is => 'rw', isa => HashRef, default => sub { +{} });

sub BUILDARGS {
	my $class	= shift;
	my @params 	= @_;
	my %args;
	if (scalar(@params) == 1) {
		%args	= (filename => shift(@params));
	} else {
		%args	= @params;
	}
	return $class->SUPER::BUILDARGS(%args);
}

sub BUILD {
	my $self	= shift;
	my $file	= $self->filename;
	
	unless (-d $file) {
		make_path($file);
	}
	my $env = LMDB::Env->new($file, {
		mapsize => 100 * 1024 * 1024 * 1024, # Plenty space, don't worry
		maxdbs => 20, # Some databases
		mode   => 0640,
	});
	$self->env($env);
	if ($self->initialize) {
		my $txn		= $self->env->BeginTxn();
		my %databases;
		foreach my $name (qw(quads stats fullIndexes term_to_id id_to_term graphs prefixes)) {
			$databases{$name}	= $txn->OpenDB({ dbname => $name, flags => MDB_CREATE });
		}
		
		my $f		= DateTime::Format::W3CDTF->new();
		my $stats	= $databases{'stats'};
		$stats->put("Diomede-Version", '0.0.13');
		$stats->put("Last-Modified", $f->format_datetime(DateTime->now()));
		foreach my $key (qw(next_unassigned_term_id next_unassigned_quad_id)) {
			$stats->put($key, pack('Q>', 1));
		}
		my $indexes	= $databases{'fullIndexes'};
		my %positions = ('s' => 0, 'p' => 1, 'o' => 2, 'g' => 3);
		foreach my $key (qw(spog pogs gops)) {
			my @pos	= map { $positions{$_} } split(//, $key);
			$txn->OpenDB({ dbname => $key, flags => MDB_CREATE });
			$indexes->put($key, pack('Q>4', @pos));
		}
		
		$txn->commit();
	}
	
	my $txn		= $self->env->BeginTxn(MDB_RDONLY);
	my $indexes	= $txn->OpenDB({ dbname => 'fullIndexes' });
	$self->iterate_database($indexes, sub {
		my ($key, $value)	= @_;
		my @order	= unpack('Q>4', $value);
		$self->indexes->{$key}	= \@order;
	});
}

sub iterate_database {
	my $self	= shift;
	my $db		= shift;
	my $handler	= shift;
	my $cursor	= $db->Cursor;
	eval {
		local($LMDB_File::die_on_err)	= 0;
		my ($key, $value);
		unless ($cursor->get($key, $value, MDB_FIRST)) {
			while (1) {
				$handler->($key, $value);



( run in 2.882 seconds using v1.01-cache-2.11-cpan-98e64b0badf )