File Coverage

lib/CHI/Driver/LMDB.pm
Criterion Covered Total %
statement 30 120 25.0
branch 0 14 0.0
condition n/a
subroutine 10 34 29.4
pod 0 10 0.0
total 40 178 22.4


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