X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=blobdiff_plain;f=lib%2FDBM%2FDeep.pm;h=94c296faf73cdfe7ec15b9afc79161aa9f3f364b;hb=a4d36ff61c367864cdf95523dd9771b01773930c;hp=d102d36ca4d18bd32497772c0aa658753fd0d289;hpb=5c0756fcb3b5c7ca76c52be6c7c9d78841e5d57b;p=dbsrgits%2FDBM-Deep.git diff --git a/lib/DBM/Deep.pm b/lib/DBM/Deep.pm index d102d36..94c296f 100644 --- a/lib/DBM/Deep.pm +++ b/lib/DBM/Deep.pm @@ -5,13 +5,16 @@ use 5.006_000; use strict; use warnings FATAL => 'all'; -our $VERSION = q(1.0014); +our $VERSION = q(1.0015); -use Data::Dumper (); use Scalar::Util (); -use DBM::Deep::Engine; -use DBM::Deep::File; +use DBM::Deep::Engine::DBI (); +use DBM::Deep::Engine::File (); + +use DBM::Deep::SQL::Util; +use DBM::Deep::SQL::Array; +use DBM::Deep::SQL::Hash; use overload '""' => sub { overload::StrVal( $_[0] ) }, @@ -19,9 +22,6 @@ use overload use constant DEBUG => 0; -## -# Setup constants for users to pass to new() -## sub TYPE_HASH () { DBM::Deep::Engine->SIG_HASH } sub TYPE_ARRAY () { DBM::Deep::Engine->SIG_ARRAY } @@ -49,19 +49,81 @@ sub _get_args { return $args; } +# Class constructor method for Perl OO interface. +# Calls tie() and returns blessed reference to tied hash or array, +# providing a hybrid OO/tie interface. sub new { - ## - # Class constructor method for Perl OO interface. - # Calls tie() and returns blessed reference to tied hash or array, - # providing a hybrid OO/tie interface. - ## my $class = shift; my $args = $class->_get_args( @_ ); + my $self; + +=pod + if (exists $args->{dbi}) { + eval { + require DBIx::Abstract; + }; if ( $@ ) { + __PACKAGE__->_throw_error('DBIx::Abstract not installed. You cannot use the SQL mode.'); + } + unless (UNIVERSAL::isa($args->{dbi}, 'DBIx::Abstract')) { + $args->{dbi} = DBIx::Abstract->connect($args->{dbi}); + } + + if (defined $args->{id}) { + unless ($args->{id} =~ /^\d+$/ && $args->{id} > 0) { + __PACKAGE__->_throw_error('Invalid SQL record id'); + } + my $util = {dbi => $args->{dbi}}; + bless $util, 'DBM::Deep::SQL::Util'; + my $q = $util->_select( + table => 'rec_item', + fields => 'item_type', + where => {id => $args->{id}}, + ); + if ($q->[0]->[0] eq 'array') { + $args->{type} = TYPE_ARRAY; + } + elsif ($q->[0]->[0] eq 'hash') { + $args->{type} = TYPE_HASH; + } + else { + DBM::Deep->_throw_error('Unknown SQL record id'); + } + } + else { + my $util = {dbi => $args->{dbi}}; + bless $util, 'DBM::Deep::SQL::Util'; + if (defined($args->{type}) && $args->{type} eq TYPE_ARRAY) { + $args->{id} = $util->_create('array'); + } + else { + $args->{id} = $util->_create('hash'); + } + } + + if (defined($args->{type}) && $args->{type} eq TYPE_ARRAY) { + $class = 'DBM::Deep::SQL::Array'; + require DBM::Deep::SQL::Array; + tie @$self, $class, %$args; + if ($args->{prefetch}) { + (tied(@$self))->_prefetch(); + } + return bless $self, $class; + } + else { + $class = 'DBM::Deep::SQL::Hash'; + require DBM::Deep::SQL::Hash; + tie %$self, $class, %$args; + if ($args->{prefetch}) { + (tied(%$self))->_prefetch(); + } + return bless $self, $class; + } + } +=cut ## # Check if we want a tied hash or array. ## - my $self; if (defined($args->{type}) && $args->{type} eq TYPE_ARRAY) { $class = 'DBM::Deep::Array'; require DBM::Deep::Array; @@ -82,9 +144,6 @@ sub _init { my $class = shift; my ($args) = @_; - $args->{storage} = DBM::Deep::File->new( $args ) - unless exists $args->{storage}; - # locking implicitly enables autoflush if ($args->{locking}) { $args->{autoflush} = 1; } @@ -93,13 +152,19 @@ sub _init { type => TYPE_HASH, base_offset => undef, staleness => undef, - - storage => undef, engine => undef, }, $class; - $args->{engine} = DBM::Deep::Engine->new( { %{$args}, obj => $self } ) - unless exists $args->{engine}; + unless ( exists $args->{engine} ) { + my $class = exists $args->{dbi} + ? 'DBM::Deep::Engine::DBI' + : 'DBM::Deep::Engine::File'; + + $args->{engine} = $class->new({ + %{$args}, + obj => $self, + }); + } # Grab the parameters we want to use foreach my $param ( keys %$self ) { @@ -108,16 +173,15 @@ sub _init { } eval { - local $SIG{'__DIE__'}; + local $SIG{'__DIE__'}; - $self->lock_exclusive; - $self->_engine->setup_fh( $self ); - $self->_storage->set_inode; - $self->unlock; + $self->lock_exclusive; + $self->_engine->setup( $self ); + $self->unlock; }; if ( $@ ) { - my $e = $@; - eval { local $SIG{'__DIE__'}; $self->unlock; }; - die $e; + my $e = $@; + eval { local $SIG{'__DIE__'}; $self->unlock; }; + die $e; } return $self; @@ -137,17 +201,17 @@ sub TIEARRAY { sub lock_exclusive { my $self = shift->_get_self; - return $self->_storage->lock_exclusive( $self ); + return $self->_engine->lock_exclusive( $self, @_ ); } *lock = \&lock_exclusive; sub lock_shared { my $self = shift->_get_self; - return $self->_storage->lock_shared( $self ); + return $self->_engine->lock_shared( $self, @_ ); } sub unlock { my $self = shift->_get_self; - return $self->_storage->unlock( $self ); + return $self->_engine->unlock( $self, @_ ); } sub _copy_value { @@ -158,18 +222,19 @@ sub _copy_value { ${$spot} = $value; } else { - # This assumes hash or array only. This is a bad assumption moving forward. - # -RobK, 2008-05-27 my $r = Scalar::Util::reftype( $value ); my $tied; if ( $r eq 'ARRAY' ) { $tied = tied(@$value); } - else { + elsif ( $r eq 'HASH' ) { $tied = tied(%$value); } + else { + __PACKAGE__->_throw_error( "Unknown type for '$value'" ); + } - if ( eval { local $SIG{__DIE__}; $tied->isa( 'DBM::Deep' ) } ) { + if ( eval { local $SIG{__DIE__}; $tied->isa( __PACKAGE__ ) } ) { ${$spot} = $tied->_repr; $tied->_copy_node( ${$spot} ); } @@ -183,7 +248,7 @@ sub _copy_value { } my $c = Scalar::Util::blessed( $value ); - if ( defined $c && !$c->isa( 'DBM::Deep') ) { + if ( defined $c && !$c->isa( __PACKAGE__ ) ) { ${$spot} = bless ${$spot}, $c } } @@ -200,9 +265,6 @@ sub _copy_value { #} sub export { - ## - # Recursively export into standard Perl hashes and arrays. - ## my $self = shift->_get_self; my $temp = $self->_repr; @@ -229,25 +291,24 @@ sub _check_legality { return $r if 'HASH' eq $r; return $r if 'ARRAY' eq $r; - DBM::Deep->_throw_error( + __PACKAGE__->_throw_error( "Storage of references of type '$r' is not supported." ); } sub import { - # Perl calls import() on use -- ignore - return if !ref $_[0]; + return if !ref $_[0]; # Perl calls import() on use -- ignore my $self = shift->_get_self; my ($struct) = @_; my $type = $self->_check_legality( $struct ); if ( !$type ) { - DBM::Deep->_throw_error( "Cannot import a scalar" ); + __PACKAGE__->_throw_error( "Cannot import a scalar" ); } if ( substr( $type, 0, 1 ) ne $self->_type ) { - DBM::Deep->_throw_error( + __PACKAGE__->_throw_error( "Cannot import " . ('HASH' eq $type ? 'a hash' : 'an array') . " into " . ('HASH' eq $type ? 'an array' : 'a hash') ); @@ -303,23 +364,25 @@ sub import { #XXX Need to keep track of who has a fh to this file in order to #XXX close them all prior to optimize on Win32/cygwin +# Rebuild entire database into new file, then move +# it back on top of original. sub optimize { - ## - # Rebuild entire database into new file, then move - # it back on top of original. - ## my $self = shift->_get_self; + # Optimizing is only something we need to do when we're working with our + # own file format. Otherwise, let the other guy do the optimizations. + return unless $self->_engine->isa( 'DBM::Deep::Engine::File' ); + #XXX Need to create a new test for this -# if ($self->_storage->{links} > 1) { +# if ($self->_engine->storage->{links} > 1) { # $self->_throw_error("Cannot optimize: reference count is greater than 1"); # } #XXX Do we have to lock the tempfile? #XXX Should we use tempfile() here instead of a hard-coded name? - my $temp_filename = $self->_storage->{file} . '.tmp'; - my $db_temp = DBM::Deep->new( + my $temp_filename = $self->_engine->storage->{file} . '.tmp'; + my $db_temp = __PACKAGE__->new( file => $temp_filename, type => $self->_type, @@ -332,13 +395,13 @@ sub optimize { $self->lock_exclusive; $self->_engine->clear_cache; $self->_copy_node( $db_temp ); - $db_temp->_storage->close; + $db_temp->_engine->storage->close; undef $db_temp; ## # Attempt to copy user, group and permissions over to new file ## - $self->_storage->copy_stats( $temp_filename ); + $self->_engine->storage->copy_stats( $temp_filename ); # q.v. perlport for more information on this variable if ( $^O eq 'MSWin32' || $^O eq 'cygwin' ) { @@ -349,21 +412,21 @@ sub optimize { # with a soft copy. ## $self->unlock; - $self->_storage->close; + $self->_engine->storage->close; } - if (!rename $temp_filename, $self->_storage->{file}) { + if (!rename $temp_filename, $self->_engine->storage->{file}) { unlink $temp_filename; $self->unlock; $self->_throw_error("Optimize failed: Cannot copy temp file over original: $!"); } $self->unlock; - $self->_storage->close; + $self->_engine->storage->close; - $self->_storage->open; + $self->_engine->storage->open; $self->lock_exclusive; - $self->_engine->setup_fh( $self ); + $self->_engine->setup( $self ); $self->unlock; return 1; @@ -375,11 +438,10 @@ sub clone { ## my $self = shift->_get_self; - return DBM::Deep->new( + return __PACKAGE__->new( type => $self->_type, base_offset => $self->_base_offset, staleness => $self->_staleness, - storage => $self->_storage, engine => $self->_engine, ); } @@ -400,7 +462,7 @@ sub clone { my $func = shift; if ( $is_legal_filter{$type} ) { - $self->_storage->{"filter_$type"} = $func; + $self->_engine->storage->{"filter_$type"} = $func; return 1; } @@ -415,33 +477,40 @@ sub clone { sub begin_work { my $self = shift->_get_self; - return $self->_engine->begin_work( $self, @_ ); + $self->lock_exclusive; + my $rv = eval { $self->_engine->begin_work( $self, @_ ) }; + my $e = $@; + $self->unlock; + die $e if $e; + return $rv; } sub rollback { my $self = shift->_get_self; - return $self->_engine->rollback( $self, @_ ); + $self->lock_exclusive; + my $rv = eval { $self->_engine->rollback( $self, @_ ) }; + my $e = $@; + $self->unlock; + die $e if $e; + return $rv; } sub commit { my $self = shift->_get_self; - return $self->_engine->commit( $self, @_ ); + $self->lock_exclusive; + my $rv = eval { $self->_engine->commit( $self, @_ ) }; + my $e = $@; + $self->unlock; + die $e if $e; + return $rv; } -## # Accessor methods -## - sub _engine { my $self = $_[0]->_get_self; return $self->{engine}; } -sub _storage { - my $self = $_[0]->_get_self; - return $self->{storage}; -} - sub _type { my $self = $_[0]->_get_self; return $self->{type}; @@ -457,10 +526,7 @@ sub _staleness { return $self->{staleness}; } -## # Utility methods -## - sub _throw_error { my $n = 0; while( 1 ) { @@ -471,15 +537,13 @@ sub _throw_error { } } +# Store single hash key/value or array element in database. sub STORE { - ## - # Store single hash key/value or array element in database. - ## my $self = shift->_get_self; my ($key, $value) = @_; - warn "STORE($self, $key, $value)\n" if DEBUG; + warn "STORE($self, '$key', '@{[defined$value?$value:'undef']}')\n" if DEBUG; - unless ( $self->_storage->is_writable ) { + unless ( $self->_engine->storage->is_writable ) { $self->_throw_error( 'Cannot write to a readonly filehandle' ); } @@ -487,47 +551,43 @@ sub STORE { # User may be storing a complex value, in which case we do not want it run # through the filtering system. - if ( !ref($value) && $self->_storage->{filter_store_value} ) { - $value = $self->_storage->{filter_store_value}->( $value ); + if ( !ref($value) && $self->_engine->storage->{filter_store_value} ) { + $value = $self->_engine->storage->{filter_store_value}->( $value ); } - $self->_engine->write_value( $self, $key, $value); + $self->_engine->write_value( $self, $key, $value ); $self->unlock; return 1; } +# Fetch single value or element given plain key or array index sub FETCH { - ## - # Fetch single value or element given plain key or array index - ## my $self = shift->_get_self; my ($key) = @_; - warn "FETCH($self,$key)\n" if DEBUG; + warn "FETCH($self, '$key')\n" if DEBUG; $self->lock_shared; - my $result = $self->_engine->read_value( $self, $key); + my $result = $self->_engine->read_value( $self, $key ); $self->unlock; # Filters only apply to scalar values, so the ref check is making # sure the fetched bucket is a scalar, not a child hash or array. - return ($result && !ref($result) && $self->_storage->{filter_fetch_value}) - ? $self->_storage->{filter_fetch_value}->($result) + return ($result && !ref($result) && $self->_engine->storage->{filter_fetch_value}) + ? $self->_engine->storage->{filter_fetch_value}->($result) : $result; } +# Delete single key/value pair or element given plain key or array index sub DELETE { - ## - # Delete single key/value pair or element given plain key or array index - ## my $self = shift->_get_self; my ($key) = @_; - warn "DELETE($self,$key)\n" if DEBUG; + warn "DELETE($self, '$key')\n" if DEBUG; - unless ( $self->_storage->is_writable ) { + unless ( $self->_engine->storage->is_writable ) { $self->_throw_error( 'Cannot write to a readonly filehandle' ); } @@ -538,8 +598,8 @@ sub DELETE { ## my $value = $self->_engine->delete_key( $self, $key); - if (defined $value && !ref($value) && $self->_storage->{filter_fetch_value}) { - $value = $self->_storage->{filter_fetch_value}->($value); + if (defined $value && !ref($value) && $self->_engine->storage->{filter_fetch_value}) { + $value = $self->_engine->storage->{filter_fetch_value}->($value); } $self->unlock; @@ -547,13 +607,11 @@ sub DELETE { return $value; } +# Check if a single key or element exists given plain key or array index sub EXISTS { - ## - # Check if a single key or element exists given plain key or array index - ## my $self = shift->_get_self; my ($key) = @_; - warn "EXISTS($self,$key)\n" if DEBUG; + warn "EXISTS($self, '$key')\n" if DEBUG; $self->lock_shared; @@ -564,14 +622,12 @@ sub EXISTS { return $result; } +# Clear all keys from hash, or all elements from array. sub CLEAR { - ## - # Clear all keys from hash, or all elements from array. - ## my $self = shift->_get_self; warn "CLEAR($self)\n" if DEBUG; - unless ( $self->_storage->is_writable ) { + unless ( $self->_engine->storage->is_writable ) { $self->_throw_error( 'Cannot write to a readonly filehandle' ); } @@ -602,16 +658,14 @@ sub CLEAR { return 1; } -## # Public method aliases -## -sub put { (shift)->STORE( @_ ) } -sub store { (shift)->STORE( @_ ) } -sub get { (shift)->FETCH( @_ ) } -sub fetch { (shift)->FETCH( @_ ) } +sub put { (shift)->STORE( @_ ) } +sub get { (shift)->FETCH( @_ ) } +sub store { (shift)->STORE( @_ ) } +sub fetch { (shift)->FETCH( @_ ) } sub delete { (shift)->DELETE( @_ ) } sub exists { (shift)->EXISTS( @_ ) } -sub clear { (shift)->CLEAR( @_ ) } +sub clear { (shift)->CLEAR( @_ ) } sub _dump_file {shift->_get_self->_engine->_dump_file;}