File Coverage

blib/lib/NoSQL/PL2SQL/Simple.pm
Criterion Covered Total %
statement 22 253 8.7
branch 0 92 0.0
condition 0 59 0.0
subroutine 8 37 21.6
pod 13 18 72.2
total 43 459 9.3


line stmt bran cond sub pod time code
1             package NoSQL::PL2SQL::Simple;
2              
3 1     1   62581 use 5.008009;
  1         3  
  1         97  
4 1     1   6 use strict;
  1         1  
  1         34  
5 1     1   6 use warnings;
  1         7  
  1         141  
6              
7             # Items to export into callers namespace by default. Note: do not export
8             # names by default without a very good reason. Use EXPORT_OK instead.
9             # Do not simply export all your public functions/methods/constants.
10              
11             # This allows declaration use NoSQL::PL2SQL::Simple ':all';
12             # If you do not need this, moving things directly into @EXPORT or @EXPORT_OK
13             # will save memory.
14             our %EXPORT_TAGS = ( 'all' => [ qw(
15             ) ] ) ;
16              
17             our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } ) ;
18              
19             our @EXPORT = qw(
20             ) ;
21              
22             our $VERSION = '0.24' ;
23              
24 1     1   5 use Scalar::Util ;
  1         2  
  1         97  
25 1     1   5 use base qw( NoSQL::PL2SQL ) ;
  1         2  
  1         624  
26 1     1   7 use Carp ;
  1         2  
  1         4726  
27              
28             my @autodestroy = () ;
29              
30             my @sql = (
31             [ qw( textkey textvalue 1 ) ],
32             [ qw( intkey intvalue 0 ) ],
33             [ qw( datekey datevalue 1 ) ],
34             ) ;
35             my %sql = map { $_->[0] => $_ } @sql ;
36              
37             my %private ;
38              
39             ################################################################################
40             ##
41             ## update() refreshes the instance after data definition changes
42             ##
43             ################################################################################
44              
45             $private{update} = sub {
46             my $self = shift ;
47             my $tied = tied %$self ;
48             my $package = ref $self ;
49              
50             return unless $tied->{tied} ;
51             delete $tied->{tied} ;
52              
53             my $o = $package->SQLObject( $tied->{dsn}->{object}, 0 ) ;
54             my %keys = map { $_ => &{ $private{recno} }( $o, $_ ) } keys %$o ;
55             $tied->{keys} = \%keys ;
56             } ;
57              
58              
59             ################################################################################
60             ##
61             ## sqlsave() rolls back the rollback
62             ##
63             ################################################################################
64              
65             $private{sqlsave} = sub {
66             my $self = shift ;
67             my $tied = tied %$self ;
68             delete $tied->{globals}->{rollback} ;
69             } ;
70              
71              
72             ################################################################################
73             ##
74             ## recno() is part of the constructor. It uses the internal structure
75             ## of NoSQL::PL2SQL::Object to pull out the unique recordID.
76             ##
77             ################################################################################
78              
79             $private{recno} = sub {
80             my $self = shift ;
81             my $tied = tied %$self ;
82             return $tied->record->{objectid} || $tied->{top} unless @_ ;
83              
84             my $key = shift ;
85             return $tied->data->{$key}->{top} ;
86             } ;
87              
88              
89             ################################################################################
90             ##
91             ## filter() takes two arrays and returns the intersection. It is called
92             ## recursively by query().
93             ##
94             ################################################################################
95              
96             $private{filter} = sub {
97             my @set = @{ shift @_ } ;
98             return [] unless @set ;
99             return [ sort { $a <=> $b } @set ] unless @_ ;
100              
101             my @against = sort { $a <=> $b } @{ shift @_ } ;
102             my @out = () ;
103              
104             while ( @set && @against ) {
105             my $cmp = $set[0] <=> $against[0] ;
106             shift @set if $cmp < 0 ;
107             shift @against if $cmp > 0 ;
108             next if $cmp ;
109              
110             push @out, shift @set ;
111             shift @against ;
112             }
113              
114             return \@out ;
115             } ;
116              
117              
118             ################################################################################
119             ##
120             ## index() is the common method to addTextIndex, addNumberIndex, etc.
121             ##
122             ################################################################################
123              
124             $private{index} = sub {
125             my $self = shift ;
126             my $tied = tied %$self ;
127             my $package = ref $self ;
128              
129             $tied->{tied} ||= $package->SQLObject(
130             $tied->{dsn}->{object}, 0 ) ;
131             my $i = push @autodestroy, $tied->{tied} ;
132             Scalar::Util::weaken( $autodestroy[ $i -1 ] ) ;
133             return unless @_ ;
134              
135             my $type = shift ;
136             map { $self->{$_} = $tied->{tied}->{$_} = $type } @_ ;
137             } ;
138              
139              
140             ################################################################################
141             ##
142             ## matching() is commonly called by methods to query the index database.
143             ##
144             ################################################################################
145              
146             $private{matching} = sub {
147             my $self = shift ;
148             my $tied = tied %$self ;
149             my $package = ref $self ;
150              
151             my $name = shift ;
152              
153             my $format = defined $name? $sql{ $self->{ $name } }: $sql{intkey} ;
154              
155             my @sql = () ;
156             push @sql, [ $format->[0], defined $name?
157             $tied->{keys}->{ $name }: $tied->{id}
158             ] ;
159             unless ( @_ ) {
160             my @rows = $tied->{dsn}->{index}->fetch( @sql ) ;
161              
162             return [] unless @rows ;
163             return $rows[0] unless ref $rows[0] ;
164             return [] unless keys %{ $rows[0] } ;
165              
166             my @out = map { $_->{objectid}
167             => $_->{ $format->[1] } } @rows ;
168             return \@out ;
169             }
170              
171             my $value = shift ;
172             push @sql, [ $format->[1], $value, $format->[2] ] ;
173              
174             my @rows = $tied->{dsn}->{index}->fetch( @sql ) ;
175             return $rows[0] if @rows && ! ref $rows[0] ;
176             return [] unless keys %{ $rows[0] } ;
177              
178             my @out = map { $_->{objectid} => $_->{ $format->[1] } } @rows ;
179             return \@out ;
180             } ;
181              
182              
183             ################################################################################
184             ##
185             ## indexmap() creates the structures to create the SQL insert statements
186             ## for the index table
187             ##
188             ################################################################################
189              
190             $private{indexmap} = sub {
191             my $self = shift ;
192             my $tied = tied %$self ;
193             my $keys = shift ;
194             my $value = shift ;
195             my $orderid = shift ;
196             my $format = $sql{ $self->{ $keys->[1] } } ;
197              
198             my @index = () ;
199             push @index, [ $format->[0], $tied->{keys}->{ $keys->[1] } ] ;
200             push @index, [ $format->[1], $value->{ $keys->[0] },
201             $format->[2] ] ;
202             push @index, [ objectid => $orderid ] ;
203             return \@index ;
204             } ;
205              
206             ################################################################################
207             ##
208             ## getinstance() returns null for passed instances
209             ## distinguishes between instances and objects
210             ##
211             ################################################################################
212              
213             $private{getinstance} = sub {
214             my $self = shift ;
215             my $tied = tied %$self ;
216             return $tied->{parent} ;
217             } ;
218              
219             ################################################################################
220             ##
221             ## A tied hash is used to hide internal properties by overloading access
222             ## methods.
223             ##
224             ################################################################################
225              
226             sub TIEHASH {
227 0     0     my $package = shift ;
228 0           my $self = shift ;
229 0           return bless $self, $package ;
230             }
231              
232             sub CLEAR {
233 0     0     my $self = shift ;
234 0           undef $self->{clone} ;
235             }
236              
237             sub FETCH {
238 0     0     my $self = shift ;
239 0           my $key = shift ;
240 0           return $self->{clone}->{$key} ;
241             }
242              
243             sub EXISTS {
244 0     0     my $self = shift ;
245 0           my $key = shift ;
246 0           return exists $self->{clone}->{$key} ;
247             }
248              
249             sub DELETE {
250 0     0     my $self = shift ;
251 0           my $key = shift ;
252 0           return delete $self->{clone}->{$key} ;
253             }
254              
255             sub STORE {
256 0     0     my $self = shift ;
257 0           my $key = shift ;
258 0           my $value = shift ;
259 0           return $self->{clone}->{$key} = $value ;
260             }
261              
262             sub FIRSTKEY {
263 0     0     my $self = shift ;
264 0           $self->{nextkey} = [ keys %{ $self->{clone} } ] ;
  0            
265 0           return $self->NEXTKEY ;
266             }
267              
268             sub NEXTKEY {
269 0     0     my $self = shift ;
270 0           return shift @{ $self->{nextkey} } ;
  0            
271             }
272              
273             sub new {
274 0     0 1   return db( @_ ) ;
275             }
276              
277             sub db {
278 0     0 1   my $package = shift ;
279 0           my $self = {} ;
280              
281 0           my @dsn = ( @_, $package->dsn ) ;
282 0 0 0       carp( "Missing data sources" ) and return undef unless @dsn ;
283              
284 0           my $dsn = {} ;
285 0           $dsn->{object} = shift @dsn ;
286 0           $dsn->{index} = shift @dsn ;
287              
288 0           $package->SQLError( ObjectNotFound => \&newobject ) ;
289              
290 0           my $o = $package->SQLObject( $dsn->{object}, 0 ) ;
291 0           $self->{id} = &{ $private{recno} }( $o ) ;
  0            
292 0           $self->{clone} = $o->SQLClone() ;
293 0           $self->{dsn} = $dsn ;
294            
295 0           my %keys = map { $_ => &{ $private{recno} }( $o, $_ ) } keys %$o ;
  0            
  0            
296 0           $self->{keys} = \%keys ;
297 0           tie my %out, __PACKAGE__, $self ;
298              
299 0           return bless \%out, $package ;
300             }
301              
302             sub loadschema {
303 0 0   0 0   my $package = shift @_ unless ref $_[0] ;
304 0 0         my ( $dsn, $index ) = $package->dsn if defined $package ;
305              
306 0   0       $dsn ||= shift @_ ;
307 0 0 0       my $table = shift @_ if @_ && ! ref $_[0] ;
308 0 0 0       $index ||= shift @_ if @_ ;
309 0 0 0       $index ||= $dsn->table( $table ) if $dsn && $table ;
      0        
310 0 0 0       carp( "Missing data sources" ) and return
      0        
311             unless defined $dsn && defined $index ;
312              
313 0           $dsn->loadschema ;
314 0           $index->loadschema( $dsn->indexschema ) ;
315             }
316              
317             sub dsn {
318 0     0 0   my $package = shift ;
319 0           return () ;
320             }
321              
322             sub addTextIndex {
323 0     0 1   my $self = shift ;
324 0           return &{ $private{index} }( $self, $sql[0][0], @_ ) ;
  0            
325             }
326              
327             sub addNumberIndex {
328 0     0 1   my $self = shift ;
329 0           return &{ $private{index} }( $self, $sql[1][0], @_ ) ;
  0            
330             }
331              
332             sub addDateIndex {
333 0     0 1   my $self = shift ;
334 0           return &{ $private{index} }( $self, $sql[2][0], @_ ) ;
  0            
335             }
336              
337             sub recordID {
338 0     0 1   my $array = shift ;
339 0           my @args = @$array ;
340 0           my $self = shift @args ;
341 0 0         return $args[0] unless wantarray ;
342 0           return @args ;
343             }
344              
345             sub records {
346 0     0 0   my $array = shift ;
347 0           my @args = @$array ;
348 0           my $self = shift @args ;
349              
350 0           my @out = map { $self->record( $_ ) } @args ;
  0            
351 0 0 0       return $out[0] if @out && ! wantarray ;
352 0           return @out ;
353             }
354              
355             sub record {
356 0     0 1   my $self = shift ;
357 0 0         return $self->records if $self->isa('ARRAY') ;
358              
359 0           my $tied = tied %$self ;
360 0           my $package = ref $self ;
361              
362 0 0         return undef unless @_ ;
363              
364 0           &{ $private{update} }( $self ) ;
  0            
365              
366 0           my @args = ( shift @_ ) ;
367 0 0 0       push @args, ( @_ && ref $_[0] )? shift @_: undef ;
368 0 0         my ( $objectid, $value ) =
369             ref $args[0]? ( undef, $args[0] ): @args[0,1] ;
370              
371 0           my $argid = $objectid ;
372 0           my $dsn = $tied->{dsn}->{object} ;
373 0           my $index = $tied->{dsn}->{index} ;
374 0           my $out = {} ;
375              
376 0 0 0       if ( ! defined $objectid && $value
      0        
      0        
377             && ref $value eq ref $self
378             && $value->SQLObjectID ) {
379 0           $objectid = $value->SQLObjectID ;
380              
381 0 0         return $self->record( $objectid, $args[1], @_ )
382             if defined $args[1] ;
383 0           $out = tied %$value ;
384             }
385              
386 0           my %index = @_ ;
387 0           my @index = () ;
388 0 0         if ( $value ) {
389 0           map { push @index, [ $_ => $index{$_} ] }
  0            
390             grep exists $self->{ $index{$_} },
391             keys %index ;
392 0           map { push @index, [ $_ => $_ ] }
  0            
393             grep exists $value->{$_},
394             keys %$self ;
395             }
396              
397 0   0       while ( defined $value && defined $objectid ) {
398 0 0         my $archive = $self->{archive}?
399             $package->SQLClone( $dsn, $objectid ):
400             undef ;
401              
402 0           $index->delete( [ objectid => $objectid ] ) ;
403              
404 0 0 0       if ( $out->{clone} && ! defined $argid ) {
405 0           &{ $private{sqlsave} }( $out->{clone} ) ;
  0            
406             }
407             else {
408 0           delete $out->{clone} ;
409 0           $dsn->delete( [ objectid => $argid ] ) ;
410 0           $out->{clone} = $package->SQLObject(
411             $dsn, $argid, $value
412             ) ;
413             }
414              
415 0 0         last unless defined $archive ;
416              
417 0           my $archiveid = &{ $private{recno} }(
  0            
418             $package->SQLObject( $dsn, $archive )
419             ) ;
420 0           $index->insert(
421             [ intkey => $tied->{keys}->{archive} ],
422             [ intvalue => $objectid ],
423             [ objectid => $archiveid ]
424             ) ;
425              
426 0           last ;
427             }
428              
429 0           delete $out->{clone} ;
430 0 0         $out->{clone} = defined $objectid?
    0          
431             $package->SQLObject( $dsn, $objectid ):
432             defined $value?
433             $package->SQLObject( $dsn, $value ):
434             undef ;
435              
436 0 0         return undef unless $out->{clone} ;
437 0           $out->{clone}->SQLRollback ;
438 0           $out->{id} = $out->{clone}->sqlobjectid ; ## lc method name
439 0           $out->{parent} = $self ;
440              
441 0           map { $index->update( undef, @$_ ) }
  0            
442 0           map { &{ $private{indexmap} }(
  0            
443             $self, $_, $out->{clone}, $out->{id} )
444             } @index ;
445              
446 0 0         $index->update( undef,
447             [ intkey => $tied->{id} ],
448             [ intvalue => $out->{id} ],
449             [ objectid => $out->{id} ]
450             ) if $value ;
451              
452 0           tie my %out, __PACKAGE__, $out ;
453 0           return bless \%out, $package ;
454             }
455              
456             sub save {
457 0     0 1   my $self = shift ;
458 0           my $tied = tied %$self ;
459              
460 0 0         return $self->record( @_ ) unless $tied->{parent} ;
461 0           return $tied->{parent}->record( $self, @_ ) ;
462             }
463              
464             sub reindex {
465 0     0 1   my $self = shift ;
466 0           my $tied = tied %$self ;
467 0           my $parent = $tied->{parent} ;
468 0           $tied = tied %$parent ;
469 0           my $index = $tied->{dsn}->{index} ;
470 0           my $objectid = $self->SQLObjectID ;
471              
472 0 0         return "reindex() requires an index name" unless @_ ;
473 0           my $propkey = shift ;
474 0 0         my $indexkey = @_? shift @_: $propkey ;
475              
476 0 0         return "unknown index: $indexkey" unless $parent->{ $indexkey } ;
477              
478 0           my $format = $sql{ $parent->{ $indexkey } } ;
479 0           my $key = $tied->{keys}->{ $indexkey } ;
480              
481 0           $index->delete( [ $format->[0] => $key ],
482             [ objectid => $objectid ] ) ;
483 0           $index->update( undef, [ $format->[0] => $key ],
484             [ $format->[1] => $self->{ $propkey }, $format->[2] ],
485             [ objectid => $objectid ] ) ;
486 0           return undef ;
487             }
488              
489             sub SQLObjectID {
490 0     0 1   my $self = shift ;
491 0           my $tied = tied %$self ;
492 0           return $tied->{id} ;
493             }
494              
495             sub keyValues {
496 0     0 1   my $self = shift ;
497 0           my $indexid = shift ;
498              
499 0           my $instance = &{ $private{getinstance} }( $self ) ;
  0            
500 0 0 0       carp "Argument is not an object" and return () unless $instance ;
501              
502 0           my $tied = tied %$instance ;
503 0           my $dsn = $tied->{dsn}->{index} ;
504 0           my $format = $sql{ $instance->{$indexid} } ;
505 0           my @sql = ( [ objectid => $self->SQLObjectID ],
506             [ $format->[0], $tied->{keys}->{$indexid} ]
507             ) ;
508              
509 0 0         if ( @_ == 0 ) {
510 0 0         return bless [ $dsn,
511             [ $format->[1], undef, $format->[2] ],
512             @sql ], __PACKAGE__ .'::keyValues'
513             unless wantarray ;
514              
515 0           return map { $_->{ $format->[1] } } $dsn->fetch( @sql ) ;
  0            
516             }
517              
518 0           map { $dsn->insert( @sql, [ $format->[1], $_, $format->[2] ] ) } @_ ;
  0            
519             }
520              
521             sub NoSQL::PL2SQL::Simple::keyValues::clear {
522 0     0     my $args = shift ;
523 0           my $dsn = shift @$args ;
524              
525 0 0         shift @$args unless @_ ;
526 0 0         $args->[0]->[1] = shift @_ if @_ ;
527 0           return $dsn->delete( @$args ) ;
528             }
529              
530             sub delete {
531 0     0 1   my $self = shift ;
532 0           my $tied = tied %$self ;
533 0           my $package = ref $self ;
534 0 0         my $recno = shift if @_ ;
535              
536 0 0         if ( $tied->{parent} ) {
537 0           $recno = $self ;
538 0           $tied = tied %{ $tied->{parent} } ;
  0            
539             }
540              
541 0 0         return undef unless $recno ;
542 0 0         $recno = $recno->SQLObjectID if ref $recno ;
543              
544 0           my @sql = () ;
545 0           push @sql, [ $sql{intkey}->[0], $tied->{id} ] ;
546 0           push @sql, [ $sql{intkey}->[1], $recno ] ;
547 0           $tied->{dsn}->{index}->delete( @sql ) ;
548 0           return $recno ;
549             }
550              
551             sub querytest {
552 0     0 0   return &{ $private{matching} }( @_ ) ;
  0            
553             }
554              
555             ## double check how empty sets are returned
556             sub query {
557 0     0 1   my $self = shift ;
558 0           my $package = ref $self ;
559              
560 0           my @key = () ;
561 0 0         push @key, [ shift @_ ] if @_ == 1 ;
562              
563 0           my @nvp = () ;
564 0           push @nvp, @key ;
565 0           push @nvp, [ splice @_, 0, 2 ] while @_ ;
566              
567 0   0       my @error = grep @$_ && ! exists $self->{ $_->[0] }, @nvp ;
568 0 0 0       carp sprintf( "Unknown data definition %s", $error[0][0] )
569             and return () if @error ;
570 0   0       my $archive = @nvp && $nvp[0][0] eq 'archive' ;
571              
572 0           my $all = &{ $private{matching} }( $self ) ;
  0            
573 0           my $out = @nvp == 0? $all:
574 0 0         &{ $private{matching} }( $self, @{ shift @nvp } ) ;
  0            
575 0 0         return $out unless ref $out ;
576              
577 0   0       $all ||= [] ;
578 0           my $save = &{ $private{filter} }( [ keys %{ { @$all } } ] ) ;
  0            
  0            
579 0           $save = &{ $private{filter} }(
  0            
580             $archive? (): ( $save ),
581 0 0         [ keys %{ { @$out } } ]
    0          
582             ) if $out != $all ;
583              
584 0           while ( @nvp ) {
585 0   0       $out = &{ $private{matching} }( $self, @{ pop @nvp } ) || [] ;
586 0           $save = &{ $private{filter} }(
  0            
587 0           $save, [ keys %{ { @$out } } ] ) ;
588             }
589              
590 0 0         return wantarray? @$save:
    0          
591             bless [ $self, @$save ], $package
592             unless @key ;
593              
594 0           my %out = @$out ;
595 0           return map { $_ => $out{$_} } @$save ;
  0            
596             }
597              
598 0     0     sub DESTROY {}
599              
600             sub AUTOLOAD {
601 0     0     my $self = shift ;
602 0           my $package = ref $self ;
603              
604 1     1   13 use vars qw( $AUTOLOAD ) ;
  1         5  
  1         250  
605 0           my $func = $AUTOLOAD ;
606 0           $func =~ s/^${package}::// ;
607 0 0         return exists $self->{$func}? $self->query( $func, @_ ): undef ;
608             }
609              
610             sub newobject {
611 0     0 0   my $package = shift ;
612 0           my $error = shift ;
613 0           my $errortext = pop ;
614              
615 0 0 0       return carp( $errortext ) && undef if $_[-1] ;
616 0           return $package->SQLObject( @_, {} ) ;
617             }
618              
619             sub END {
620 1     1   265 undef @autodestroy ;
621             }
622              
623              
624             # Preloaded methods go here.
625              
626             1;
627             __END__