File Coverage

blib/lib/Trinket/Directory/DataAccess/RAM.pm
Criterion Covered Total %
statement 146 180 81.1
branch 25 50 50.0
condition 7 16 43.7
subroutine 31 34 91.1
pod 4 18 22.2
total 213 298 71.4


line stmt bran cond sub pod time code
1             ###########################################################################
2             ### Trinket::Directory::DataAccess::RAM
3             ###
4             ### Access to directory of persistent objects.
5             ###
6             ### $Id: RAM.pm,v 1.2 2001/02/16 07:25:45 deus_x Exp $
7             ###
8             ### TODO:
9             ### -- Callbacks for Object to accomdate on-demand property get/set
10             ### -- Do something meaningful in close()
11             ### -- Save contents of RAM directories? implement in open()/close()?
12             ### -- Cooperate with ACLs
13             ### -- Implement a cursor for access to search results
14             ### -- Implement support for data types (char is only type for now)
15             ### -- Should DESTROY() do something? (per warning)
16             ### -- How do we handle the left over undefined storage slots left by
17             ### deleted objects? (Compact the database? Renumber everything
18             ### and all references to those numbers? Use a hash instead?)
19             ### -- Prevent serialization of any properties of type 'ref'?
20             ### -- Minimize search leaf subs even further?
21             ###
22             ###########################################################################
23              
24             package Trinket::Directory::DataAccess::RAM;
25              
26 1     1   9 use strict;
  1         2  
  1         74  
27 1     1   7 use vars qw($VERSION @ISA @EXPORT $DESCRIPTION $AUTOLOAD);
  1         2  
  1         140  
28 1     1   21 no warnings qw( uninitialized );
  1         10  
  1         92  
29              
30             # {{{ Begin POD
31              
32             =head1 NAME
33              
34             Trinket::Directory::DataAccess::RAM -
35              
36             =head1 DESCRIPTION
37              
38             TODO
39              
40             =cut
41              
42             # }}}
43              
44             # {{{ METADATA
45              
46             BEGIN
47             {
48 1     1   3 $VERSION = "0.0";
49 1         21 @ISA = qw( Trinket::Directory::DataAccess );
50 1         27 $DESCRIPTION = 'Base object directory';
51             }
52              
53             # }}}
54              
55 1     1   7 use Trinket::Directory::DataAccess;
  1         2  
  1         52  
56              
57 1     1   5 use Trinket::Object;
  1         2  
  1         11  
58 1     1   6 use Trinket::Directory::FilterParser;
  1         2  
  1         134  
59 1     1   1061 use Bit::Vector::Overload;
  1         15797  
  1         89  
60 1     1   13 use Storable qw( thaw freeze );
  1         2  
  1         97  
61 1     1   1046 use MIME::Base64 qw(encode_base64 decode_base64);
  1         882  
  1         124  
62 1     1   7 use Carp qw( confess croak cluck );
  1         3  
  1         78  
63 1     1   7 use Data::Dumper qw( Dumper );
  1         3  
  1         115  
64              
65             Bit::Vector->Configuration("out=bin");
66              
67             ### Class-global collection of directories.
68             our %DIRS = ();
69              
70             # {{{ METHODS
71              
72             =head1 METHODS
73              
74             =over 4
75              
76             =cut
77              
78             # }}}
79              
80             # {{{ init(): Object initializer
81              
82             =item init({...})
83              
84             TODO
85              
86             =cut
87              
88             sub init
89             {
90 1     1   6 no strict 'refs';
  1         3  
  1         3406  
91 6     6 1 13 my ($self, $props) = @_;
92              
93 6         23 $self->{directory} = undef;
94              
95             return
96 6         17 }
97              
98             # }}}
99              
100             # {{{ create()
101              
102             =item $dir->create($params)
103              
104             Create a new object directory, destroys any existing directory
105             associated with the given parameters.
106              
107             =cut
108              
109             sub create
110             {
111 1     1 1 3 my ($self, $name, $params) = @_;
112              
113 1         3 $self->{dir_name} = $name;
114              
115 1         10 my $dir_name = $self->{dir_name};
116 1         7 $DIRS{$dir_name} =
117             {
118             created => 1,
119             objects => [],
120             indices => {}
121             };
122 1         3 $self->{directory} = $DIRS{$dir_name};
123 1 50       5 $self->{save_file} = $params->{file} if $params->{file};
124              
125 1         4 return 1;
126             }
127              
128             # }}}
129             # {{{ open()
130              
131             =item $dir->open($params)
132              
133             TODO
134              
135             =cut
136              
137             sub open
138             {
139 5     5 1 12 my ($self, $dir_name, $params) = @_;
140              
141 5 50       17 $self->{save_file} = $params->{file} if $params->{file};
142 5         15 $self->{dir_name} = $dir_name;
143              
144             ### Load save file
145 5 50       17 if (defined $self->{save_file})
146             {
147 0         0 local *FIN;
148 0         0 local $/; undef $/;
  0         0  
149 0 0       0 open (FIN, $self->{save_file}) ||
150             die "Could not open ".$self->{save_file}.": $!";
151 0         0 my $serial = ;
152 0         0 close (FIN);
153              
154 0         0 $DIRS{$self->{dir_name}} = thaw(decode_base64($serial));
155             }
156              
157 5 100       46 return undef if ($DIRS{$dir_name}->{created} ne 1);
158              
159 3         8 $self->{directory} = $DIRS{$dir_name};
160 3 50       11 $self->{cache_objects} && $self->clear_cache();
161              
162 3         14 return 1;
163             }
164              
165             # }}}
166             # {{{ close()
167              
168             =item $dir->close($params)
169              
170             TODO
171              
172             =cut
173              
174             sub close {
175 2     2 1 6 my ($self, $params) = @_;
176              
177 2 50       9 if (defined $self->{save_file}) {
178 0         0 my $serial = $self->serialize();
179 0         0 local *FOUT;
180 0 0       0 open (FOUT, "> ".$self->{save_file}) ||
181             die "Could not open ".$self->{save_file}.": $!";
182 0         0 print FOUT $serial;
183 0         0 close (FOUT);
184             }
185            
186 2         5 return 1;
187             }
188              
189             # }}}
190             # {{{ serialize()
191              
192             sub serialize {
193 0     0 0 0 my ($self) = @_;
194             #return Dumper($DIRS{$self->{dir_name}});
195 0         0 return encode_base64(freeze($DIRS{$self->{dir_name}}));
196             }
197              
198             # }}}
199             # {{{ deserialize()
200              
201             sub deserialize {
202 0     0 0 0 my ($self, $data) = @_;
203              
204 0         0 eval {
205             #my $VAR1;
206             #eval $data;
207             #$DIRS{$self->{dir_name}} = $VAR1;
208 0         0 $DIRS{$self->{dir_name}} = thaw(decode_base64($data));
209 0         0 $self->{directory} = $DIRS{$self->{dir_name}};
210             };
211 0 0       0 if ($@) {
212 0         0 return undef;
213             }
214              
215 0         0 return 1;
216             }
217              
218             # }}}
219              
220             # {{{ store_object():
221              
222             sub store_object {
223 106     106 0 161 my ($self, $obj) = @_;
224              
225 106         162 my ($serialized, $dirty, $dirty_name, $dirty_vals, $dirty_old,
226             $dirty_new, $id, $dir, $is_new);
227              
228             ### Get the object's id
229 106         328 $id = $obj->get_id();
230              
231             ### Check if this is an attempt to store an object with the id of
232             ### an object which has been previously deleted. If so, undefine
233             ### the object's id and proceed.
234 106 100 66     420 if ( (defined $id) && ( $self->is_deleted_id($id) ) )
235 1         3 { $obj->set_id($id = undef); }
236              
237             ### Does this object have an id? If not, give it one.
238 106         190 $is_new = 0;
239 106 50       436 if (!defined $id) {
240 106         504 $obj->set_id($id = $self->get_new_id());
241 106         421 $is_new = 1;
242             }
243              
244 106         209 eval {
245             ### HACK: We don't want to serialize the directory. Actually,
246             ### there are a lot of things we don't want to serialize. Will
247             ### have to find a way to handle this cleanly
248             #$obj->set_directory(undef);
249            
250             ### Serialize and store the object.
251 106         422 foreach my $prop_name ($obj->list_properties()) {
252 954         2888 $obj->get($prop_name);
253             }
254 106         624 $self->{directory}->{objects}->[$id] = $obj;
255            
256             ### Update dirty indexes...
257 106 50       496 if ($dirty = $obj->_find_dirty_indices()) {
258 106         203 while (($dirty_name, $dirty_vals) = each %{$dirty}) {
  741         4212  
259 635         1967 ($dirty_old, $dirty_new) =
260             ($dirty_vals->[DIRTY_OLD_VALUE],
261             $dirty_vals->[DIRTY_NEW_VALUE]);
262            
263 635 100       1491 if ($dirty_old)
264 3         11 { $self->delete_from_index($id, $dirty_name, $dirty_old); }
265 635         1873 $self->store_in_index($id, $dirty_name, $dirty_new);
266             }
267             }
268             };
269              
270 106 50       273 if ($@) {
271 0         0 confess ($@);
272             }
273            
274 106 50       238 if ($is_new) {
275 106         528 my @parent_classes = $obj->_derive_ancestry();
276 106         441 foreach my $class (@parent_classes) {
277 318         898 $self->store_in_index($id, 'class', $class);
278             }
279             }
280              
281             ### Storage failure. Recover, fire a warning, and return
282             ### empty-handed. (croaking now, should cluck.)
283 106 50       296 if ($@)
284 0         0 { croak ($@); return undef; }
  0         0  
285              
286 106         1218 return $id;
287             }
288              
289             # }}}
290             # {{{ retrieve_object():
291              
292             sub retrieve_object
293             {
294 28     28 0 52 my ($self, $id) = @_;
295 28         154 return $self->{directory}->{objects}->[$id];
296             }
297              
298             # }}}
299             # {{{ delete_object
300              
301             sub delete_object
302             {
303 5     5 0 10 my ($self, $id, $obj) = @_;
304              
305 5         16 $self->{directory}->{objects}->[$id] = undef;
306              
307             ### Delete all indexes for this object's properties
308             #my $name;
309             #foreach $name ($obj->list_properties())
310             # { $self->delete_from_index($id, $name); }
311              
312 5         14 return 1;
313             }
314              
315             # }}}
316             # {{{ search_objects
317              
318             ### Define a mapping of search filter LoL node names to methods
319             my %op_methods =
320             (
321             'AND' => '_search_join_op',
322             'OR' => '_search_join_op',
323             'NOT' => '_search_join_op',
324             'EQ' => '_search_leaf_op',
325             'APPROX' => '_search_leaf_op',
326             'GT' => '_search_leaf_op',
327             'GE' => '_search_leaf_op',
328             'LT' => '_search_leaf_op',
329             'LE' => '_search_leaf_op',
330             );
331              
332             sub search_objects
333             {
334 15     15 0 22 my ($self, $parsed) = @_;
335              
336 15         39 my ($op, $operand) = ($parsed->[SEARCH_OP], $parsed->[SEARCH_OPERAND]);
337              
338 15   50     53 my $op_method = $op_methods{$op} || die "No '$op' method!";
339              
340 15 50 33     73 cluck('Bad filter') if ( (!defined $op) || (!defined $operand) );
341              
342 15         133 my ($result) = $self->$op_method($op, $operand);
343              
344 15         191 return $result->Index_List_Read();
345             }
346              
347             # }}}
348              
349             # {{{ _search_join_op
350              
351             my %_search_join_subs =
352             (
353             AND => sub
354             {
355             my $result_vec = shift;
356             while(@_) {
357             my $sub_vec = shift;
358             my $tmp_vec = $result_vec->Shadow();
359             $tmp_vec->Intersection($result_vec, $sub_vec);
360             $result_vec = $tmp_vec;
361             }
362             return $result_vec;
363             },
364             OR => sub
365             {
366             my $result_vec = shift;
367             while(@_) {
368             my $sub_vec = shift;
369             my $tmp_vec = $result_vec->Shadow();
370             $tmp_vec->Union($result_vec, $sub_vec);
371             $result_vec = $tmp_vec;
372             }
373             return $result_vec;
374             },
375             NOT => sub
376             {
377             my $result_vec = shift;
378             my $tmp_vec = $result_vec->Shadow();
379             $tmp_vec->Complement($result_vec);
380             return $tmp_vec;
381             },
382             );
383              
384             sub _search_join_op
385             {
386 9     9   17 my ($self, $op, $operand) = @_;
387              
388 9         15 my $result;
389 9         11 my ($i, $sub_op, $sub_operand, $sub_op_method, $sub_result);
390 9         14 my @sub_results = ();
391              
392 9         23 my $id_range = $self->get_id_range();
393              
394 9         31 for (my $i=0; $i<@$operand; $i+=2)
395             {
396 18         42 ($sub_op, $sub_operand) =
397             ($operand->[SEARCH_OP + $i], $operand->[SEARCH_OPERAND + $i]);
398              
399 18   50     57 $sub_op_method = $op_methods{$sub_op} ||
400             die "No '$sub_op' method!";
401 18         55 $sub_result = $self->$sub_op_method($sub_op, $sub_operand);
402              
403 18         59 push @sub_results, $sub_result;
404             }
405              
406 9         30 my $result_vec = $_search_join_subs{$op}->(@sub_results);
407              
408 9         58 return $result_vec;
409             }
410              
411             # }}}
412             # {{{ _search_leaf_op
413              
414             my %_search_leaf_subs =
415             (
416             'EQ' => sub {
417             my ($index, $val) = @_;
418             my @ids;
419              
420             ### Get all ids whose value is equal to the given value.
421             if ($val eq '*') {
422             foreach my $k1 (keys %{$index})
423             { push @ids, keys %{$index->{$k1}}; }
424             } else {
425             @ids = keys %{$index->{$val}};
426             }
427            
428             return \@ids;
429             },
430             'APPROX' => sub
431             {
432             my ($index, $val) = @_;
433             my @ids;
434              
435             return \@ids;
436             },
437             'GT' => sub
438             {
439             my ($index, $val) = @_;
440             my @ids;
441             foreach my $k (keys %$index)
442             { push @ids, keys %{$index->{$k}} if ($val < $k); }
443             return \@ids;
444             },
445             'GE' => sub
446             {
447             my ($index, $val) = @_;
448             my @ids;
449             foreach my $k (keys %$index)
450             { push @ids, keys %{$index->{$k}} if ($val <= $k); }
451             return \@ids;
452             },
453             'LT' => sub
454             {
455             my ($index, $val) = @_;
456             my @ids;
457             foreach my $k (keys %$index)
458             { push @ids, keys %{$index->{$k}} if ($val > $k); }
459             return \@ids;
460             },
461             'LE' => sub
462             {
463             my ($index, $val) = @_;
464             my @ids;
465             foreach my $k (keys %$index)
466             { push @ids, keys %{$index->{$k}} if ($val >= $k); }
467             return \@ids;
468             },
469             );
470              
471             sub _search_leaf_op
472             {
473 24     24   40 my ($self, $op, $operand) = @_;
474              
475 24         33 my $result;
476              
477             ### Assert that this is an operation taking two string nodes
478 24 50 33     122 if ( ($operand->[0] ne "STRING") || ($operand->[2] ne "STRING") )
479             {
480 0         0 cluck("Bad or unimplemented filter format");
481 0         0 return undef;
482             }
483              
484 24         49 my ($name, $val) = ($operand->[1], $operand->[3]);
485              
486 24         53 my $id_range = $self->get_id_range();
487 24         142 my $result = new Bit::Vector($id_range);
488              
489             ### Grab a reference to the named index.
490 24         60 my $index = $self->{directory}->{indices}->{$name};
491              
492 24         70 my $ids = $_search_leaf_subs{$op}->($index, $val);
493              
494 24         231 $result->Index_List_Store(@$ids);
495              
496 24         117 return $result;
497             }
498              
499             # }}}
500              
501             # {{{ is_ready():
502              
503             sub is_ready
504             {
505 667     667 0 891 my $self = shift;
506              
507 667 100       1993 return undef if (!defined $self->{directory});
508              
509 665         3102 return ( $self->{directory}->{created} eq 1 );
510             }
511              
512             # }}}
513              
514             # {{{ get_new_id():
515              
516             sub get_new_id
517             {
518 106     106 0 219 my $self = shift;
519              
520 106         145 return scalar(@{$self->{directory}->{objects}});
  106         498  
521             }
522              
523             # }}}
524             # {{{ is_deleted_id():
525              
526             sub is_deleted_id
527             {
528 1     1 0 3 my ($self, $id) = @_;
529              
530 1   33     12 return ( exists ($self->{directory}->{objects}->[$id]) &&
531             !defined ($self->{directory}->{objects}->[$id]) );
532             }
533              
534             # }}}
535             # {{{ get_id_range()
536              
537             sub get_id_range
538             {
539 33     33 0 48 my $self = shift;
540              
541 33         39 return scalar(@{$self->{directory}->{objects}});
  33         92  
542             }
543              
544             # }}}
545              
546             # {{{ index_exists()
547              
548             sub index_exists
549             {
550 956     956 0 1799 my ($self, $name) = @_;
551              
552 956         4313 return ( defined ( $self->{directory}->{indices}->{$name} ) );
553             }
554              
555             # }}}
556             # {{{ create_index()
557              
558             sub create_index
559             {
560 7     7 0 11 my ($self, $name) = @_;
561              
562 7         18 $self->{directory}->{indices}->{$name} = {};
563              
564 7         13 return 1;
565             }
566              
567             # }}}
568             # {{{ delete_from_index()
569              
570             sub delete_from_index {
571 3     3 0 10 my ($self, $id, $name, $value) = @_;
572            
573 3 50       10 return undef if (!$self->index_exists($name));
574            
575 3         9 my $name_index = $self->{directory}->{indices}->{$name};
576            
577 3 50       10 if (defined $value) {
578 3         12 delete $name_index->{$value}->{$id};
579 3         18 delete $name_index->{$value}
580 3 50       5 if (! keys(%{$name_index->{$value}}) );
581             } else {
582 0         0 foreach $value ( keys %{$name_index} ) {
  0         0  
583 0 0       0 delete $name_index->{$value}->{$id}
584             if ($name_index->{$value}->{$id});
585 0 0       0 if (! keys(%{$name_index->{$value}}) ) {
  0         0  
586 0         0 delete $name_index->{$value};
587 0         0 last;
588             }
589             }
590             }
591             }
592              
593             # }}}
594             # {{{ store_in_index()
595              
596             sub store_in_index
597             {
598 953     953 0 2232 my ($self, $id, $name, $value) = @_;
599              
600 953 100       2451 $self->create_index($name)
601             if (!$self->index_exists($name));
602              
603 953         5027 $self->{directory}->{indices}->{$name}->{$value}->{$id} = 1;
604             }
605              
606             # }}}
607              
608             # {{{ DESTROY
609              
610 0     0     sub DESTROY {
611             ## no-op to pacify warnings
612            
613             }
614              
615             # }}}
616              
617             # {{{ End POD
618              
619             =back
620              
621             =head1 AUTHOR
622              
623             Maintained by Leslie Michael Orchard >
624              
625             =head1 COPYRIGHT
626              
627             Copyright (c) 2000, Leslie Michael Orchard. All Rights Reserved.
628             This module is free software; you can redistribute it and/or
629             modify it under the same terms as Perl itself.
630              
631             =cut
632              
633             # }}}
634              
635             1;
636             __END__