File Coverage

blib/lib/LMDB_File.pm
Criterion Covered Total %
statement 239 294 81.2
branch 79 156 50.6
condition 47 83 56.6
subroutine 51 63 80.9
pod 15 17 88.2
total 431 613 70.3


line stmt bran cond sub pod time code
1             package LMDB_File;
2              
3 4     4   139987 use 5.010000;
  4         12  
  4         126  
4 4     4   15 use strict;
  4         4  
  4         106  
5 4     4   17 use warnings;
  4         9  
  4         89  
6 4     4   13 use Carp;
  4         5  
  4         220  
7              
8             require Exporter;
9 4     4   1959 use AutoLoader;
  4         4777  
  4         17  
10              
11             our $VERSION = '0.07_3';
12             our $DEBUG = 0;
13              
14             our @ISA = qw(Exporter);
15             our @CARP_NOT = qw(LMDB::Env LMDB::Txn LMDB::Cursor LMDB_File);
16              
17             our @EXPORT = qw();
18             our %EXPORT_TAGS = (
19             envflags => [qw(MDB_FIXEDMAP MDB_NOSUBDIR MDB_NOSYNC MDB_RDONLY MDB_NOMETASYNC
20             MDB_NOMEMINIT MDB_WRITEMAP MDB_MAPASYNC MDB_NOTLS MDB_NOLOCK MDB_NORDAHEAD)],
21             dbflags => [qw(MDB_REVERSEKEY MDB_DUPSORT MDB_INTEGERKEY MDB_DUPFIXED
22             MDB_INTEGERDUP MDB_REVERSEDUP MDB_CREATE)],
23             writeflags => [qw(MDB_NOOVERWRITE MDB_NODUPDATA MDB_CURRENT MDB_RESERVE
24             MDB_APPEND MDB_APPENDDUP MDB_MULTIPLE)],
25             cursor_op => [qw(MDB_FIRST MDB_FIRST_DUP MDB_GET_BOTH MDB_GET_BOTH_RANGE
26             MDB_GET_CURRENT MDB_GET_MULTIPLE MDB_NEXT MDB_NEXT_DUP MDB_NEXT_MULTIPLE
27             MDB_NEXT_NODUP MDB_PREV MDB_PREV_DUP MDB_PREV_NODUP MDB_LAST MDB_LAST_DUP
28             MDB_SET MDB_SET_KEY MDB_SET_RANGE)],
29             copyflags => [qw(MDB_CP_COMPACT)],
30             error => [qw(MDB_SUCCESS MDB_KEYEXIST MDB_NOTFOUND MDB_PAGE_NOTFOUND MDB_CORRUPTED
31             MDB_PANIC MDB_VERSION_MISMATCH MDB_INVALID MDB_MAP_FULL MDB_DBS_FULL
32             MDB_READERS_FULL MDB_TLS_FULL MDB_TXN_FULL MDB_CURSOR_FULL MDB_PAGE_FULL
33             MDB_MAP_RESIZED MDB_INCOMPATIBLE MDB_BAD_RSLOT MDB_BAD_TXN MDB_BAD_VALSIZE
34             MDB_BAD_DBI MDB_LAST_ERRCODE)],
35             version => [qw(MDB_VERSION_FULL MDB_VERSION_MAJOR MDB_VERSION_MINOR
36             MDB_VERSION_PATCH MDB_VERSION_STRING MDB_VERSION_DATE)],
37             );
38             $EXPORT_TAGS{flags} = [
39             @{$EXPORT_TAGS{envflags}}, @{$EXPORT_TAGS{dbflags}},
40             @{$EXPORT_TAGS{writeflags}}, @{$EXPORT_TAGS{copyflags}}
41             ];
42             {
43             my %seen;
44             push @{$EXPORT_TAGS{all}},
45             grep {!$seen{$_}++} @{$EXPORT_TAGS{$_}} foreach keys %EXPORT_TAGS;
46             }
47             our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } );
48              
49             sub AUTOLOAD {
50 105     105   4814 my $constname;
51 105         99 our $AUTOLOAD;
52 105         416 ($constname = $AUTOLOAD) =~ s/.*:://;
53 105 50       228 croak "&LMDB_File::constant not defined" if $constname eq 'constant';
54 105         311 my ($error, $val) = constant($constname);
55 105 50       156 if ($error) { croak $error; }
  0         0  
56             {
57 4     4   1150 no strict 'refs';
  4         5  
  4         305  
  105         89  
58 105     2000317   463 *$AUTOLOAD = sub { $val };
  2000317         3404838  
59             }
60 105         242 goto &$AUTOLOAD;
61             }
62              
63             require XSLoader;
64             XSLoader::load('LMDB_File', $VERSION);
65              
66             my $dbflmask = do {
67 4     4   15 no strict 'refs';
  4         5  
  4         257  
68             my $f = 0;
69             $f |= &{"LMDB_File::$_"}() for @{$EXPORT_TAGS{dbflags}};
70             $f;
71             };
72              
73             package LMDB::Env;
74 4     4   18 use Scalar::Util ();
  4         6  
  4         115  
75 4     4   16 use Fcntl;
  4         3  
  4         13518  
76              
77             our %Envs;
78             sub new {
79 6     6   1294 my ($proto, $path, $eflags) = @_;
80 6         51 create(my $self);
81 6 50       35 return unless $self;
82 6 100 50     28 $eflags = { flags => ($eflags || 0) } unless ref $eflags;
83 6 50 66     47 $eflags->{mapsize} and $self->set_mapsize($eflags->{mapsize})
84             and return;
85 6 50 66     42 $eflags->{maxdbs} and $self->set_maxdbs($eflags->{maxdbs})
86             and return;
87 6 50 33     18 $eflags->{maxreaders} and $self->set_maxreaders($eflags->{maxreaders})
88             and return;
89 6 50       20 if($^O =~ /openbsd/) {
90             # OpenBSD lacks an unified buffer cache (UBC) so LMDB only works
91             # with MDB_WRITEMAP set when not in read-only mode
92 0 0       0 $eflags->{flags} |= LMDB_File::MDB_WRITEMAP()
93             unless $eflags->{flags} & LMDB_File::MDB_RDONLY();
94             }
95 6 50 50     1356 $self->open($path, $eflags->{flags}, $eflags->{mode} || 0600)
96             and return;
97 4 50       35180 warn "Created LMDB::Env $$self\n" if $DEBUG;
98 4         26 return $self;
99             }
100              
101             sub Clean {
102 0     0   0 my $self = shift;
103 0 0       0 my $txl = $Envs{ $$self }[0] or return;
104 0 0       0 if(@$txl) {
105 0         0 Carp::carp("LMDB: Aborting $#$txl transactions in $$self.");
106 0         0 $txl->[$#$txl]->abort;
107             }
108 0         0 $Envs{ $$self }[0] = [];
109             }
110              
111             sub DESTROY {
112 6     6   49 my $self = shift;
113 6 100       76 if(my $evd = delete $Envs{ $$self }) {
114 4         7 my $txl = $evd->[0];
115 4 50       15 if(@$txl) { # Only posible at global destruction.
116 0         0 Carp::carp("LMDB: OOPS! Destroying an active environment!");
117 0         0 Carp::carp("LMDB: Aborting $#$txl transactions in $$self.");
118 0         0 $txl->[$#$txl]->abort;
119             }
120             }
121 6         5149 $self->close;
122 6 50       241 warn "Closed LMDB::Env $$self (remains @{[scalar keys %Envs]})\n"
  0         0  
123             if $DEBUG;
124             }
125              
126             sub BeginTxn {
127 14     14   12740 my $self = shift;
128 14         69 $self->get_flags(my $eflags);
129 14   66     73 my $tflags = shift || ($eflags & LMDB_File::MDB_RDONLY());
130 14         37 my $txl = $Envs{ $$self }[0];
131 14 50       36 warn "BeginTxn $$self($$), deep: ", scalar(@$txl), "\n" if $DEBUG;
132 14 100       42 return $txl->[0]->SubTxn($tflags) if @$txl;
133 13         51 LMDB::Txn->new($self, $tflags);
134             }
135              
136             sub CLONE {
137             # After a thread is created all Txns of parent thread are forgot
138 0     0   0 $_->[0] = [] for values %Envs;
139 0         0 _clone();
140 0         0 1;
141             }
142              
143             package LMDB::Txn;
144              
145             our %Txns;
146             my %Cursors;
147              
148             sub new {
149 14     14   26 my ($parent, $env, $tflags) = @_;
150 14         24 my $txl = $Envs{ $$env }[0];
151 14 50 66     71 Carp::croak("Transaction active, should be subtransaction")
152             if !ref($parent) && @$txl;
153 14   66     203 _begin($env, ref($parent) && $parent, $tflags, my $self);
154 14 50       47 return unless $self;
155 14         34 $Txns{$$self} = {
156             Active => 1,
157             Env => $env, # A transaction references the environment
158             RO => $tflags & LMDB_File::MDB_RDONLY(),
159             };
160 14         36 unshift @$txl, $self;
161 14         50 Scalar::Util::weaken($txl->[0]);
162 14 50       31 warn "Created LMDB::Txn $$self in $$env\n" if $DEBUG;
163 14         63 return $self;
164             }
165              
166             sub SubTxn {
167 1     1   2 my $self = shift;
168 1 50       5 if($^O =~ /openbsd/) {
169             # Needs MDB_WRITEMAP so
170 0         0 Carp::croak("Subtransactions are unsupported in this OS");
171             }
172 1   50     5 my $tflags = shift || 0;
173 1         4 return $self->new($self->env, $tflags);
174             }
175              
176             sub DESTROY {
177 14     14   1852 my $self = shift;
178 14 100       90 my $td = $Txns{ $$self } or return;
179 11 100 66     92 if($td->{Active} && !$td->{RO} && $td->{AC}) {
      100        
180 2 50       16 warn "LMDB: Destroying an active transaction, commiting $$self...\n"
181             if $DEBUG;
182 2         7 $self->commit;
183             } else {
184 9 50       24 warn "LMDB: Destroying an active transaction, aborting $$self...\n"
185             if $DEBUG;
186 9         22 $self->abort;
187             }
188             }
189              
190             sub _prune {
191 14     14   21 my $self = shift;
192 14         18 my $eid = shift;
193 14 50 33     138 if(my $txl = $Envs{ $eid } && $Envs{ $eid }[0]) {
194 14         49 while(my $rel = shift @$txl) {
195 14         45 my $td = delete $Txns{ $$rel };
196 14         41 delete $Cursors{$_} for keys %{ $td->{Cursors} };
  14         88  
197 14 50       69 last if $$rel == $$self;
198             }
199 14 50       48 warn "LMDB::Txn: Txns list deep: @{[scalar @$txl]}\n" if $DEBUG > 2;
  0         0  
200             }
201 14 50       32 warn "LMDB::Txn: $$self($$) finalized in $eid\n" if $DEBUG > 1;
202 14         107 $$self = 0;
203             }
204              
205             sub abort {
206 10     10   37 my $self = shift;
207 10 100       27 unless($Txns{ $$self }) {
208 1         13 Carp::carp("Terminated transaction");
209 1         465 return;
210             }
211 9         31 my $eid = $self->_env;
212 9         35 $self->_abort;
213 9 50       19 warn "LMDB::Txn $$self aborted\n" if $DEBUG;
214 9         22 $self->_prune($eid);
215             }
216              
217             sub commit {
218 6     6   1358 my $self = shift;
219 6 100       45 my $td = $Txns{ $$self } or Carp::croak("Terminated transaction");
220 5 50       14 Carp::croak("Not an active transaction") unless $td->{Active};
221 5         17 my $eid = $self->_env;
222 5         1118105 $self->_commit;
223 5 50       41 warn "LMDB::Txn $$self commited\n" if $DEBUG;
224 5         40 $self->_prune($eid);
225             }
226              
227             sub Flush {
228 0     0   0 my $self = shift;
229 0 0       0 my $td = $Txns{ $$self } or Carp::croak("Terminated transaction");
230 0 0       0 Carp::croak("Not an active transaction") unless $td->{Active};
231 0         0 $self->_commit;
232             # This depends on malloc order, beware!
233 0         0 _begin($td->{Env}, undef, $td->{RO}, my $ntxn);
234 0 0       0 Carp::croak("Can't recreate Txn") unless $$ntxn == $$self;
235 0         0 $$ntxn = 0;
236             }
237              
238             sub reset {
239 0     0   0 my $self = shift;
240 0 0       0 my $td = $Txns{ $$self } or Carp::croak("Terminated transaction");
241 0 0       0 Carp::croak("Not a read-only transaction") unless $td->{RO};
242 0 0       0 $self->_reset if $td->{Active};
243 0         0 $td->{Active} = 0;
244             }
245              
246             sub renew {
247 0     0   0 my $self = shift;
248 0 0       0 my $td = $Txns{ $$self } or Carp::croak("Terminated transaction");
249 0 0       0 $self->_reset if $td->{Active};
250 0         0 $self->_renew;
251 0         0 $td->{Active} = 1;
252             }
253              
254             sub OpenDB {
255 16     16   142 my ($self, $name, $flags) = @_;
256 16 100       75 my $options = ref($name) eq 'HASH' ? $name : { dbname => $name, flags => $flags };
257 16         69 LMDB_File->open($self, $options->{dbname}, $options->{flags});
258             }
259              
260             sub env {
261 3     3   285 my $self = shift;
262 3 50       25 $Txns{$$self} && $Txns{$$self}{Env};
263             }
264              
265             sub AutoCommit {
266 2     2   7 my $self = shift;
267 2 50       9 my $td = $Txns{ $$self } or Carp::croak("Terminated transaction");
268 2         5 my $prev = $td->{AC};
269 2 50       6 $td->{AC} = shift if(@_);
270 2         5 $prev;
271             }
272              
273             # Fast low-level dbi API
274             sub open {
275 20     20   32 my($txn, $name, $flags) = @_;
276 20   100     63 $flags ||= 0;
277 20 100       69 Carp::croak("Not an alive transaction") unless $Txns{ $$txn };
278 19         109 Carp::croak("Not the current child transaction")
279 19 50       23 unless(${$Envs{ $txn->_env }[0][0]} == $$txn);
280 19         210 _dbi_open($txn, $name, $flags & $dbflmask, my $dbi);
281 15 50 33     63 warn "Opened dbi $dbi\n" if $dbi && $DEBUG;
282 15         47 return $dbi;
283             }
284             *get = \&LMDB_File::_get;
285             *put = \&LMDB_File::_put;
286             *del = \&LMDB_File::_del;
287              
288             sub CLONE_SKIP {
289             # All LMDB Transactions are usable only in the thread that create it
290 0     0   0 1;
291             }
292              
293             package LMDB::Cursor;
294              
295             sub get {
296 96     96   87 LMDB_File::_chkalive($Cursors{${$_[0]}});
  96         183  
297 96         419 goto &_get;
298             }
299              
300             sub put {
301 0     0   0 LMDB_File::_chkalive($Cursors{${$_[0]}});
  0         0  
302 0         0 goto &_put;
303             }
304              
305             sub del {
306 0     0   0 LMDB_File::_chkalive($Cursors{${$_[0]}});
  0         0  
307 0         0 goto &_del;
308             }
309              
310             sub DESTROY {
311 6     6   1293 my $self = shift;
312 6 50       21 return unless $Cursors{$$self};
313 6         30 my $txnId = $self->txn;
314 6         21 $self->close;
315 6         26 delete $Txns{$txnId}{Cursors}{$$self};
316 6         32 delete $Cursors{$$self};
317             }
318              
319             package LMDB_File;
320 0     0   0 sub CLONE_SKIP { 1; }
321              
322             our $die_on_err = 1;
323             our $last_err = 0;
324              
325             sub new {
326 2     2 1 4 my($proto, $txn, $dbi) = @_;
327 2 50       15 Carp::croak("Need a Txn") unless $txn->isa('LMDB::Txn');
328 2   33     18 bless [ $txn, $dbi ], ref($proto) || $proto;
329             }
330              
331             sub open {
332 19     19 1 63 my $proto = shift;
333 19         28 my $class = ref $proto;
334 19 100       43 my $txn = $class ? $proto->[0] : shift;
335 19 50       93 Carp::croak("Need a Txn") unless $txn->isa('LMDB::Txn');
336 19 50       42 my $dbi = $txn->open(@_) or return;
337 14   66     144 bless [ $txn, $dbi ], $class || $proto;
338             }
339              
340             sub DESTROY {
341 16     16   1856 my $self = shift;
342             }
343              
344             sub _chkalive {
345 267     267   263 my $self = shift;
346 267         347 my $txn = $self->[0];
347 267 50 50     1890 Carp::croak("Not an active transaction")
      66        
      33        
348             unless($txn && ($Txns{ $$txn } || undef $self->[0]) && $Txns{ $$txn }{Active} );
349             # A parent transaction and its cursors may not issue any other operations than
350             # mdb_txn_commit and mdb_txn_abort while it has active child transactions.
351 266         1070 Carp::croak("Not the current child transaction")
352 266 50       263 unless(${$Envs{ $txn->_env }[0][0]} == $$txn);
353 266         1115 $txn, $self->[1];
354             }
355              
356             sub Alive {
357 5     5 1 673 my $self = shift;
358 5         12 my $txn = $self->[0];
359 5 50 66     52 $txn && (($Txns{ $$txn } && $self->[1]) || undef $self->[0]);
      100        
360             }
361              
362             sub flags {
363 2     2 1 4 my $self = shift;
364 2         7 _dbi_flags(_chkalive($self), my $flags);
365 2         35 $flags;
366             }
367              
368             sub put {
369 90     90 1 7080 my $self = shift;
370 90 50       158 warn "put: '$_[0]' => '$_[1]'\n" if $DEBUG > 2;
371 90         114 _put(_chkalive($self), @_);
372 89         304 $_[1];
373             }
374              
375             sub get {
376 28 50   28 1 5729 warn "get: '$_[1]'\n" if $DEBUG > 2;
377 28         57 my($txn, $dbi) = _chkalive($_[0]);
378 27 50       55 return _get($txn, $dbi, $_[1], $_[2]) if @_ > 2;
379 27         24 my($res, $data);
380             {
381 27         23 local $die_on_err = 0;
  27         30  
382 27         235 $res = _get($txn, $dbi, $_[1], $data);
383             }
384 27 50 66     172 croak($@) if $res && $die_on_err && $res != MDB_NOTFOUND() or undef $@;
      33        
      50        
385 27         100 $data;
386             }
387              
388             sub Rget {
389 0 0   0 0 0 warn "get: '$_[1]'\n" if $DEBUG > 2;
390 0         0 local $die_on_err = 0;
391 0         0 _get(_chkalive($_[0]), $_[1], my $data);
392 0         0 return \$data;
393             }
394              
395              
396             sub del {
397 3     3 1 9 _del(_chkalive($_[0]), $_[1], $_[2]);
398             }
399              
400             sub stat {
401 26     26 1 3379 _stat(_chkalive($_[0]));
402             }
403              
404             sub set_dupsort {
405 0     0 0 0 my $self = shift;
406 0         0 my $txn = $self->[0];
407 0         0 $Envs{ $txn->_env }[1][ $self->[1] ] = shift;
408             }
409              
410             sub set_compare {
411 1     1 1 3 my $self = shift;
412 1         3 my $txn = $self->[0];
413 1         8 $Envs{ $txn->_env }[2][ $self->[1] ] = shift;
414             }
415              
416             sub Cursor {
417 6     6 1 9 my $DB = shift;
418 6         20 my ($txn, $dbi) = _chkalive($DB);
419 6         61 LMDB::Cursor::open($txn, $dbi, my $cursor);
420 6 50       37 return unless $cursor;
421 6         25 $Txns{$$txn}{Cursors}{$$cursor} = 1;
422 6         13 $Cursors{$$cursor} = $DB;
423 6 50       27 warn "Cursor opened for #$dbi\n" if $DEBUG;
424 6         17 $cursor;
425             }
426              
427 2     2 1 24 sub Txn : lvalue { $_[0][0]; }
428              
429 2     2 1 15 sub dbi : lvalue { $_[0][1]; }
430              
431             sub drop {
432 3   50 3 1 39 _drop(_chkalive($_[0]), $_[1] || 0);
433             }
434              
435             sub TIEHASH {
436 3     3   8 my $proto = shift;
437 3 100 66     12 return $proto if ref($proto) && _chkalive($proto); # Auto
438 1         1 my $mux = shift;
439 1         2 my $options = shift;
440 1 50       2 $options = { flags => $options } unless ref $options; # DBM Compat
441 1         2 my $txn;
442 1 50       4 if(ref $mux eq 'LMDB::Txn') {
    50          
443 0         0 $txn = $mux;
444             } elsif(ref $mux eq 'LMDB::Env') {
445 0         0 $txn = $mux->BeginTxn;
446 0         0 $txn->AutoCommit(1);
447             } else { # mux is dir
448 1 50       3 $options->{mode} = shift if @_; # DBM Compat
449 1         5 $txn = LMDB::Env->new($mux, $options)->BeginTxn;
450 1         4 $txn->AutoCommit(1);
451             }
452 1         3 $txn->OpenDB($options);
453             }
454              
455             sub FETCH {
456 7     7   74 my($self, $key) = @_;
457 7         10 my ($data, $res);
458             {
459 7         8 local $die_on_err = 0;
  7         13  
460 7         14 $res = _get(_chkalive($self), $key, $data);
461             }
462 7 50 66     43 croak($@) if $res && $die_on_err && $res != MDB_NOTFOUND() or undef $@;
      33        
      50        
463 7         24 $data;
464             }
465              
466             *STORE = \&put;
467             *CLEAR = \&drop;
468              
469             sub UNTIE {
470 3     3   13 my $self = shift;
471 3         5 my $txn = $self->[0];
472 3 50 50     21 return unless($txn && ($Txns{ $$txn } || undef($self->[0])));
      33        
473 3         11 delete $self->[2]; # Free cursor
474             }
475              
476             sub SCALAR {
477 5     5   528 return $_[0]->stat->{entries};
478             }
479              
480             sub EXISTS {
481 2     2   1440 my($self, $key) = @_;
482 2         5 local $die_on_err = 0;
483 2         7 return !_get(_chkalive($self), $key, my $dummy);
484             }
485              
486             sub DELETE {
487 1     1   3 my($self, $key) = @_;
488 1         3 my @self = _chkalive($self);
489 1         3 my $data;
490 1         2 local $die_on_err = 0;
491 1 50       10 if(_get(@self, $key, $data) != MDB_NOTFOUND()) {
492 1         23 _del(@self, $key, undef);
493             }
494 1         7 return $data;
495             }
496              
497             sub FIRSTKEY {
498 4     4   16 my $self = shift;
499 4         10 $self->[2] = $self->Cursor;
500 4         14 $self->NEXTKEY;
501             }
502              
503             # I hop some day tie hashed are optimized
504             sub NEXTKEY {
505 85     85   1346 my($self, $key) = @_;
506 85 100       176 my $op = defined($key) ? MDB_NEXT() : MDB_FIRST() ;
507 85         96 local $die_on_err = 0;
508 85         175 my $res = $self->[2]->get($key, my $data, $op);
509 85 100       133 if($res == MDB_NOTFOUND()) {
510 4         39 return;
511             }
512 81 50       336 return wantarray ? ($key, $data) : $key;
513             }
514              
515             sub _mydbflags {
516 1     1   2 my($envid, $dbi, $bit) = @_;
517 1         7 my $cm = \vec($Envs{ $envid }[3], $dbi, LMDB_OFLAGN());
518 1         3 my $om = $$cm;
519 1 50       4 if(@_ > 3) {
520 1 50       5 $$cm = $_[3] ? ($$cm | $bit) : ($$cm & ~$bit);
521 1         6 _resetcurdbi();
522             }
523 1         5 return $om & $bit;
524             }
525              
526             sub ReadMode {
527 0     0 1 0 my $self = shift;
528 0         0 my($txn, $dbi) = _chkalive($self);
529 0         0 _mydbflags($txn->_env, $dbi, 1, @_);
530             }
531              
532             sub UTF8 {
533 1     1 1 432 my $self = shift;
534 1         4 my($txn, $dbi) = _chkalive($self);
535 1         5 _mydbflags($txn->_env, $dbi, 2, @_);
536             }
537              
538             1;
539             __END__