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