package forks::BerkeleyDB::shared::array; $VERSION = 0.054; use strict; use warnings; use BerkeleyDB 0.27; use forks::BerkeleyDB::ElemNotExists; use vars qw(@ISA); @ISA = qw(BerkeleyDB::Recno); #--------------------------------------------------------------------------- sub new { my $type = shift; my $class = ref($type) || $type; my $self = $class->SUPER::new(@_); return undef unless defined $self; return bless($self, $class); } # standard Perl feature methods implemented: # TIEARRAY # FETCH, STORE # FETCHSIZE, STORESIZE # EXTEND # EXISTS, DELETE # CLEAR # PUSH, POP # SHIFT, UNSHIFT # SPLICE # UNTIE, DESTROY #--------------------------------------------------------------------------- *TIEARRAY = *TIEARRAY = \&new; sub _exists_elem ($) { my $value = shift; return defined $value && UNIVERSAL::isa($value, 'forks::BerkeleyDB::ElemNotExists') ? 0 : 1; } sub _db_filter_array_elem_not_exists_to_undef ($) { my $value = shift; return defined $value && UNIVERSAL::isa($value, 'forks::BerkeleyDB::ElemNotExists') ? undef : $value; } #--------------------------------------------------------------------------- sub FETCH { my $value = undef; $_[0]->db_get($_[1], $value); return defined $value && UNIVERSAL::isa($value, 'forks::BerkeleyDB::ElemNotExists') ? undef : $value; #_db_filter_array_elem_not_exists_to_undef } sub STORE { if (defined $_[2]) { return undef unless $_[0]->db_put($_[1], $_[2]) == 0; } else { no warnings 'uninitialized'; return undef unless $_[0]->db_put($_[1], $_[2]) == 0; } return $_[2]; } #--------------------------------------------------------------------------- #sub FETCHSIZE {} #use BerkeleyDB.pm method sub STORESIZE { my $self = shift; my $count = shift; my $nkeys = $self->FETCHSIZE(); #warn "STORESIZE: count=$count; nkeys=$nkeys"; if ($nkeys < $count) { #add undef elements my $value = forks::BerkeleyDB::ElemNotExists->instance(); $self->db_put($_, $value, DB_APPEND) for ($nkeys..($count - 1)); } elsif ($nkeys > $count) { #trim elements my $value = undef; my $cursor = $self->db_cursor(DB_WRITECURSOR); for (($count - 1)..($nkeys - 1)) { return $self->FETCHSIZE() unless $cursor->c_get($_, $value, DB_LAST) == 0; #optimized: using DB_LAST prevents database renumbering return $self->FETCHSIZE() unless $cursor->c_del() == 0; } } return $self->FETCHSIZE(); } #--------------------------------------------------------------------------- sub EXTEND { return $_[1]; #no need for pre-allocation } #--------------------------------------------------------------------------- sub EXISTS { #test that this works after delete my $self = shift; my $key = shift; my $value = undef; return 0 unless $self->db_get($key, $value) == 0; return _exists_elem($value) ? 1 : 0; } sub DELETE { #doesn't appear to support deleting entire array (delete @a[0..$#a] == DB truncate)? my $self = shift; return undef unless @_; my $key = shift; my $value = undef; #warn "DELETE: key=$key"; my $cursor = $self->db_cursor(DB_WRITECURSOR); return undef unless $cursor->c_get($key, $value, DB_SET) == 0; #set cursor position if ($key == $self->FETCHSIZE() - 1) { #if this is last key, delete element return undef unless $cursor->c_del() == 0; } else { #initialize element to "not exists" state my $new_value = forks::BerkeleyDB::ElemNotExists->instance(); #warn "DELETE: success!"; return undef unless $cursor->c_put($key, $new_value, DB_CURRENT) == 0; } ### delete any other "not exists" elements, starting from last element ### my ($cur_key, $cur_value) = (0, ''); while ($cursor->c_get($key, $value, DB_LAST) == 0) { if (_exists_elem($value)) { last; } else { return undef unless $cursor->c_del() == 0; } } $cursor->c_close(); #warn "DELETE: success!"; return _db_filter_array_elem_not_exists_to_undef($value); } #--------------------------------------------------------------------------- sub CLEAR { my $self = shift; my $count = 0; $self->truncate($count); return defined $count && $count > 0 ? 1 : 0; } #--------------------------------------------------------------------------- sub PUSH { my $self = shift; my $key = 0; foreach (@_) { return $self->FETCHSIZE() unless $self->db_put($key, $_, DB_APPEND) == 0; } return $self->FETCHSIZE(); } sub POP { my $self = shift; my $value = $self->SUPER::POP(@_); return defined $value && UNIVERSAL::isa($value, 'forks::BerkeleyDB::ElemNotExists') ? undef : $value; #_db_filter_array_elem_not_exists_to_undef } #--------------------------------------------------------------------------- sub SHIFT { my $self = shift; my $value = $self->SUPER::SHIFT(@_); return defined $value && UNIVERSAL::isa($value, 'forks::BerkeleyDB::ElemNotExists') ? undef : $value; #_db_filter_array_elem_not_exists_to_undef } sub UNSHIFT { my $self = shift; return undef unless @_; $self->SUPER::UNSHIFT(@_); return $self->FETCHSIZE(); } #--------------------------------------------------------------------------- sub SPLICE { my $self = shift; my $offset = shift || 0; my $length = shift; my $nkeys = $self->FETCHSIZE(); my $p_offset = $offset < 0 ? $nkeys + $offset : $offset; $p_offset = $nkeys - 1 if $p_offset > $nkeys - 1; ### handle warnings ### unless (defined $length) { warnings::warnif('uninitialized', 'Use of uninitialized value in splice'); $length = $nkeys - $offset; } warnings::warnif('misc', 'splice() offset past end of array') if $offset > $nkeys - 1; die "Modification of non-creatable array value attempted, subscript $offset" if $offset < 0 && abs($offset) > $nkeys; ### remove elements ### my @removed; #warn "length=$length"; if ($length > 0) { my $cursor = $self->db_cursor(DB_WRITECURSOR); my $max_idx = $p_offset + $length - 1 > $nkeys - 1 ? $nkeys - 1 : $p_offset + $length - 1; for ($p_offset..$max_idx) { my $key = $p_offset; my $value = undef; my $status = $cursor->c_get($key, $value, DB_SET) == 0; #set cursor position next if $status == DB_NOTFOUND || $status == DB_KEYEMPTY; push @removed, _db_filter_array_elem_not_exists_to_undef($value); return @removed unless $cursor->c_del() == 0; $nkeys--; } $cursor->c_close(); } ### insert elements ### if (@_) { my $num_vals = scalar @_; #warn "num_vals to insert=$num_vals"; ### extend database to new size ### $nkeys = $self->STORESIZE($nkeys + $num_vals); #warn "new size=",$nkeys; ### insert elements starting at offset (and temporarily save old ones) ### my @values_to_move; my $cursor = $self->db_cursor(DB_WRITECURSOR); my $max_idx = $p_offset + ($num_vals - 1) > $nkeys - 1 ? $nkeys - 1 : $p_offset + ($num_vals - 1); #warn "insert: range=$p_offset..$max_idx"; for my $key ($p_offset..$max_idx) { my $value = undef; my $new_value = shift; my $status = $cursor->c_get($key, $value, DB_SET) == 0; #set cursor position next if $status == DB_NOTFOUND || $status == DB_KEYEMPTY; push @values_to_move, $value; #warn "insert: key=$key"; return @removed unless $cursor->c_put($key, $new_value, DB_CURRENT) == 0; #warn "insert success! (status=$status)"; } ### move elements shifted by splice ### #warn "move: values=(",join(',', @values_to_move),")"; #warn "move: range=",($p_offset + $num_vals)."..".($nkeys - 1); for my $key (($p_offset + $num_vals)..($nkeys - 1)) { #warn "move: key=$key"; my $value = undef; my $status = $cursor->c_get($key, $value, DB_SET) == 0; #set cursor position next if $status == DB_NOTFOUND || $status == DB_KEYEMPTY; push @values_to_move, $value; return @removed unless $cursor->c_put($key, shift @values_to_move, DB_CURRENT) == 0; } $cursor->c_close(); } return @removed; } #--------------------------------------------------------------------------- sub UNTIE { eval { $_[0]->db_sync(); }; } sub DESTROY { # eval { $_[0]->db_sync(); }; $_[0]->SUPER::DESTROY(@_) if $_[0]; } #--------------------------------------------------------------------------- 1; __END__ =pod =head1 NAME forks::BerkeleyDB::shared::array - class for tie-ing arrays to BerkeleyDB Recno =head1 DESCRIPTION Helper class for L. See documentation there. =head1 AUTHOR Eric Rybski . =head1 COPYRIGHT Copyright (c) 2006-2008 Eric Rybski . All rights reserved. This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself. =head1 SEE ALSO L, L. =cut