File Coverage

blib/lib/Ace.pm
Criterion Covered Total %
statement 88 425 20.7
branch 18 232 7.7
condition 10 89 11.2
subroutine 17 53 32.0
pod 22 31 70.9
total 155 830 18.6


line stmt bran cond sub pod time code
1             package Ace;
2              
3 4     4   12035 use strict;
  4         8  
  4         200  
4 4     4   21 use Carp qw(croak carp cluck);
  4         6  
  4         335  
5 4     4   23 use Scalar::Util 'weaken';
  4         10  
  4         479  
6              
7 4     4   20 use vars qw($VERSION @ISA @EXPORT @EXPORT_OK $Error $DEBUG_LEVEL);
  4         6  
  4         624  
8              
9 4     4   7435 use Data::Dumper;
  4         50997  
  4         331  
10 4     4   13037 use AutoLoader 'AUTOLOAD';
  4         7782  
  4         44  
11             require Exporter;
12             use overload
13 4         29 '""' => 'asString',
14 4     4   179 'cmp' => 'cmp';
  4         7  
15              
16             @ISA = qw(Exporter);
17              
18             # Items to export into callers namespace by default.
19             @EXPORT = qw(STATUS_WAITING STATUS_PENDING STATUS_ERROR);
20              
21             # Optional exports
22             @EXPORT_OK = qw(rearrange ACE_PARSE);
23             $VERSION = '1.92';
24              
25 4     4   386 use constant STATUS_WAITING => 0;
  4         6  
  4         237  
26 4     4   21 use constant STATUS_PENDING => 1;
  4         6  
  4         175  
27 4     4   19 use constant STATUS_ERROR => -1;
  4         7  
  4         155  
28 4     4   18 use constant ACE_PARSE => 3;
  4         6  
  4         143  
29              
30 4     4   17 use constant DEFAULT_PORT => 200005; # rpc server
  4         6  
  4         148  
31 4     4   19 use constant DEFAULT_SOCKET => 2005; # socket server
  4         5  
  4         24524  
32              
33             require Ace::Iterator;
34             require Ace::Object;
35 4     4   2277 eval qq{use Ace::Freesubs}; # XS file, may not be available
  0         0  
  0         0  
36              
37             # Map database names to objects (to fix file-caching issue)
38             my %NAME2DB;
39              
40             # internal cache of objects
41             my %MEMORY_CACHE;
42              
43             my %DEFAULT_CACHE_PARAMETERS = (
44             default_expires_in => '1 day',
45             auto_purge_interval => '12 hours',
46             );
47              
48             # Preloaded methods go here.
49             $Error = '';
50              
51             # Pseudonyms and deprecated methods.
52             *list = \&fetch;
53             *Ace::ERR = *Error;
54              
55             # now completely deprecated and gone
56             # *find_many = \&fetch_many;
57             # *models = \&classes;
58              
59             sub connect {
60 3     3 1 476 my $class = shift;
61 3         7 my ($host,$port,$user,$pass,$path,$program,
62             $objclass,$timeout,$query_timeout,$database,
63             $server_type,$url,$u,$p,$cache,$other);
64              
65             # one-argument single "URL" form
66 3 50       64 if (@_ == 1) {
67 0         0 return $class->connect(-url=>shift);
68             }
69              
70             # multi-argument (traditional) form
71 3         37 ($host,$port,$user,$pass,
72             $path,$objclass,$timeout,$query_timeout,$url,$cache,$other) =
73             rearrange(['HOST','PORT','USER','PASS',
74             'PATH',['CLASS','CLASSMAPPER'],'TIMEOUT',
75             'QUERY_TIMEOUT','URL','CACHE'],@_);
76              
77 3 50 0     16 ($host,$port,$u,$pass,$p,$server_type) = $class->process_url($url)
78             or croak "Usage: Ace->connect(-host=>\$host,-port=>\$port [,-path=>\$path]\n"
79             if defined $url;
80              
81 3 50       11 if ($path) { # local database
82 0         0 $server_type = 'Ace::Local';
83             } else { # either RPC or socket server
84 3   50     11 $host ||= 'localhost';
85 3   50     129 $user ||= $u || '';
      33        
86 3   50     25 $path ||= $p || '';
      33        
87 3 0 33     8 $port ||= $server_type eq 'Ace::SocketServer' ? DEFAULT_SOCKET : DEFAULT_PORT;
88 3 50       27 $query_timeout = 120 unless defined $query_timeout;
89 3 50 50     24 $server_type ||= 'Ace::SocketServer' if $port < 100000;
90 3 50 0     21 $server_type ||= 'Ace::RPC' if $port >= 100000;
91             }
92              
93             # we've normalized parameters, so do the actual connect
94 3 50       241 eval "require $server_type" || croak "Module $server_type not loaded: $@";
95 3 50       25 if ($path) {
96 0         0 $database = $server_type->connect(-path=>$path,%$other);
97             } else {
98 3         47 $database = $server_type->connect($host,$port,$query_timeout,$user,$pass,%$other);
99             }
100              
101 3 50       20 unless ($database) {
102 3   50     11 $Ace::Error ||= "Couldn't open database";
103 3         35 return;
104             }
105              
106 0   0     0 my $contents = {
107             'database'=> $database,
108             'host' => $host,
109             'port' => $port,
110             'path' => $path,
111             'class' => $objclass || 'Ace::Object',
112             'timeout' => $query_timeout,
113             'user' => $user,
114             'pass' => $pass,
115             'other' => $other,
116             'date_style' => 'java',
117             'auto_save' => 0,
118             };
119              
120 0   0     0 my $self = bless $contents,ref($class)||$class;
121              
122 0 0       0 $self->_create_cache($cache) if $cache;
123 0         0 $self->name2db("$self",$self);
124 0         0 return $self;
125             }
126              
127             sub reopen {
128 0     0 1 0 my $self = shift;
129 0 0       0 return 1 if $self->ping;
130 0         0 my $class = ref($self->{database});
131 0         0 my $database;
132 0 0       0 if ($self->{path}) {
133 0         0 $database = $class->connect(-path=>$self->{path},%{$self->other});
  0         0  
134             } else {
135 0         0 $database = $class->connect($self->{host},$self->{port}, $self->{timeout},
136 0         0 $self->{user},$self->{pass},%{$self->{other}});
137             }
138 0 0       0 unless ($database) {
139 0         0 $Ace::Error = "Couldn't open database";
140 0         0 return;
141             }
142 0         0 $self->{database} = $database;
143 0         0 1;
144             }
145              
146             sub class {
147 0     0 0 0 my $self = shift;
148 0         0 my $d = $self->{class};
149 0 0       0 $self->{class} = shift if @_;
150 0         0 $d;
151             }
152              
153             sub class_for {
154 0     0 0 0 my $self = shift;
155 0         0 my ($class,$id) = @_;
156 0         0 my $selected_class;
157              
158 0 0       0 if (my $selector = $self->class) {
159 0 0       0 if (ref $selector eq 'HASH') {
    0          
    0          
160 0   0     0 $selected_class = $selector->{$class} || $selector->{'_DEFAULT_'};
161             }
162             elsif ($selector->can('class_for')) {
163 0         0 $selected_class = $selector->class_for($class,$id,$self);
164             }
165             elsif (!ref $selector) {
166 0         0 $selected_class = $selector;
167             }
168             else {
169 0         0 croak "$selector is neither a scalar, nor a HASH, nor an object that supports the class_for() method";
170             }
171             }
172              
173 0   0     0 $selected_class ||= 'Ace::Object';
174              
175 0 0 0     0 eval "require $selected_class; 1;" || croak $@
176             unless $selected_class->can('new');
177              
178 0         0 $selected_class;
179             }
180              
181             sub process_url {
182 0     0 0 0 my $class = shift;
183 0         0 my $url = shift;
184 0         0 my ($host,$port,$user,$pass,$path,$server_type) = ('','','','','','');
185              
186 0 0       0 if ($url) { # look for host:port
187 0         0 local $_ = $url;
188 0 0       0 if (m!^rpcace://([^:]+):(\d+)$!) { # rpcace://localhost:200005
    0          
    0          
    0          
    0          
189 0         0 ($host,$port) = ($1,$2);
190 0         0 $server_type = 'Ace::RPC';
191             } elsif (m!^sace://([\w:]+)\@([^:]+):(\d+)$!) { # sace://user@localhost:2005
192 0         0 ($user,$host,$port) = ($1,$2,$3);
193 0         0 $server_type = 'Ace::SocketServer';
194             } elsif (m!^sace://([^:]+):(\d+)$!) { # sace://localhost:2005
195 0         0 ($host,$port) = ($1,$2);
196 0         0 $server_type = 'Ace::SocketServer';
197             } elsif (m!^tace:(/.+)$!) { # tace:/path/to/database
198 0         0 $path = $1;
199 0         0 $server_type = 'Ace::Local';
200             } elsif (m!^(/.+)$!) { # /path/to/database
201 0         0 $path = $1;
202 0         0 $server_type = 'Ace::Local';
203             } else {
204 0         0 return;
205             }
206             }
207              
208 0 0       0 if ($user =~ /:/) {
209 0         0 ($user,$pass) = split /:/,$user;
210             }
211              
212 0         0 return ($host,$port,$user,$pass,$path,$server_type);
213              
214             }
215              
216             # Return the low-level Ace::AceDB object
217             sub db {
218 0     0 0 0 return $_[0]->{'database'};
219             }
220              
221             # Fetch a model from the database.
222             # Since there are limited numbers of models, we cache
223             # the results internally.
224             sub model {
225 0     0 1 0 my $self = shift;
226 0         0 require Ace::Model;
227 0         0 my $model = shift;
228 0         0 my $break_cycle = shift; # for breaking cycles when following #includes
229 0         0 my $key = join(':',$self,'MODEL',$model);
230 0   0     0 $self->{'models'}{$model} ||= eval{$self->cache->get($key)};
  0         0  
231 0 0       0 unless ($self->{models}{$model}) {
232 0         0 $self->{models}{$model} =
233             Ace::Model->new($self->raw_query("model \"$model\""),$self,$break_cycle);
234 0         0 eval {$self->cache->set($key=>$self->{models}{$model})};
  0         0  
235             }
236 0         0 return $self->{'models'}{$model};
237             }
238              
239             # cached get
240             # pass "1" for fill to get a full fill
241             # pass any other true value to get a tag fill
242             sub get {
243 0     0 1 0 my $self = shift;
244 0         0 my ($class,$name,$fill) = @_;
245              
246             # look in caches first
247 0   0     0 my $obj = $self->memory_cache_fetch($class=>$name)
248             || $self->file_cache_fetch($class=>$name);
249 0 0       0 return $obj if $obj;
250              
251             # _acedb_get() does the caching
252 0 0       0 $obj = $self->_acedb_get($class,$name,$fill) or return;
253 0         0 $obj;
254             }
255              
256             sub _acedb_get {
257 0     0   0 my $self = shift;
258 0         0 my ($class,$name,$filltag) = @_;
259 0 0       0 return unless $self->count($class,$name) >= 1;
260              
261             #return $self->{class}->new($class,$name,$self,1) unless $filltag;
262 0 0       0 return ($self->_list)[0] unless $filltag;
263              
264 0 0 0     0 if (defined $filltag && $filltag eq '1') { # full fill
265 0         0 return $self->_fetch();
266             } else {
267 0         0 return $self->_fetch(undef,undef,$filltag);
268             }
269             }
270              
271              
272             #### CACHE AND CARRY CODE ####
273             # Be very careful here. The key used for the memory cache is in the format
274             # db:class:name, but the key used for the file cache is in the format class:name.
275             # The difference is that the filecache has a built-in namespace but the memory
276             # cache doesn't.
277             sub memory_cache_fetch {
278 0     0 1 0 my $self = shift;
279 0         0 my ($class,$name) = @_;
280 0         0 my $key = join ":",$self,$class,$name;
281 0 0       0 return unless defined $MEMORY_CACHE{$key};
282 0 0       0 carp "memory_cache hit on $class:$name"
283             if Ace->debug;
284 0         0 return $MEMORY_CACHE{$key};
285             }
286              
287             sub memory_cache_store {
288 0     0 1 0 my $self = shift;
289 0 0       0 croak "Usage: memory_cache_store(\$obj)" unless @_ == 1;
290 0         0 my $obj = shift;
291 0         0 my $key = join ':',$obj->db,$obj->class,$obj->name;
292 0 0       0 return if exists $MEMORY_CACHE{$key};
293 0 0       0 carp "memory_cache store on ",$obj->class,":",$obj->name if Ace->debug;
294 0         0 weaken($MEMORY_CACHE{$key} = $obj);
295             }
296              
297             sub memory_cache_clear {
298 0     0 1 0 my $self = shift;
299 0         0 %MEMORY_CACHE = ();
300             }
301              
302             sub memory_cache_delete {
303 0     0 1 0 my $package = shift;
304 0 0       0 my $obj = shift or croak "Usage: memory_cache_delete(\$obj)";
305 0         0 my $key = join ':',$obj->db,$obj->class,$obj->name;
306 0         0 delete $MEMORY_CACHE{$key};
307             }
308              
309             # Call as:
310             # $ace->file_cache_fetch($class=>$id)
311             sub file_cache_fetch {
312 0     0 1 0 my $self = shift;
313 0         0 my ($class,$name) = @_;
314 0         0 my $key = join ':',$class,$name;
315 0 0       0 my $cache = $self->cache or return;
316 0         0 my $obj = $cache->get($key);
317 0 0 0     0 if ($obj && !exists $obj->{'.root'}) { # consistency checks
318 0         0 require Data::Dumper;
319 0         0 warn "CACHE BUG! Discarding inconsistent object $obj\n";
320 0         0 warn Data::Dumper->Dump([$obj],['obj']);
321 0         0 $cache->remove($key);
322 0         0 return;
323             }
324 0 0       0 warn "cache ",$obj?'hit':'miss'," on '$key'\n" if Ace->debug;
    0          
325 0 0       0 $self->memory_cache_store($obj) if $obj;
326 0         0 $obj;
327             }
328              
329             # call as
330             # $ace->file_cache_store($obj);
331             sub file_cache_store {
332 0     0 1 0 my $self = shift;
333 0         0 my $obj = shift;
334              
335 0 0       0 return unless $obj->name;
336              
337 0         0 my $key = join ':',$obj->class,$obj->name;
338 0 0       0 my $cache = $self->cache or return;
339              
340 0 0       0 warn "caching $key obj=",overload::StrVal($obj),"\n" if Ace->debug;
341 0 0       0 if ($key eq ':') { # something badly wrong
342 0         0 cluck "NULL OBJECT";
343             }
344 0         0 $cache->set($key,$obj);
345             }
346              
347             sub file_cache_delete {
348 0     0 1 0 my $self = shift;
349 0         0 my $obj = shift;
350 0         0 my $key = join ':',$obj->class,$obj->name;
351 0 0       0 my $cache = $self->cache or return;
352              
353 0 0       0 carp "deleting $key obj=",overload::StrVal($obj),"\n" if Ace->debug;
354 0         0 $cache->remove($key,$obj);
355             }
356              
357             #### END: CACHE AND CARRY CODE ####
358              
359              
360             # Fetch one or a group of objects from the database
361             sub fetch {
362 0     0 1 0 my $self = shift;
363 0         0 my ($class,$pattern,$count,$offset,$query,$filled,$total,$filltag) =
364             rearrange(['CLASS',['NAME','PATTERN'],'COUNT','OFFSET','QUERY',
365             ['FILL','FILLED'],'TOTAL','FILLTAG'],@_);
366              
367 0 0 0     0 if (defined $class
      0        
368             && defined $pattern
369             && $pattern !~ /[\?\*]/
370             # && !wantarray
371             ) {
372 0         0 return $self->get($class,$pattern,$filled);
373             }
374              
375 0         0 $offset += 0;
376 0   0     0 $pattern ||= '*';
377 0         0 $pattern = Ace->freeprotect($pattern);
378 0 0       0 if (defined $query) {
    0          
379 0 0       0 $query = "query $query" unless $query=~/^query\s/;
380             } elsif (defined $class) {
381 0         0 $query = qq{find $class $pattern};
382             } else {
383 0         0 croak "must call fetch() with the -class or -query arguments";
384             }
385              
386              
387 0         0 my $r = $self->raw_query($query);
388              
389 0         0 my ($cnt) = $r =~ /Found (\d+) objects/m;
390 0 0       0 $$total = $cnt if defined $total;
391              
392             # Scalar context and a pattern match operation. Return the
393             # object count without bothering to fetch the objects
394 0 0 0     0 return $cnt if !wantarray and $pattern =~ /(?:[^\\]|^)[*?]/;
395              
396 0         0 my(@h);
397 0 0       0 if ($filltag) {
398 0         0 @h = $self->_fetch($count,$offset,$filltag);
399             } else {
400 0 0       0 @h = $filled ? $self->_fetch($count,$offset) : $self->_list($count,$offset);
401             }
402              
403 0 0       0 return wantarray ? @h : $h[0];
404             }
405              
406             sub cache {
407 0     0 1 0 my $self = shift;
408 0         0 my $d = $self->{filecache};
409 0 0       0 $self->{filecache} = shift if @_;
410 0         0 $d;
411             }
412              
413             sub _create_cache {
414 0     0   0 my $self = shift;
415 0         0 my $params = shift;
416 0 0 0     0 $params = {} if $params and !ref $params;
417              
418 0 0       0 return unless eval {require Cache::SizeAwareFileCache}; # not installed
  0         0  
419              
420 0         0 (my $namespace = "$self") =~ s!/!_!g;
421 0         0 my %cache_params = (
422             namespace => $namespace,
423             %DEFAULT_CACHE_PARAMETERS,
424             %$params,
425             );
426 0         0 my $cache_obj = Cache::SizeAwareFileCache->new(\%cache_params);
427 0         0 $self->cache($cache_obj);
428             }
429              
430             # class method
431             sub name2db {
432 0     0 1 0 shift;
433 0         0 my $name = shift;
434 0 0       0 return unless defined $name;
435 0         0 my $d = $NAME2DB{$name};
436             # weaken($NAME2DB{$name} = shift) if @_;
437 0 0       0 $NAME2DB{$name} = shift if @_;
438 0         0 $d;
439             }
440              
441             # make a new object using indicated class and name pattern
442             sub new {
443 0     0 1 0 my $self = shift;
444 0         0 my ($class,$pattern) = rearrange([['CLASS'],['NAME','PATTERN']],@_);
445 0 0 0     0 croak "You must provide -class and -pattern arguments"
446             unless $class && $pattern;
447             # escape % signs in the string
448 0         0 $pattern = Ace->freeprotect($pattern);
449 0         0 $pattern =~ s/(?
450 0         0 my $r = $self->raw_query("new $class $pattern");
451 0 0 0     0 if (defined($r) and $r=~/write access/im) { # this keeps changing
452 0         0 $Ace::Error = "Write access denied";
453 0         0 return;
454             }
455              
456 0 0       0 unless ($r =~ /($class)\s+\"([^\"]+)\"$/im) {
457 0         0 $Ace::Error = $r;
458 0         0 return;
459             }
460 0         0 $self->fetch($1 => $2);
461             }
462              
463             # perform an AQL query
464             sub aql {
465 0     0 1 0 my $self = shift;
466 0         0 my $query = shift;
467 0         0 my $db = $self->db;
468 0         0 my $r = $self->raw_query("aql -j $query");
469 0 0       0 if ($r =~ /(AQL error.*)/) {
470 0         0 $self->error($1);
471 0         0 return;
472             }
473 0         0 my @r;
474 0         0 foreach (split "\n",$r) {
475 0 0       0 next if m!^//!;
476 0 0       0 next if m!^\0!;
477 0         0 my ($class,$id) = Ace->split($_);
478 0         0 my @objects = map { $self->class_for($class,$id)->new(Ace->split($_),$self,1)} split "\t";
  0         0  
479 0         0 push @r,\@objects;
480             }
481 0         0 return @r;
482             }
483              
484             # Return the contents of a keyset. Pattern matches are allowed, in which case
485             # the keysets will be merged.
486             sub keyset {
487 0     0 1 0 my $self = shift;
488 0         0 my $pattern = shift;
489 0         0 $self->raw_query (qq{find keyset "$pattern"});
490 0         0 $self->raw_query (qq{follow});
491 0         0 return $self->_list;
492             }
493              
494              
495             #########################################################
496             # These functions are for low-level (non OO) access only.
497             # This is for low-level access only.
498             sub show {
499 0     0 0 0 my ($self,$class,$pattern,$tag) = @_;
500 0         0 $Ace::Error = '';
501 0 0       0 return unless $self->count($class,$pattern);
502              
503             # if we get here, then we've got some data to return.
504 0         0 my @result;
505 0 0       0 my $ts = $self->{'timestamps'} ? '-T' : '';
506 0         0 $self->{database}->query("show -j $ts $tag");
507 0         0 my $result = $self->read_object;
508 0 0       0 unless ($result =~ /(\d+) object dumped/m) {
509 0         0 $Ace::Error = 'Unexpected close during show';
510 0         0 return;
511             }
512 0         0 return grep (!m!^//!,split("\n\n",$result));
513             }
514              
515             sub read_object {
516 0     0 0 0 my $self = shift;
517 0 0       0 return unless $self->{database};
518 0         0 my $result;
519 0         0 while ($self->{database}->status == STATUS_PENDING()) {
520 0         0 my $data = $self->{database}->read();
521             # $data =~ s/\0//g; # get rid of nulls in the buffer
522 0 0       0 $result .= $data if defined $data;
523             }
524 0         0 return $result;
525             }
526              
527             # do a query, and return the result immediately
528             sub raw_query {
529 0     0 1 0 my ($self,$query,$no_alert,$parse) = @_;
530 0 0       0 $self->_alert_iterators unless $no_alert;
531 0 0       0 $self->{database}->query($query, $parse ? ACE_PARSE : () );
532 0         0 return $self->read_object;
533             }
534              
535             # return the last error
536             sub error {
537 0     0 1 0 my $class = shift;
538 0 0       0 $Ace::Error = shift() if defined($_[0]);
539 0         0 $Ace::Error=~s/\0//g; # get rid of nulls
540 0         0 return $Ace::Error;
541             }
542              
543             # close the database
544             sub close {
545 0     0 1 0 my $self = shift;
546 0 0       0 $self->raw_query('save') if $self->auto_save;
547 0         0 foreach (keys %{$self->{iterators}}) {
  0         0  
548 0         0 $self->_unregister_iterator($_);
549             }
550 0         0 delete $self->{database};
551             }
552              
553             sub DESTROY {
554 0     0   0 my $self = shift;
555 0 0       0 return if caller() =~ /^Cache\:\:/;
556 0 0       0 warn "$self->DESTROY at ", join ' ',caller() if Ace->debug;
557 0         0 $self->close;
558             }
559              
560              
561             #####################################################################
562             ###################### private routines #############################
563             sub rearrange {
564 7     7 0 32 my($order,@param) = @_;
565 7 50       28 return unless @param;
566 7         16 my %param;
567              
568 7 50       34 if (ref $param[0] eq 'HASH') {
569 0         0 %param = %{$param[0]};
  0         0  
570             } else {
571 7 100 66     93 return @param unless (defined($param[0]) && substr($param[0],0,1) eq '-');
572              
573 3         7 my $i;
574 3         14 for ($i=0;$i<@param;$i+=2) {
575 9         33 $param[$i]=~s/^\-//; # get rid of initial - if present
576 9         31 $param[$i]=~tr/a-z/A-Z/; # parameters are upper case
577             }
578              
579 3         16 %param = @param; # convert into associative array
580             }
581              
582 3         6 my(@return_array);
583              
584 3         17 local($^W) = 0;
585 3         6 my($key)='';
586 3         8 foreach $key (@$order) {
587 30         29 my($value);
588 30 100       54 if (ref($key) eq 'ARRAY') {
589 3         10 foreach (@$key) {
590 6 50       16 last if defined($value);
591 6         8 $value = $param{$_};
592 6         14 delete $param{$_};
593             }
594             } else {
595 27         33 $value = $param{$key};
596 27         39 delete $param{$key};
597             }
598 30         52 push(@return_array,$value);
599             }
600 3 50       23 push (@return_array,\%param) if %param;
601 3         22 return @return_array;
602             }
603              
604             # do a query, but don't return the result
605             sub _query {
606 0     0   0 my ($self,@query) = @_;
607 0         0 $self->_alert_iterators;
608 0         0 $self->{'database'}->query("@query");
609             }
610              
611             # return a portion of the active list
612             sub _list {
613 0     0   0 my $self = shift;
614 0         0 my ($count,$offset) = @_;
615 0         0 my (@result);
616 0         0 my $query = 'list -j';
617 0 0       0 $query .= " -b $offset" if defined $offset;
618 0 0       0 $query .= " -c $count" if defined $count;
619 0         0 my $result = $self->raw_query($query);
620 0         0 $result =~ s/\0//g; # get rid of &$#&@( nulls
621 0         0 foreach (split("\n",$result)) {
622 0         0 my ($class,$name) = Ace->split($_);
623 0 0 0     0 next unless $class and $name;
624 0         0 my $obj = $self->memory_cache_fetch($class,$name);
625 0   0     0 $obj ||= $self->file_cache_fetch($class,$name);
626 0 0       0 unless ($obj) {
627 0         0 $obj = $self->class_for($class,$name)->new($class,$name,$self,1);
628 0         0 $self->memory_cache_store($obj);
629 0         0 $self->file_cache_store($obj);
630             }
631 0         0 push @result,$obj;
632             }
633 0         0 return @result;
634             }
635              
636             # return a portion of the active list
637             sub _fetch {
638 0     0   0 my $self = shift;
639 0         0 my ($count,$start,$tag) = @_;
640 0         0 my (@result);
641 0 0       0 $tag = '' unless defined $tag;
642 0         0 my $query = "show -j $tag";
643 0 0       0 $query .= ' -T' if $self->{timestamps};
644 0 0       0 $query .= " -b $start" if defined $start;
645 0 0       0 $query .= " -c $count" if defined $count;
646 0         0 $self->{database}->query($query);
647 0         0 while (my @objects = $self->_fetch_chunk) {
648 0         0 push (@result,@objects);
649             }
650             # copy tag into a portion of the tree
651 0 0       0 if ($tag) {
652 0         0 for my $tree (@result) {
653 0         0 my $obj = $self->class_for($tree->class,$tree->name)->new($tree->class,$tree->name,$self,1);
654 0         0 $obj->_attach_subtree($tag=>$tree);
655 0         0 $tree = $obj;
656             }
657             }
658             # now recache 'em
659 0         0 for (@result) {
660 0 0       0 if (my $obj = $self->memory_cache_store($_)) {
661 0 0       0 %$obj = %$_ unless $obj->filled; # contents copy -- replace partial object with full object
662 0         0 $_ = $obj;
663             } else {
664 0         0 $self->memory_cache_store($_);
665             }
666             }
667 0 0       0 return wantarray ? @result : $result[0];
668             }
669              
670             sub _fetch_chunk {
671 0     0   0 my $self = shift;
672 0 0       0 return unless $self->{database}->status == STATUS_PENDING();
673 0         0 my $result = $self->{database}->read();
674 0         0 $result =~ s/\0//g; # get rid of &$#&@!! nulls
675 0         0 my @chunks = split("\n\n",$result);
676 0         0 my @result;
677 0         0 foreach (@chunks) {
678 0 0       0 next if m!^//!;
679 0 0       0 next unless /\S/; # occasional empty lines
680 0         0 my ($class,$id) = Ace->split($_); # /^\?([^?]+)\?([^?]+)\?/m;
681 0         0 push(@result,$self->class_for($class,$id)->newFromText($_,$self));
682             }
683 0         0 return @result;
684             }
685              
686             sub _alert_iterators {
687 0     0   0 my $self = shift;
688 0         0 foreach (keys %{$self->{iterators}}) {
  0         0  
689 0 0       0 $self->{iterators}{$_}->invalidate if $self->{iterators}{$_};
690             }
691 0         0 undef $self->{active_list};
692             }
693              
694             sub asString {
695 0     0 0 0 my $self = shift;
696 0 0       0 return "tace://$self->{path}" if $self->{'path'};
697 0 0 0     0 my $server = $self->db && $self->db->isa('Ace::SocketServer') ? 'sace' : 'rpcace';
698 0 0       0 return "$server://$self->{host}:$self->{port}" if $self->{'host'};
699 0         0 return ref $self;
700             }
701              
702             sub cmp {
703 0     0 0 0 my ($self,$arg,$reversed) = @_;
704 0         0 my $cmp;
705 0 0 0     0 if (ref($arg) and $arg->isa('Ace')) {
706 0         0 $cmp = $self->asString cmp $arg->asString;
707             } else {
708 0         0 $cmp = $self->asString cmp $arg;
709             }
710 0 0       0 return $reversed ? -$cmp : $cmp;
711             }
712              
713              
714             # Count the objects matching pattern without fetching them.
715             sub count {
716 0     0 1 0 my $self = shift;
717 0         0 my ($class,$pattern,$query) = rearrange(['CLASS',
718             ['NAME','PATTERN'],
719             'QUERY'],@_);
720 0         0 $Ace::Error = '';
721              
722             # A special case occurs when we have already fetched this
723             # object and it is already on the active list. In this
724             # case, we do not need to recount.
725 0 0       0 $query = '' unless defined $query;
726 0 0       0 $pattern = '' unless defined $pattern;
727 0 0       0 $class = '' unless defined $class;
728              
729 0         0 my $active_tag = "$class$pattern$query";
730 0 0 0     0 if (defined $self->{'active_list'} &&
731             defined ($self->{'active_list'}->{$active_tag})) {
732 0         0 return $self->{'active_list'}->{$active_tag};
733             }
734              
735 0 0       0 if ($query) {
736 0 0       0 $query = "query $query" unless $query=~/^query\s/;
737             } else {
738 0         0 $pattern =~ tr/\n//d;
739 0   0     0 $pattern ||= '*';
740 0         0 $pattern = Ace->freeprotect($pattern);
741 0         0 $query = "find $class $pattern";
742             }
743 0         0 my $result = $self->raw_query($query);
744             # unless ($result =~ /Found (\d+) objects/m) {
745 0 0       0 unless ($result =~ /(\d+) Active Objects/m) {
746 0         0 $Ace::Error = 'Unexpected close during find';
747 0         0 return;
748             }
749 0         0 return $self->{'active_list'}->{$active_tag} = $1;
750             }
751              
752             1;
753              
754             __END__