package FastEAV; # Persistent EAV in File with performant Algorithm use strict; use warnings; use IO::File; use Fcntl qw(:flock); use Carp; use bytes; sub DESTROY{ my $self = shift; $self->write if $self->{AUTO}; $self->{FH}->close; } sub new{ my $pkg = shift; my %cfg = ( file => '', auto => 1, confess => 0, lock => 0, @_); $Carp::Verbose = 1 if $cfg{confess}; return eval{ my $fh = IO::File->new; $fh->open($cfg{file}, O_CREAT|O_RDWR|O_BINARY) || croak "IO-Error open file '$cfg{file}': $!"; if( $cfg{lock} ){ flock($fh, LOCK_EX) or carp "Your system does not support flock"; } my $self = bless{ FH => $fh, EAV => {}, AUTO => $cfg{auto}, }, $pkg; $self->_thaw; $self; }; } sub _thaw{ my $self = shift; my $fh = $self->{FH}; my %res = (); seek $fh,0,0; my $buffer = ''; # Anzahl aller Schlüssel Werte Paare read($fh, $buffer, 4); my $eavcnt = unpack("N", $buffer) || 0; read($fh, $buffer, $eavcnt); my @lens = unpack "N$eavcnt", $buffer; while(@lens){ my $elen = shift @lens; my $alen = shift @lens; my $vlen = shift @lens; read($fh, my $ent, $elen); read($fh, my $att, $alen); read($fh, my $val, $vlen); $res{$ent}{$att} = $val; } $self->{EAV} = \%res; # Auto Increment appended read($fh, my $ai, 4); $self->{lfdnr} = unpack("N", $ai) || 10; } # Make Data persistent sub write{ my $self = shift; my $fh = $self->{FH}; my $eav = $self->{EAV}; seek $fh,0,0; $fh->truncate(0); # buffers my $pack = ''; my $data = ''; my $eavcnt = 0; foreach my $ent( keys %{$eav} ){ foreach my $att( keys %{$eav->{$ent}} ){ my $val = $eav->{$ent}{$att} || ''; $pack .= pack( "NNN", length($ent), length($att), length($val)); $data .= $ent.$att.$val; $eavcnt++; } } # $eavcnt wird multipliziert mit 12 # 3 steht für E,A,V und 4 für die jeweiligen Bytes $fh->print(pack("N", $eavcnt * 12).$pack.$data); # Append Auto Increment $fh->print( pack('N', $self->{lfdnr}) ); $fh->flush; } sub checkin{ my $self = shift; my $entity = shift; return eval{ croak "Arguments ar not a Hash" unless scalar @_ % 2 == 0; croak "Entity Empty" if ! $entity; my %hash = @_; $self->{EAV}{$entity} = \%hash; }; } sub checkout{ my $self = shift; my $entity = shift || return undef; return $self->{EAV}{$entity}; } # count Entries OR numbers sub count{ my $self = shift; return wantarray ? keys %{$self->{EAV}} : scalar keys %{$self->{EAV}}; } # delete Entry sub delete{ my $self = shift; my $entity = shift || return; delete $self->{EAV}{$entity}; } sub update{ my $self = shift; my $entity = shift; return eval{ croak "The Entity is not defined" unless defined $entity; croak "Arguments ar not a Hash" unless scalar @_ % 2 == 0; croak "The Entry does not exists" unless exists $self->{EAV}{$entity}; my %data = @_; $self->{EAV}{$entity} = \%data; 1; }; } sub purge{ my $self = shift; $self->{EAV} = {}; } sub unlock{ my $self = shift; flock($self->{FH}, LOCK_UN); } sub rawdata{ my $self = shift; $self->{FH}->seek(0,0); read($self->{FH}, my $bin, -s $self->{FH}); return $bin } sub lfdnr{ my $self = shift; return ++$self->{lfdnr}; } 1;######################################################################### __END__ package main; use Data::Dumper; use strict; use warnings; my $ff = FastEAV->new( file => '/tmp/fasteav.bin' ) or die $@; #$ff->checkin('addr', name => 'foo', plz => 12345, ort => undef) or die $@; print Dumper $ff, $ff->lfdnr;