39987910593cf2890d1d04f40ecb8fb0778e400a
[dbsrgits/DBM-Deep.git] / lib / DBM / Deep.pm
1 package DBM::Deep;
2
3 use 5.006_000;
4
5 use strict;
6 use warnings FATAL => 'all';
7
8 our $VERSION = q(1.0015);
9
10 use Scalar::Util ();
11
12 use DBM::Deep::Engine::File ();
13
14 use DBM::Deep::SQL::Util;
15 use DBM::Deep::SQL::Array;
16 use DBM::Deep::SQL::Hash;
17
18 use overload
19     '""' => sub { overload::StrVal( $_[0] ) },
20     fallback => 1;
21
22 use constant DEBUG => 0;
23
24 sub TYPE_HASH   () { DBM::Deep::Engine->SIG_HASH  }
25 sub TYPE_ARRAY  () { DBM::Deep::Engine->SIG_ARRAY }
26
27 # This is used in all the children of this class in their TIE<type> methods.
28 sub _get_args {
29     my $proto = shift;
30
31     my $args;
32     if (scalar(@_) > 1) {
33         if ( @_ % 2 ) {
34             $proto->_throw_error( "Odd number of parameters to " . (caller(1))[2] );
35         }
36         $args = {@_};
37     }
38     elsif ( ref $_[0] ) {
39         unless ( eval { local $SIG{'__DIE__'}; %{$_[0]} || 1 } ) {
40             $proto->_throw_error( "Not a hashref in args to " . (caller(1))[2] );
41         }
42         $args = $_[0];
43     }
44     else {
45         $args = { file => shift };
46     }
47
48     return $args;
49 }
50
51 # Class constructor method for Perl OO interface.
52 # Calls tie() and returns blessed reference to tied hash or array,
53 # providing a hybrid OO/tie interface.
54 sub new {
55     my $class = shift;
56     my $args = $class->_get_args( @_ );
57     my $self;
58     
59     if (exists $args->{dbi}) {
60         eval {
61             require DBIx::Abstract;
62         }; if ( $@ ) {
63             __PACKAGE__->_throw_error('DBIx::Abstract not installed. You cannot use the SQL mode.');
64         }
65         unless (UNIVERSAL::isa($args->{dbi}, 'DBIx::Abstract')) {
66             $args->{dbi} = DBIx::Abstract->connect($args->{dbi});
67         }
68
69         if (defined $args->{id}) {
70             unless ($args->{id} =~ /^\d+$/ && $args->{id} > 0) {
71                 __PACKAGE__->_throw_error('Invalid SQL record id');
72             }
73             my $util = {dbi => $args->{dbi}};
74             bless $util, 'DBM::Deep::SQL::Util';
75             my $q = $util->_select(
76                 table  => 'rec_item',
77                 fields => 'item_type',
78                 where  => {id => $args->{id}},
79             );
80             if ($q->[0]->[0] eq 'array') {
81                 $args->{type} = TYPE_ARRAY;
82             }
83             elsif ($q->[0]->[0] eq 'hash') {
84                 $args->{type} = TYPE_HASH;
85             }
86             else {
87                 DBM::Deep->_throw_error('Unknown SQL record id');
88             }
89         }
90         else {
91             my $util = {dbi => $args->{dbi}};
92             bless $util, 'DBM::Deep::SQL::Util';
93             if (defined($args->{type}) && $args->{type} eq TYPE_ARRAY) {
94                 $args->{id} = $util->_create('array');
95             }
96             else {
97                 $args->{id} = $util->_create('hash');
98             }
99         }
100
101         if (defined($args->{type}) && $args->{type} eq TYPE_ARRAY) {
102             $class = 'DBM::Deep::SQL::Array';
103             require DBM::Deep::SQL::Array;
104             tie @$self, $class, %$args;
105             if ($args->{prefetch}) {
106                 (tied(@$self))->_prefetch();
107             }
108             return bless $self, $class;
109         }
110         else {
111             $class = 'DBM::Deep::SQL::Hash';
112             require DBM::Deep::SQL::Hash;
113             tie %$self, $class, %$args;
114             if ($args->{prefetch}) {
115                 (tied(%$self))->_prefetch();
116             }
117             return bless $self, $class;
118         }
119     }
120
121     ##
122     # Check if we want a tied hash or array.
123     ##
124     if (defined($args->{type}) && $args->{type} eq TYPE_ARRAY) {
125         $class = 'DBM::Deep::Array';
126         require DBM::Deep::Array;
127         tie @$self, $class, %$args;
128     }
129     else {
130         $class = 'DBM::Deep::Hash';
131         require DBM::Deep::Hash;
132         tie %$self, $class, %$args;
133     }
134
135     return bless $self, $class;
136 }
137
138 # This initializer is called from the various TIE* methods. new() calls tie(),
139 # which allows for a single point of entry.
140 sub _init {
141     my $class = shift;
142     my ($args) = @_;
143
144     # locking implicitly enables autoflush
145     if ($args->{locking}) { $args->{autoflush} = 1; }
146
147     # These are the defaults to be optionally overridden below
148     my $self = bless {
149         type        => TYPE_HASH,
150         base_offset => undef,
151         staleness   => undef,
152         engine      => undef,
153     }, $class;
154
155     unless ( exists $args->{engine} ) {
156         my $class = exists $args->{dbi}
157             ? 'DBM::Deep::Engine::DBI'
158             : 'DBM::Deep::Engine::File';
159
160         $args->{engine} = $class->new({
161             %{$args},
162             obj => $self,
163         });
164     }
165
166     # Grab the parameters we want to use
167     foreach my $param ( keys %$self ) {
168         next unless exists $args->{$param};
169         $self->{$param} = $args->{$param};
170     }
171
172     eval {
173         local $SIG{'__DIE__'};
174
175         $self->lock_exclusive;
176         $self->_engine->setup( $self );
177         $self->unlock;
178     }; if ( $@ ) {
179         my $e = $@;
180         eval { local $SIG{'__DIE__'}; $self->unlock; };
181         die $e;
182     }
183
184     return $self;
185 }
186
187 sub TIEHASH {
188     shift;
189     require DBM::Deep::Hash;
190     return DBM::Deep::Hash->TIEHASH( @_ );
191 }
192
193 sub TIEARRAY {
194     shift;
195     require DBM::Deep::Array;
196     return DBM::Deep::Array->TIEARRAY( @_ );
197 }
198
199 sub lock_exclusive {
200     my $self = shift->_get_self;
201     return $self->_engine->lock_exclusive( $self, @_ );
202 }
203 *lock = \&lock_exclusive;
204 sub lock_shared {
205     my $self = shift->_get_self;
206     return $self->_engine->lock_shared( $self, @_ );
207 }
208
209 sub unlock {
210     my $self = shift->_get_self;
211     return $self->_engine->unlock( $self, @_ );
212 }
213
214 sub _copy_value {
215     my $self = shift->_get_self;
216     my ($spot, $value) = @_;
217
218     if ( !ref $value ) {
219         ${$spot} = $value;
220     }
221     else {
222         my $r = Scalar::Util::reftype( $value );
223         my $tied;
224         if ( $r eq 'ARRAY' ) {
225             $tied = tied(@$value);
226         }
227         elsif ( $r eq 'HASH' ) {
228             $tied = tied(%$value);
229         }
230         else {
231             __PACKAGE__->_throw_error( "Unknown type for '$value'" );
232         }
233
234         if ( eval { local $SIG{__DIE__}; $tied->isa( __PACKAGE__ ) } ) {
235             ${$spot} = $tied->_repr;
236             $tied->_copy_node( ${$spot} );
237         }
238         else {
239             if ( $r eq 'ARRAY' ) {
240                 ${$spot} = [ @{$value} ];
241             }
242             else {
243                 ${$spot} = { %{$value} };
244             }
245         }
246
247         my $c = Scalar::Util::blessed( $value );
248         if ( defined $c && !$c->isa( __PACKAGE__ ) ) {
249             ${$spot} = bless ${$spot}, $c
250         }
251     }
252
253     return 1;
254 }
255
256 #sub _copy_node {
257 #    die "Must be implemented in a child class\n";
258 #}
259 #
260 #sub _repr {
261 #    die "Must be implemented in a child class\n";
262 #}
263
264 sub export {
265     my $self = shift->_get_self;
266
267     my $temp = $self->_repr;
268
269     $self->lock_exclusive;
270     $self->_copy_node( $temp );
271     $self->unlock;
272
273     my $classname = $self->_engine->get_classname( $self );
274     if ( defined $classname ) {
275       bless $temp, $classname;
276     }
277
278     return $temp;
279 }
280
281 sub _check_legality {
282     my $self = shift;
283     my ($val) = @_;
284
285     my $r = Scalar::Util::reftype( $val );
286
287     return $r if !defined $r || '' eq $r;
288     return $r if 'HASH' eq $r;
289     return $r if 'ARRAY' eq $r;
290
291     __PACKAGE__->_throw_error(
292         "Storage of references of type '$r' is not supported."
293     );
294 }
295
296 sub import {
297     return if !ref $_[0]; # Perl calls import() on use -- ignore
298
299     my $self = shift->_get_self;
300     my ($struct) = @_;
301
302     my $type = $self->_check_legality( $struct );
303     if ( !$type ) {
304         __PACKAGE__->_throw_error( "Cannot import a scalar" );
305     }
306
307     if ( substr( $type, 0, 1 ) ne $self->_type ) {
308         __PACKAGE__->_throw_error(
309             "Cannot import " . ('HASH' eq $type ? 'a hash' : 'an array')
310             . " into " . ('HASH' eq $type ? 'an array' : 'a hash')
311         );
312     }
313
314     my %seen;
315     my $recurse;
316     $recurse = sub {
317         my ($db, $val) = @_;
318
319         my $obj = 'HASH' eq Scalar::Util::reftype( $db ) ? tied(%$db) : tied(@$db);
320         $obj ||= $db;
321
322         my $r = $self->_check_legality( $val );
323         if ( 'HASH' eq $r ) {
324             while ( my ($k, $v) = each %$val ) {
325                 my $r = $self->_check_legality( $v );
326                 if ( $r ) {
327                     my $temp = 'HASH' eq $r ? {} : [];
328                     if ( my $c = Scalar::Util::blessed( $v ) ) {
329                         bless $temp, $c;
330                     }
331                     $obj->put( $k, $temp );
332                     $recurse->( $temp, $v );
333                 }
334                 else {
335                     $obj->put( $k, $v );
336                 }
337             }
338         }
339         elsif ( 'ARRAY' eq $r ) {
340             foreach my $k ( 0 .. $#$val ) {
341                 my $v = $val->[$k];
342                 my $r = $self->_check_legality( $v );
343                 if ( $r ) {
344                     my $temp = 'HASH' eq $r ? {} : [];
345                     if ( my $c = Scalar::Util::blessed( $v ) ) {
346                         bless $temp, $c;
347                     }
348                     $obj->put( $k, $temp );
349                     $recurse->( $temp, $v );
350                 }
351                 else {
352                     $obj->put( $k, $v );
353                 }
354             }
355         }
356     };
357     $recurse->( $self, $struct );
358
359     return 1;
360 }
361
362 #XXX Need to keep track of who has a fh to this file in order to
363 #XXX close them all prior to optimize on Win32/cygwin
364 # Rebuild entire database into new file, then move
365 # it back on top of original.
366 sub optimize {
367     my $self = shift->_get_self;
368
369     # Optimizing is only something we need to do when we're working with our
370     # own file format. Otherwise, let the other guy do the optimizations.
371     return unless $self->_engine->isa( 'DBM::Deep::Engine::File' );
372
373 #XXX Need to create a new test for this
374 #    if ($self->_engine->storage->{links} > 1) {
375 #        $self->_throw_error("Cannot optimize: reference count is greater than 1");
376 #    }
377
378     #XXX Do we have to lock the tempfile?
379
380     #XXX Should we use tempfile() here instead of a hard-coded name?
381     my $temp_filename = $self->_engine->storage->{file} . '.tmp';
382     my $db_temp = __PACKAGE__->new(
383         file => $temp_filename,
384         type => $self->_type,
385
386         # Bring over all the parameters that we need to bring over
387         ( map { $_ => $self->_engine->$_ } qw(
388             byte_size max_buckets data_sector_size num_txns
389         )),
390     );
391
392     $self->lock_exclusive;
393     $self->_engine->clear_cache;
394     $self->_copy_node( $db_temp );
395     $db_temp->_engine->storage->close;
396     undef $db_temp;
397
398     ##
399     # Attempt to copy user, group and permissions over to new file
400     ##
401     $self->_engine->storage->copy_stats( $temp_filename );
402
403     # q.v. perlport for more information on this variable
404     if ( $^O eq 'MSWin32' || $^O eq 'cygwin' ) {
405         ##
406         # Potential race condition when optmizing on Win32 with locking.
407         # The Windows filesystem requires that the filehandle be closed
408         # before it is overwritten with rename().  This could be redone
409         # with a soft copy.
410         ##
411         $self->unlock;
412         $self->_engine->storage->close;
413     }
414
415     if (!rename $temp_filename, $self->_engine->storage->{file}) {
416         unlink $temp_filename;
417         $self->unlock;
418         $self->_throw_error("Optimize failed: Cannot copy temp file over original: $!");
419     }
420
421     $self->unlock;
422     $self->_engine->storage->close;
423
424     $self->_engine->storage->open;
425     $self->lock_exclusive;
426     $self->_engine->setup( $self );
427     $self->unlock;
428
429     return 1;
430 }
431
432 sub clone {
433     ##
434     # Make copy of object and return
435     ##
436     my $self = shift->_get_self;
437
438     return __PACKAGE__->new(
439         type        => $self->_type,
440         base_offset => $self->_base_offset,
441         staleness   => $self->_staleness,
442         engine      => $self->_engine,
443     );
444 }
445
446 #XXX Migrate this to the engine, where it really belongs and go through some
447 # API - stop poking in the innards of someone else..
448 {
449     my %is_legal_filter = map {
450         $_ => ~~1,
451     } qw(
452         store_key store_value
453         fetch_key fetch_value
454     );
455
456     sub set_filter {
457         my $self = shift->_get_self;
458         my $type = lc shift;
459         my $func = shift;
460
461         if ( $is_legal_filter{$type} ) {
462             $self->_engine->storage->{"filter_$type"} = $func;
463             return 1;
464         }
465
466         return;
467     }
468
469     sub filter_store_key   { $_[0]->set_filter( store_key   => $_[1] ); }
470     sub filter_store_value { $_[0]->set_filter( store_value => $_[1] ); }
471     sub filter_fetch_key   { $_[0]->set_filter( fetch_key   => $_[1] ); }
472     sub filter_fetch_value { $_[0]->set_filter( fetch_value => $_[1] ); }
473 }
474
475 sub begin_work {
476     my $self = shift->_get_self;
477     $self->lock_exclusive;
478     my $rv = eval { $self->_engine->begin_work( $self, @_ ) };
479     my $e = $@;
480     $self->unlock;
481     die $e if $e;
482     return $rv;
483 }
484
485 sub rollback {
486     my $self = shift->_get_self;
487     $self->lock_exclusive;
488     my $rv = eval { $self->_engine->rollback( $self, @_ ) };
489     my $e = $@;
490     $self->unlock;
491     die $e if $e;
492     return $rv;
493 }
494
495 sub commit {
496     my $self = shift->_get_self;
497     $self->lock_exclusive;
498     my $rv = eval { $self->_engine->commit( $self, @_ ) };
499     my $e = $@;
500     $self->unlock;
501     die $e if $e;
502     return $rv;
503 }
504
505 # Accessor methods
506 sub _engine {
507     my $self = $_[0]->_get_self;
508     return $self->{engine};
509 }
510
511 sub _type {
512     my $self = $_[0]->_get_self;
513     return $self->{type};
514 }
515
516 sub _base_offset {
517     my $self = $_[0]->_get_self;
518     return $self->{base_offset};
519 }
520
521 sub _staleness {
522     my $self = $_[0]->_get_self;
523     return $self->{staleness};
524 }
525
526 # Utility methods
527 sub _throw_error {
528     my $n = 0;
529     while( 1 ) {
530         my @caller = caller( ++$n );
531         next if $caller[0] =~ m/^DBM::Deep/;
532
533         die "DBM::Deep: $_[1] at $0 line $caller[2]\n";
534     }
535 }
536
537 # Store single hash key/value or array element in database.
538 sub STORE {
539     my $self = shift->_get_self;
540     my ($key, $value) = @_;
541     warn "STORE($self, '$key', '@{[defined$value?$value:'undef']}')\n" if DEBUG;
542
543     unless ( $self->_engine->storage->is_writable ) {
544         $self->_throw_error( 'Cannot write to a readonly filehandle' );
545     }
546
547     $self->lock_exclusive;
548
549     # User may be storing a complex value, in which case we do not want it run
550     # through the filtering system.
551     if ( !ref($value) && $self->_engine->storage->{filter_store_value} ) {
552         $value = $self->_engine->storage->{filter_store_value}->( $value );
553     }
554
555     $self->_engine->write_value( $self, $key, $value );
556
557     $self->unlock;
558
559     return 1;
560 }
561
562 # Fetch single value or element given plain key or array index
563 sub FETCH {
564     my $self = shift->_get_self;
565     my ($key) = @_;
566     warn "FETCH($self, '$key')\n" if DEBUG;
567
568     $self->lock_shared;
569
570     my $result = $self->_engine->read_value( $self, $key );
571
572     $self->unlock;
573
574     # Filters only apply to scalar values, so the ref check is making
575     # sure the fetched bucket is a scalar, not a child hash or array.
576     return ($result && !ref($result) && $self->_engine->storage->{filter_fetch_value})
577         ? $self->_engine->storage->{filter_fetch_value}->($result)
578         : $result;
579 }
580
581 # Delete single key/value pair or element given plain key or array index
582 sub DELETE {
583     my $self = shift->_get_self;
584     my ($key) = @_;
585     warn "DELETE($self, '$key')\n" if DEBUG;
586
587     unless ( $self->_engine->storage->is_writable ) {
588         $self->_throw_error( 'Cannot write to a readonly filehandle' );
589     }
590
591     $self->lock_exclusive;
592
593     ##
594     # Delete bucket
595     ##
596     my $value = $self->_engine->delete_key( $self, $key);
597
598     if (defined $value && !ref($value) && $self->_engine->storage->{filter_fetch_value}) {
599         $value = $self->_engine->storage->{filter_fetch_value}->($value);
600     }
601
602     $self->unlock;
603
604     return $value;
605 }
606
607 # Check if a single key or element exists given plain key or array index
608 sub EXISTS {
609     my $self = shift->_get_self;
610     my ($key) = @_;
611     warn "EXISTS($self, '$key')\n" if DEBUG;
612
613     $self->lock_shared;
614
615     my $result = $self->_engine->key_exists( $self, $key );
616
617     $self->unlock;
618
619     return $result;
620 }
621
622 # Clear all keys from hash, or all elements from array.
623 sub CLEAR {
624     my $self = shift->_get_self;
625     warn "CLEAR($self)\n" if DEBUG;
626
627     unless ( $self->_engine->storage->is_writable ) {
628         $self->_throw_error( 'Cannot write to a readonly filehandle' );
629     }
630
631     $self->lock_exclusive;
632
633     #XXX Rewrite this dreck to do it in the engine as a tight loop vs.
634     # iterating over keys - such a WASTE - is this required for transactional
635     # clearning?! Surely that can be detected in the engine ...
636     if ( $self->_type eq TYPE_HASH ) {
637         my $key = $self->first_key;
638         while ( $key ) {
639             # Retrieve the key before deleting because we depend on next_key
640             my $next_key = $self->next_key( $key );
641             $self->_engine->delete_key( $self, $key, $key );
642             $key = $next_key;
643         }
644     }
645     else {
646         my $size = $self->FETCHSIZE;
647         for my $key ( 0 .. $size - 1 ) {
648             $self->_engine->delete_key( $self, $key, $key );
649         }
650         $self->STORESIZE( 0 );
651     }
652
653     $self->unlock;
654
655     return 1;
656 }
657
658 # Public method aliases
659 sub put    { (shift)->STORE( @_ )  }
660 sub get    { (shift)->FETCH( @_ )  }
661 sub store  { (shift)->STORE( @_ )  }
662 sub fetch  { (shift)->FETCH( @_ )  }
663 sub delete { (shift)->DELETE( @_ ) }
664 sub exists { (shift)->EXISTS( @_ ) }
665 sub clear  { (shift)->CLEAR( @_ )  }
666
667 sub _dump_file {shift->_get_self->_engine->_dump_file;}
668
669 1;
670 __END__