File Coverage

blib/lib/CHI/Driver/LMDB.pm
Criterion Covered Total %
statement 24 26 92.3
branch n/a
condition n/a
subroutine 9 9 100.0
pod n/a
total 33 35 94.2


line stmt bran cond sub pod time code
1 1     1   416 use 5.008; # utf8
  1         2  
2 1     1   3 use strict;
  1         1  
  1         19  
3 1     1   12 use warnings;
  1         1  
  1         27  
4 1     1   469 use utf8;
  1         9  
  1         3  
5              
6             package CHI::Driver::LMDB;
7              
8             our $VERSION = '0.002004';
9              
10             # ABSTRACT: use OpenLDAPs LMDB Key-Value store as a cache backend.
11              
12             our $AUTHORITY = 'cpan:KENTNL'; # AUTHORITY
13              
14 1     1   55 use Carp qw( croak );
  1         1  
  1         55  
15 1     1   398 use Moo qw( extends has around );
  1         10318  
  1         10  
16 1     1   1891 use Path::Tiny qw( path );
  1         9090  
  1         72  
17 1     1   530 use File::Spec::Functions qw( tmpdir );
  1         546  
  1         57  
18 1     1   438 use LMDB_File 0.006 qw( MDB_CREATE MDB_NEXT );
  0            
  0            
19             extends 'CHI::Driver';
20              
21             has 'dir_create_mode' => ( is => 'ro', lazy => 1, default => oct 775 );
22             has 'root_dir' => ( is => 'ro', lazy => 1, builder => '_build_root_dir' );
23             has 'cache_size' => ( is => 'ro', lazy => 1, default => '5m' );
24             has 'single_txn' => ( is => 'ro', lazy => 1, default => sub { undef } );
25             has 'db_flags' => ( is => 'ro', lazy => 1, default => MDB_CREATE );
26             has 'tx_flags' => ( is => 'ro', lazy => 1, default => 0 );
27             has 'put_flags' => ( is => 'ro', lazy => 1, default => 0 );
28              
29             my %env_opts = (
30             mapsize => { is => 'ro', lazy => 1, builder => '_build_mapsize' },
31             maxreaders => { is => 'ro', lazy => 1, default => 126 },
32             maxdbs => { is => 'ro', lazy => 1, default => 1024 },
33             mode => { is => 'ro', lazy => 1, default => oct 600 },
34             flags => { is => 'ro', lazy => 1, default => 0 },
35             );
36              
37             for my $attr ( keys %env_opts ) {
38             has $attr => %{ $env_opts{$attr} };
39             }
40              
41             my $sizes = {
42             k => 1024,
43             m => 1024 * 1024,
44             };
45              
46             sub _build_mapsize {
47             my ($self) = @_;
48             my $cache_size = $self->cache_size;
49             if ( $cache_size =~ s/([km])\z//msxi ) {
50             $cache_size *= $sizes->{ lc $1 };
51             }
52             return $cache_size;
53             }
54              
55             sub _build_root_dir {
56             return path( tmpdir() )->child( 'chi-driver-lmdb-' . $> );
57             }
58              
59             has '_existing_root_dir' => ( is => 'ro', lazy => 1, builder => '_build_existing_root_dir' );
60              
61             sub _build_existing_root_dir {
62             my ($self) = @_;
63             my $dir = path( $self->root_dir );
64             return $dir if $dir->is_dir;
65             $dir->mkpath( { mode => $self->dir_create_mode, } );
66             return $dir;
67             }
68              
69             has '_lmdb_env' => ( is => 'ro', builder => '_build_lmdb_env', lazy => 1, );
70             has '_lmdb_max_key' => ( is => 'ro', builder => '_build_lmdb_max_key', lazy => 1 );
71             has '_lmdb_dbi' => ( is => 'ro', builder => '_build_lmdb_dbi', lazy => 1, );
72              
73             sub _build_lmdb_env {
74             my ($self) = @_;
75             return LMDB::Env->new( $self->_existing_root_dir . q[], { map { $_ => $self->$_() } keys %env_opts } );
76             }
77              
78             sub _build_lmdb_max_key {
79             my ($self) = @_;
80             return $self->_lmdb_env->get_maxkeysize;
81             }
82              
83             sub _build_lmdb_dbi {
84             my ($self) = @_;
85             my $tx = $self->_lmdb_env->BeginTxn();
86             my $dbi = $tx->open( $self->namespace, $self->db_flags );
87             $tx->commit;
88             return $dbi;
89             }
90              
91             sub BUILD {
92             my ($self) = @_;
93             if ( $self->single_txn ) {
94             $self->{in_txn} = $self->_mk_txn;
95             }
96             return;
97             }
98              
99             sub DEMOLISH {
100             my ($self) = @_;
101             if ( $self->{in_txn} ) {
102             $self->{in_txn}->[0]->commit;
103             delete $self->{in_txn};
104             }
105             return;
106             }
107              
108             sub _mk_txn {
109             my ($self) = @_;
110              
111             my $dbi = $self->_lmdb_dbi;
112             my $tx = $self->_lmdb_env->BeginTxn();
113             $tx->AutoCommit(1);
114             my $db = LMDB_File->new( $tx, $dbi );
115             return [ $tx, $db ];
116             }
117              
118             sub _in_txn {
119             my ( $self, $cb ) = @_;
120             if ( $self->{in_txn} ) {
121             return $cb->( @{ $self->{in_txn} } );
122             }
123             ## no critic (Variables::ProhibitLocalVars)
124             local $self->{in_txn} = $self->_mk_txn;
125             my $rval = $cb->( @{ $self->{in_txn} } );
126             $self->{in_txn}->[0]->commit;
127             return $rval;
128             }
129              
130             sub store {
131             my ( $self, $key, $value ) = @_;
132             $self->_in_txn(
133             sub {
134             my ( undef, $db ) = @_;
135             $db->put( $key, $value, $self->put_flags );
136             },
137             );
138             return $self;
139             }
140              
141             sub fetch {
142             my ( $self, $key ) = @_;
143             my $rval;
144             $self->_in_txn(
145             sub {
146             my ( undef, $db ) = @_;
147             $rval = $db->get($key);
148             },
149             );
150             return $rval;
151             }
152              
153             sub remove {
154             my ( $self, $key ) = @_;
155              
156             $self->_in_txn(
157             sub {
158             my ( undef, $db ) = @_;
159             $db->del($key);
160             },
161             );
162             return;
163             }
164              
165             sub clear {
166             my ($self) = @_;
167              
168             $self->_in_txn(
169             sub {
170             my ( undef, $db ) = @_;
171             $db->drop;
172             },
173             );
174             return;
175             }
176              
177             sub fetch_multi_hashref {
178             my ( $self, $keys ) = @_;
179             my $out = {};
180             $self->_in_txn(
181             sub {
182             my ( undef, $db ) = @_;
183             for my $key ( @{$keys} ) {
184             $out->{$key} = $db->get($key);
185             }
186             },
187             );
188             return $out;
189             }
190              
191             sub store_multi {
192             my ( $self, $key_data, $set_options ) = @_;
193             croak 'must specify key_values' unless defined $key_data;
194             $self->_in_txn(
195             sub {
196             for my $key ( keys %{$key_data} ) {
197             $self->set( $key, $key_data->{$key}, $set_options );
198             }
199             },
200             );
201             return;
202             }
203              
204             sub get_keys {
205             my ($self) = @_;
206             my @keys;
207              
208             $self->_in_txn(
209             sub {
210             my ( undef, $db ) = @_;
211             my $cursor = $db->Cursor;
212             my ( $key, $value );
213             while (1) {
214             last unless eval { $cursor->get( $key, $value, MDB_NEXT ); 1 };
215             push @keys, $key;
216             }
217             return;
218             },
219             );
220             return @keys;
221             }
222              
223             sub get_namespaces { croak 'not supported' }
224              
225             around max_key_length => sub {
226             my ( $orig, $self, @args ) = @_;
227             my $rval = $self->$orig(@args);
228             my $real_max = $self->_lmdb_max_key;
229             return $rval > $real_max ? $real_max : $rval;
230             };
231              
232             no Moo;
233              
234             1;
235              
236             __END__