File Coverage

lib/CHI/Driver/LMDB.pm
Criterion Covered Total %
statement 30 125 24.0
branch 0 14 0.0
condition n/a
subroutine 10 35 28.5
pod 0 10 0.0
total 40 184 21.7


line stmt bran cond sub pod time code
1 1     1   1094 use 5.008; # utf8
  1         4  
  1         45  
2 1     1   7 use strict;
  1         2  
  1         43  
3 1     1   5 use warnings;
  1         13  
  1         33  
4 1     1   1009 use utf8;
  1         12  
  1         5  
5              
6             package CHI::Driver::LMDB;
7              
8             our $VERSION = '0.002003';
9              
10             # ABSTRACT: use OpenLDAPs LMDB Key-Value store as a cache backend.
11              
12             our $AUTHORITY = 'cpan:KENTNL'; # AUTHORITY
13              
14 1     1   78 use Carp qw( croak );
  1         2  
  1         75  
15 1     1   987 use Moo qw( extends has around );
  1         19889  
  1         10  
16 1     1   7656 use Path::Tiny qw( path );
  1         23420  
  1         103  
17 1     1   964 use File::Spec::Functions qw( tmpdir );
  1         730  
  1         158  
18 1     1   1012 use LMDB_File 0.006 qw( MDB_CREATE MDB_NEXT );
  1         9628  
  1         1882  
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 0     0     my ($self) = @_;
48 0           my $cache_size = $self->cache_size;
49 0 0         if ( $cache_size =~ s/([km])\z//msxi ) {
50 0           $cache_size *= $sizes->{ lc $1 };
51             }
52 0           return $cache_size;
53             }
54              
55             sub _build_root_dir {
56 0     0     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 0     0     my ($self) = @_;
63 0           my $dir = path( $self->root_dir );
64 0 0         return $dir if $dir->is_dir;
65 0           $dir->mkpath( { mode => $self->dir_create_mode, } );
66 0           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 0     0     my ($self) = @_;
75 0           return LMDB::Env->new( $self->_existing_root_dir . q[], { map { $_ => $self->$_() } keys %env_opts } );
  0            
76             }
77              
78             sub _build_lmdb_max_key {
79 0     0     my ($self) = @_;
80 0           return $self->_lmdb_env->get_maxkeysize;
81             }
82              
83             sub _build_lmdb_dbi {
84 0     0     my ($self) = @_;
85 0           my $tx = $self->_lmdb_env->BeginTxn();
86 0           my $dbi = $tx->open( $self->namespace, $self->db_flags );
87 0           $tx->commit;
88 0           return $dbi;
89             }
90              
91             sub BUILD {
92 0     0 0   my ($self) = @_;
93 0 0         if ( $self->single_txn ) {
94 0           $self->{in_txn} = $self->_mk_txn;
95             }
96 0           return;
97             }
98              
99             sub DEMOLISH {
100 0     0 0   my ($self) = @_;
101 0 0         if ( $self->{in_txn} ) {
102 0           $self->{in_txn}->[0]->commit;
103 0           delete $self->{in_txn};
104             }
105 0           return;
106             }
107              
108             sub _mk_txn {
109 0     0     my ($self) = @_;
110              
111 0           my $dbi = $self->_lmdb_dbi;
112 0           my $tx = $self->_lmdb_env->BeginTxn();
113 0           $tx->AutoCommit(1);
114 0           my $db = LMDB_File->new( $tx, $dbi );
115 0           return [ $tx, $db ];
116             }
117              
118             sub _in_txn {
119 0     0     my ( $self, $cb ) = @_;
120 0 0         if ( $self->{in_txn} ) {
121 0           return $cb->( @{ $self->{in_txn} } );
  0            
122             }
123             ## no critic (Variables::ProhibitLocalVars)
124 0           local $self->{in_txn} = $self->_mk_txn;
125 0           my $rval = $cb->( @{ $self->{in_txn} } );
  0            
126 0           $self->{in_txn}->[0]->commit;
127 0           return $rval;
128             }
129              
130             sub store {
131 0     0 0   my ( $self, $key, $value ) = @_;
132             $self->_in_txn(
133             sub {
134 0     0     my ( undef, $db ) = @_;
135 0           $db->put( $key, $value, $self->put_flags );
136             },
137 0           );
138 0           return $self;
139             }
140              
141             sub fetch {
142 0     0 0   my ( $self, $key ) = @_;
143 0           my $rval;
144             $self->_in_txn(
145             sub {
146 0     0     my ( undef, $db ) = @_;
147 0           $rval = $db->get($key);
148             },
149 0           );
150 0           return $rval;
151             }
152              
153             sub remove {
154 0     0 0   my ( $self, $key ) = @_;
155              
156             $self->_in_txn(
157             sub {
158 0     0     my ( undef, $db ) = @_;
159 0           $db->del($key);
160             },
161 0           );
162 0           return;
163             }
164              
165             sub clear {
166 0     0 0   my ($self) = @_;
167              
168             $self->_in_txn(
169             sub {
170 0     0     my ( undef, $db ) = @_;
171 0           $db->drop;
172             },
173 0           );
174 0           return;
175             }
176              
177             sub fetch_multi_hashref {
178 0     0 0   my ( $self, $keys ) = @_;
179 0           my $out = {};
180             $self->_in_txn(
181             sub {
182 0     0     my ( undef, $db ) = @_;
183 0           for my $key ( @{$keys} ) {
  0            
184 0           $out->{$key} = $db->get($key);
185             }
186             },
187 0           );
188 0           return $out;
189             }
190              
191             sub store_multi {
192 0     0 0   my ( $self, $key_data, $set_options ) = @_;
193 0 0         croak 'must specify key_values' unless defined $key_data;
194             $self->_in_txn(
195             sub {
196 0     0     for my $key ( keys %{$key_data} ) {
  0            
197 0           $self->set( $key, $key_data->{$key}, $set_options );
198             }
199             },
200 0           );
201 0           return;
202             }
203              
204             sub get_keys {
205 0     0 0   my ($self) = @_;
206 0           my @keys;
207              
208             $self->_in_txn(
209             sub {
210 0     0     my ( undef, $db ) = @_;
211 0           my $cursor = $db->Cursor;
212 0           my ( $key, $value );
213 0           while (1) {
214 0 0         last unless eval { $cursor->get( $key, $value, MDB_NEXT ); 1 };
  0            
  0            
215 0           push @keys, $key;
216             }
217 0           return;
218             },
219 0           );
220 0           return @keys;
221             }
222              
223 0     0 0   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 1     1   13 no Moo;
  1         2  
  1         10  
233              
234             1;
235              
236             __END__