package Adaptor::File; use strict; use Carp; use Storable (); # Don't want to import any names #---- Global variables use vars qw(%global_adaptor_info $debugging %g_attr_names); $debugging = 0; sub new { @_ == 3 || croak 'Usage: File_Adaptor->new(file, config_file)'; my ($pkg,$file, $config_file) = @_; my $rh_adaptor_info; foreach $rh_adaptor_info (values %global_adaptor_info) { if ($rh_adaptor_info->{'file'} eq $file) { croak "File \'$file\' has already been opened\n"; } } _load_config_file($config_file); my $all_instances = load_all($file); $all_instances = {} unless defined($all_instances); my $this = \$all_instances; bless $this, $pkg; $global_adaptor_info{$this} = {'file' => $file}; $this; } my %mapping_loaded = (); sub _load_config_file { my ($file) = @_; return if (exists $mapping_loaded{$file}); $mapping_loaded{$file}++; require $file; # for now. } sub delete { (@_ == 2) || (@_ == 3) || croak "Error: adaptor->delete (obj), or \n" . ' adaptor->delete (class, id)'; my ($this, $id); $this = shift; if (@_ == 1) { my $obj = $_[0]; ($id) = $obj->get_attributes('_id'); } else { $id = $_[1]; } my $all_instances = $$this; delete $all_instances->{$id}; } sub store { # adaptor->store($obj) (@_ == 2) || croak 'Usage adaptor->store ($obj_to_store)'; my ($this, $obj_to_store) = @_; # $this is 'all_instances' my ($id) = $obj_to_store->get_attributes('_id'); my $all_instances = $$this; if (!defined ($id )) { $id = $this->_get_next_id(); $obj_to_store->set_attributes('_id'=> $id); } $all_instances->{$id} = $obj_to_store; $id; } sub get_attrs_for_class { my ($pkg) = @_; } sub flush { # adaptor->flush(); # Complements load_all() my $this = $_[0]; my $all_instances = $$this; return if (!exists $global_adaptor_info{$this}); my $file = $global_adaptor_info{$this}->{'file'}; return unless defined $file; open (F, ">$file") || die "Error opening $file: $!\n"; my ($id, $obj); while (($id, $obj) = each %$all_instances) { my $class = ref($obj); my @attrs = $obj->get_attributes(@{$g_attr_names{$class}}); Storable::store_fd([$class, $id, @attrs], \*F); } close(F); } sub load_all { # $all_instances = load_all($file); # complement of flush () my $file = shift; return () if (! -e $file); open(F, $file) || croak "Unable to load $file: $!"; # Global information first my ($class, $id, $obj, $rh_attr_names, @attrs, $all_instances); eval { while (1) { ($class, $id, @attrs) = @{Storable::retrieve_fd(\*F)}; $obj = $all_instances->{$id}; $obj = $class->new() unless defined($obj); $rh_attr_names = $g_attr_names{$class}; $obj->set_attributes("_id" => $id, map {$rh_attr_names->[$_] => $attrs[$_]} (0 .. $#attrs)); $all_instances->{$id} = $obj; } }; $all_instances; } sub begin_transaction { my ($this) = @_; $this->flush(); } sub commit_transaction { my ($this) = @_; $this->flush(); } sub rollback_transaction { my ($this) = @_; my ($file) = $global_adaptor_info{$this}->{'file'}; my $all_instances = load_all($file); ${$this} = $all_instances; } sub retrieve { my ($this, $class, $id) = @_; $$this->{$id}; } sub retrieve_all { my ($this) = @_; values %$$this; } sub retrieve_where { my ($this, $class, $query) = @_; my $all_instances = $$this; # blank queries get everything return $this->retrieve_all() if ($query !~ /\S/); my ($boolean_expression, @attrs) = parse_query($query); # @attrs contains the attr. names used in the query # ("a", "b") translates to # my ($a, $b) = $obj->get_attributes("a", "b"); my $fetch_stmt = "my (" . join(",",map{'$' . $_} @attrs) . ") = " . "\$obj->get_attributes(qw(@attrs))"; my (@retval); my $eval_str = qq{ my \$dummy_key; my \$obj; while ((\$dummy_key, \$obj) = each \%\$all_instances) { next unless ref(\$obj) eq "$class"; $fetch_stmt; push (\@retval, \$obj) if ($boolean_expression); } }; ($debugging) && (print STDERR "EVAL:\n\t$eval_str\n"); eval ($eval_str); if ($@) { print STDERR "Ill-formed query:\n\t$query\n"; ($debugging) && (print STDERR $@); } @retval; } #----------------------------------------------------------------- # Query evaluator my %string_op = ( # Map from any operator to corresponding string op '==' => 'eq', '<' => 'lt', '<=' => 'le', '>' => 'gt', '>=' => 'ge', '!=' => 'ne', ); my $ANY_OP = '<=|>=|<|>|!=|=='; # Any comparison operator sub parse_query { my ($query) = @_; # A query like (name = 'santa') && (num_helpers < 5) gets translated to # a boolean expression: # ($obj->{name} eq 'santa') && ($obj->{num_helpers} < 5); # Instead of parsing the expression, we just do a set of simple # transformations to convert it to an eval'able Perl expression. # These are the rules; # 1. A query term is (e.g. name == 'santa') # 2. If query is blank, it should evaluate to 1 # 3. mapped to '$obj->{}' # 4. If is a *quoted* string, then gets mapped to the # appropriate string op. # Rule 2. return 1 if ($query =~ /^\s*$/); # First squirrel away all instances of escaped quotes. We'll # restore them later. This way it doesn't get in the way when # we are processing rule 4. $query =~ s/\\[']/\200/g; $query =~ s/\\["]/\201/g; # Replace all '=' by '==' because it messes up the data inside the # object ! # TBD: should not do this for '=' inside strings $query =~ s/([^!><=])=/$1 == /g; # Rule 3. my %attrs; $query =~ s/(\w+)\s*($ANY_OP)/$attrs{$1}++, "\$$1 $2"/eg; # Rule 4. # Replace any comparison operator followed by a quoted string # with the appropriate string operator. A quoted string is # a quote followed by sequence of non-quotes followed by a quote $query =~ s{ ($ANY_OP) (?# Any comparison operator) \s* (?# followed by zero or more spaces,) ['"]([^'"]*)['"] (?# then by a quoted string ) }{ $string_op{$1} . ' \'' . $2 . '\'' }goxse; # global, compile-once, extended, treat as single line, eval # Restore all escaped quote characters $query =~ s/\200/\\'/g; $query =~ s/\201/\\"/g; ($query, keys %attrs); } my $counter = 0; # Calculate seconds since Jan 1, 1997 when counter was reset my $counter_reset_time = time(); sub _get_next_id { # adaptor->_get_next_id() if (++$counter > 99999) { # Assuming you can't create 99999 Perl objects in one second $counter_reset_time = time(); $counter = 0; } sprintf("%09d%05d", $counter_reset_time, ++$counter); } 1;