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