r8204@rob-kinyons-computer-2 (orig r10021): rkinyon | 2007-09-28 20:00:36 -0400
[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;
7
8 our $VERSION = q(1.0005);
9
10 use Fcntl qw( :flock );
11
12 use Clone ();
13 use Digest::MD5 ();
14 use FileHandle::Fmode ();
15 use Scalar::Util ();
16
17 use DBM::Deep::Engine;
18 use DBM::Deep::File;
19
20 use overload
21     '""' => sub { overload::StrVal( $_[0] ) },
22     fallback => 1;
23
24 ##
25 # Setup constants for users to pass to new()
26 ##
27 sub TYPE_HASH   () { DBM::Deep::Engine->SIG_HASH  }
28 sub TYPE_ARRAY  () { DBM::Deep::Engine->SIG_ARRAY }
29
30 # This is used in all the children of this class in their TIE<type> methods.
31 sub _get_args {
32     my $proto = shift;
33
34     my $args;
35     if (scalar(@_) > 1) {
36         if ( @_ % 2 ) {
37             $proto->_throw_error( "Odd number of parameters to " . (caller(1))[2] );
38         }
39         $args = {@_};
40     }
41     elsif ( ref $_[0] ) {
42         unless ( eval { local $SIG{'__DIE__'}; %{$_[0]} || 1 } ) {
43             $proto->_throw_error( "Not a hashref in args to " . (caller(1))[2] );
44         }
45         $args = $_[0];
46     }
47     else {
48         $args = { file => shift };
49     }
50
51     return $args;
52 }
53
54 sub new {
55     ##
56     # Class constructor method for Perl OO interface.
57     # Calls tie() and returns blessed reference to tied hash or array,
58     # providing a hybrid OO/tie interface.
59     ##
60     my $class = shift;
61     my $args = $class->_get_args( @_ );
62
63     ##
64     # Check if we want a tied hash or array.
65     ##
66     my $self;
67     if (defined($args->{type}) && $args->{type} eq TYPE_ARRAY) {
68         $class = 'DBM::Deep::Array';
69         require DBM::Deep::Array;
70         tie @$self, $class, %$args;
71     }
72     else {
73         $class = 'DBM::Deep::Hash';
74         require DBM::Deep::Hash;
75         tie %$self, $class, %$args;
76     }
77
78     return bless $self, $class;
79 }
80
81 # This initializer is called from the various TIE* methods. new() calls tie(),
82 # which allows for a single point of entry.
83 sub _init {
84     my $class = shift;
85     my ($args) = @_;
86
87     $args->{storage} = DBM::Deep::File->new( $args )
88         unless exists $args->{storage};
89
90     # locking implicitly enables autoflush
91     if ($args->{locking}) { $args->{autoflush} = 1; }
92
93     # These are the defaults to be optionally overridden below
94     my $self = bless {
95         type        => TYPE_HASH,
96         base_offset => undef,
97         staleness   => undef,
98
99         storage     => undef,
100         engine      => undef,
101     }, $class;
102
103     $args->{engine} = DBM::Deep::Engine->new( { %{$args}, obj => $self } )
104         unless exists $args->{engine};
105
106     # Grab the parameters we want to use
107     foreach my $param ( keys %$self ) {
108         next unless exists $args->{$param};
109         $self->{$param} = $args->{$param};
110     }
111
112     eval {
113       local $SIG{'__DIE__'};
114
115       $self->lock;
116       $self->_engine->setup_fh( $self );
117       $self->_storage->set_inode;
118       $self->unlock;
119     }; if ( $@ ) {
120       my $e = $@;
121       eval { local $SIG{'__DIE__'}; $self->unlock; };
122       die $e;
123     }
124
125     return $self;
126 }
127
128 sub TIEHASH {
129     shift;
130     require DBM::Deep::Hash;
131     return DBM::Deep::Hash->TIEHASH( @_ );
132 }
133
134 sub TIEARRAY {
135     shift;
136     require DBM::Deep::Array;
137     return DBM::Deep::Array->TIEARRAY( @_ );
138 }
139
140 sub lock {
141     my $self = shift->_get_self;
142     return $self->_storage->lock( $self, @_ );
143 }
144
145 sub unlock {
146     my $self = shift->_get_self;
147     return $self->_storage->unlock( $self, @_ );
148 }
149
150 sub _copy_value {
151     my $self = shift->_get_self;
152     my ($spot, $value) = @_;
153
154     if ( !ref $value ) {
155         ${$spot} = $value;
156     }
157     elsif ( eval { local $SIG{__DIE__}; $value->isa( 'DBM::Deep' ) } ) {
158         ${$spot} = $value->_repr;
159         $value->_copy_node( ${$spot} );
160     }
161     else {
162         my $r = Scalar::Util::reftype( $value );
163         my $c = Scalar::Util::blessed( $value );
164         if ( $r eq 'ARRAY' ) {
165             ${$spot} = [ @{$value} ];
166         }
167         else {
168             ${$spot} = { %{$value} };
169         }
170         ${$spot} = bless ${$spot}, $c
171             if defined $c;
172     }
173
174     return 1;
175 }
176
177 #sub _copy_node {
178 #    die "Must be implemented in a child class\n";
179 #}
180 #
181 #sub _repr {
182 #    die "Must be implemented in a child class\n";
183 #}
184
185 sub export {
186     ##
187     # Recursively export into standard Perl hashes and arrays.
188     ##
189     my $self = shift->_get_self;
190
191     my $temp = $self->_repr;
192
193     $self->lock();
194     $self->_copy_node( $temp );
195     $self->unlock();
196
197     my $classname = $self->_engine->get_classname( $self );
198     if ( defined $classname ) {
199       bless $temp, $classname;
200     }
201
202     return $temp;
203 }
204
205 sub import {
206     ##
207     # Recursively import Perl hash/array structure
208     ##
209     if (!ref($_[0])) { return; } # Perl calls import() on use -- ignore
210
211     my $self = shift->_get_self;
212     my ($struct) = @_;
213
214     # struct is not a reference, so just import based on our type
215     if (!ref($struct)) {
216         $struct = $self->_repr( @_ );
217     }
218
219     #XXX This isn't the best solution. Better would be to use Data::Walker,
220     #XXX but that's a lot more thinking than I want to do right now.
221     eval {
222         local $SIG{'__DIE__'};
223         $self->_import( Clone::clone( $struct ) );
224     }; if ( my $e = $@ ) {
225         die $e;
226     }
227
228     return 1;
229 }
230
231 #XXX Need to keep track of who has a fh to this file in order to
232 #XXX close them all prior to optimize on Win32/cygwin
233 sub optimize {
234     ##
235     # Rebuild entire database into new file, then move
236     # it back on top of original.
237     ##
238     my $self = shift->_get_self;
239
240 #XXX Need to create a new test for this
241 #    if ($self->_storage->{links} > 1) {
242 #        $self->_throw_error("Cannot optimize: reference count is greater than 1");
243 #    }
244
245     #XXX Do we have to lock the tempfile?
246
247     my $db_temp = DBM::Deep->new(
248         file => $self->_storage->{file} . '.tmp',
249         type => $self->_type,
250
251         # Bring over all the parameters that we need to bring over
252         ( map { $_ => $self->_engine->$_ } qw(
253             byte_size max_buckets data_sector_size num_txns
254         )),
255     );
256
257     $self->lock();
258     $self->_engine->clear_cache;
259     $self->_copy_node( $db_temp );
260     undef $db_temp;
261
262     ##
263     # Attempt to copy user, group and permissions over to new file
264     ##
265     my @stats = stat($self->_fh);
266     my $perms = $stats[2] & 07777;
267     my $uid = $stats[4];
268     my $gid = $stats[5];
269     chown( $uid, $gid, $self->_storage->{file} . '.tmp' );
270     chmod( $perms, $self->_storage->{file} . '.tmp' );
271
272     # q.v. perlport for more information on this variable
273     if ( $^O eq 'MSWin32' || $^O eq 'cygwin' ) {
274         ##
275         # Potential race condition when optmizing on Win32 with locking.
276         # The Windows filesystem requires that the filehandle be closed
277         # before it is overwritten with rename().  This could be redone
278         # with a soft copy.
279         ##
280         $self->unlock();
281         $self->_storage->close;
282     }
283
284     if (!rename $self->_storage->{file} . '.tmp', $self->_storage->{file}) {
285         unlink $self->_storage->{file} . '.tmp';
286         $self->unlock();
287         $self->_throw_error("Optimize failed: Cannot copy temp file over original: $!");
288     }
289
290     $self->unlock();
291     $self->_storage->close;
292
293     $self->_storage->open;
294     $self->lock();
295     $self->_engine->setup_fh( $self );
296     $self->unlock();
297
298     return 1;
299 }
300
301 sub clone {
302     ##
303     # Make copy of object and return
304     ##
305     my $self = shift->_get_self;
306
307     return DBM::Deep->new(
308         type        => $self->_type,
309         base_offset => $self->_base_offset,
310         staleness   => $self->_staleness,
311         storage     => $self->_storage,
312         engine      => $self->_engine,
313     );
314 }
315
316 #XXX Migrate this to the engine, where it really belongs and go through some
317 # API - stop poking in the innards of someone else..
318 {
319     my %is_legal_filter = map {
320         $_ => ~~1,
321     } qw(
322         store_key store_value
323         fetch_key fetch_value
324     );
325
326     sub set_filter {
327         my $self = shift->_get_self;
328         my $type = lc shift;
329         my $func = shift;
330
331         if ( $is_legal_filter{$type} ) {
332             $self->_storage->{"filter_$type"} = $func;
333             return 1;
334         }
335
336         return;
337     }
338
339     sub filter_store_key   { $_[0]->set_filter( store_key   => $_[1] ); }
340     sub filter_store_value { $_[0]->set_filter( store_value => $_[1] ); }
341     sub filter_fetch_key   { $_[0]->set_filter( fetch_key   => $_[1] ); }
342     sub filter_fetch_value { $_[0]->set_filter( fetch_value => $_[1] ); }
343 }
344
345 sub begin_work {
346     my $self = shift->_get_self;
347     return $self->_engine->begin_work( $self, @_ );
348 }
349
350 sub rollback {
351     my $self = shift->_get_self;
352     return $self->_engine->rollback( $self, @_ );
353 }
354
355 sub commit {
356     my $self = shift->_get_self;
357     return $self->_engine->commit( $self, @_ );
358 }
359
360 ##
361 # Accessor methods
362 ##
363
364 sub _engine {
365     my $self = $_[0]->_get_self;
366     return $self->{engine};
367 }
368
369 sub _storage {
370     my $self = $_[0]->_get_self;
371     return $self->{storage};
372 }
373
374 sub _type {
375     my $self = $_[0]->_get_self;
376     return $self->{type};
377 }
378
379 sub _base_offset {
380     my $self = $_[0]->_get_self;
381     return $self->{base_offset};
382 }
383
384 sub _staleness {
385     my $self = $_[0]->_get_self;
386     return $self->{staleness};
387 }
388
389 sub _fh {
390     my $self = $_[0]->_get_self;
391     return $self->_storage->{fh};
392 }
393
394 ##
395 # Utility methods
396 ##
397
398 sub _throw_error {
399     my $n = 0;
400     while( 1 ) {
401         my @caller = caller( ++$n );
402         next if $caller[0] =~ m/^DBM::Deep/;
403
404         die "DBM::Deep: $_[1] at $0 line $caller[2]\n";
405     }
406 }
407
408 sub STORE {
409     ##
410     # Store single hash key/value or array element in database.
411     ##
412     my $self = shift->_get_self;
413     my ($key, $value) = @_;
414
415     if ( !FileHandle::Fmode::is_W( $self->_fh ) ) {
416         $self->_throw_error( 'Cannot write to a readonly filehandle' );
417     }
418
419     ##
420     # Request exclusive lock for writing
421     ##
422     $self->lock( LOCK_EX );
423
424     # User may be storing a complex value, in which case we do not want it run
425     # through the filtering system.
426     if ( !ref($value) && $self->_storage->{filter_store_value} ) {
427         $value = $self->_storage->{filter_store_value}->( $value );
428     }
429
430     $self->_engine->write_value( $self, $key, $value);
431
432     $self->unlock();
433
434     return 1;
435 }
436
437 sub FETCH {
438     ##
439     # Fetch single value or element given plain key or array index
440     ##
441     my $self = shift->_get_self;
442     my ($key) = @_;
443
444     ##
445     # Request shared lock for reading
446     ##
447     $self->lock( LOCK_SH );
448
449     my $result = $self->_engine->read_value( $self, $key);
450
451     $self->unlock();
452
453     # Filters only apply to scalar values, so the ref check is making
454     # sure the fetched bucket is a scalar, not a child hash or array.
455     return ($result && !ref($result) && $self->_storage->{filter_fetch_value})
456         ? $self->_storage->{filter_fetch_value}->($result)
457         : $result;
458 }
459
460 sub DELETE {
461     ##
462     # Delete single key/value pair or element given plain key or array index
463     ##
464     my $self = shift->_get_self;
465     my ($key) = @_;
466
467     if ( !FileHandle::Fmode::is_W( $self->_fh ) ) {
468         $self->_throw_error( 'Cannot write to a readonly filehandle' );
469     }
470
471     ##
472     # Request exclusive lock for writing
473     ##
474     $self->lock( LOCK_EX );
475
476     ##
477     # Delete bucket
478     ##
479     my $value = $self->_engine->delete_key( $self, $key);
480
481     if (defined $value && !ref($value) && $self->_storage->{filter_fetch_value}) {
482         $value = $self->_storage->{filter_fetch_value}->($value);
483     }
484
485     $self->unlock();
486
487     return $value;
488 }
489
490 sub EXISTS {
491     ##
492     # Check if a single key or element exists given plain key or array index
493     ##
494     my $self = shift->_get_self;
495     my ($key) = @_;
496
497     ##
498     # Request shared lock for reading
499     ##
500     $self->lock( LOCK_SH );
501
502     my $result = $self->_engine->key_exists( $self, $key );
503
504     $self->unlock();
505
506     return $result;
507 }
508
509 sub CLEAR {
510     ##
511     # Clear all keys from hash, or all elements from array.
512     ##
513     my $self = shift->_get_self;
514
515     if ( !FileHandle::Fmode::is_W( $self->_fh ) ) {
516         $self->_throw_error( 'Cannot write to a readonly filehandle' );
517     }
518
519     ##
520     # Request exclusive lock for writing
521     ##
522     $self->lock( LOCK_EX );
523
524     #XXX Rewrite this dreck to do it in the engine as a tight loop vs.
525     # iterating over keys - such a WASTE - is this required for transactional
526     # clearning?! Surely that can be detected in the engine ...
527     if ( $self->_type eq TYPE_HASH ) {
528         my $key = $self->first_key;
529         while ( $key ) {
530             # Retrieve the key before deleting because we depend on next_key
531             my $next_key = $self->next_key( $key );
532             $self->_engine->delete_key( $self, $key, $key );
533             $key = $next_key;
534         }
535     }
536     else {
537         my $size = $self->FETCHSIZE;
538         for my $key ( 0 .. $size - 1 ) {
539             $self->_engine->delete_key( $self, $key, $key );
540         }
541         $self->STORESIZE( 0 );
542     }
543
544     $self->unlock();
545
546     return 1;
547 }
548
549 ##
550 # Public method aliases
551 ##
552 sub put { (shift)->STORE( @_ ) }
553 sub store { (shift)->STORE( @_ ) }
554 sub get { (shift)->FETCH( @_ ) }
555 sub fetch { (shift)->FETCH( @_ ) }
556 sub delete { (shift)->DELETE( @_ ) }
557 sub exists { (shift)->EXISTS( @_ ) }
558 sub clear { (shift)->CLEAR( @_ ) }
559
560 sub _dump_file {shift->_get_self->_engine->_dump_file;}
561
562 1;
563 __END__