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