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