File Coverage

lib/App/Repository.pm
Criterion Covered Total %
statement 1 3 33.3
branch n/a
condition n/a
subroutine 1 1 100.0
pod n/a
total 2 4 50.0


line stmt bran cond sub pod time code
1              
2             #############################################################################
3             ## $Id: Repository.pm 10819 2008-02-22 20:48:06Z spadkins $
4             #############################################################################
5              
6             package App::Repository;
7             $VERSION = (q$Revision: 10819 $ =~ /(\d[\d\.]*)/)[0]; # VERSION numbers generated by svn
8              
9 3     3   15798 use App;
  0            
  0            
10             use App::Service;
11             @ISA = ( "App::Service" );
12              
13             use strict;
14              
15             use Date::Format;
16             use App::RepositoryObject;
17             use App::Reference;
18             use Time::HiRes qw(gettimeofday);
19              
20             =head1 NAME
21              
22             App::Repository - Logical data access layer for the App::Context Framework, providing a uniform API to access data in databases, file systems, remote web sites, etc.
23              
24             =head1 SYNOPSIS
25              
26             use App::Repository;
27              
28             $context = App->context();
29             $repository = $context->service("Repository"); # or ...
30             $repository = $context->repository();
31              
32             $rep = Repository::Base->new(); # looks for %ENV, then config file
33             $rep = Repository::Base->new("sysdb"); # looks for %ENV, then config file using "sysdb"
34             $rep2 = $rep->new(); # copies attributes of existing $rep
35             $rep = Repository::Base->new(@positional_args); # undefined for Repository::Base
36             $config = {
37             'repository' => {
38             'db' => {
39             'arg1' => 'value1',
40             'arg2' => 'value2',
41             },
42             'rep2' => {
43             'arg1' => 'value1',
44             'arg2' => 'value2',
45             },
46             },
47             };
48             $rep = Repository::Base->new($config);
49             $rep = Repository::Base->new("rep2",$config);
50              
51             ###################################################################
52             # The following methods are needed for SQL support
53             ###################################################################
54              
55             $errmsg = $rep->error(); # returns the error string for prev op ("" if no error)
56             $numrows = $rep->numrows(); # returns the number of rows affected by prev op
57             print $rep->error(), "\n";
58              
59             # DATA TYPE HELPER METHODS
60             $repdate = $rep->format_repdate($date_string); # free-form date string as entered by a person
61            
62             # META-DATA: (about the tables)
63             $rep->_load_rep_metadata();
64             $rep->_load_table_metadata($tablename);
65             $typenames = $rep->get_type_names(); # print "@$typenames\n";
66             $typelabels = $rep->get_type_labels(); # print "%$typelabels\n";
67             $typedef = $rep->get_type_def($typename); # print "%$type\n";
68             $tablenames = $rep->get_table_names(); # print "@$tablenames\n";
69             $tablelabels = $rep->get_table_labels(); # print "%$tablelabels\n";
70             $table_def = $rep->get_table_def($tablename); # print "%$table\n";
71             $columnnames = $rep->get_column_names($tablename); # print "@$columnnames\n";
72             $columnlabels = $rep->get_column_labels($tablename); # print "%$columnlabels\n";
73             $column_def = $rep->get_column_def($tablename,$columnname); # print "%$column\n";
74              
75             #################################################
76             # RELATIONAL
77             #################################################
78              
79             ... (see App::Repository::DBI) ...
80              
81             $relation_names = $rep->get_relation_names($table);
82             $relation_labels = $rep->get_relation_labels($table);
83             $relation_def = $rep->get_relation_def($table, $relation_name);
84             @keys = $rep->get_related_keys($table, $key, $relation_name);
85              
86             #################################################
87             # OBJECT-ORIENTED
88             #################################################
89              
90             # OBJECT-ORIENTED
91             $class = $table;
92             $obj = $rep->object($class, $key);
93              
94             # OBJECT-ORIENTED (on RepositoryObject)
95             $relation_names = $obj->get_relation_names();
96             $relation_labels = $obj->get_relation_labels();
97             $relation_def = $obj->get_relation_def($relation_name);
98             @objs = $obj->get_related_objects($relation_name);
99            
100             #################################################
101             # TECHNICAL
102             #################################################
103              
104             $rep->commit();
105             $rep->rollback();
106             $rep->import_rows($table, $columns, $file, $options);
107             $rep->export_rows($table, $columns, $file, $options);
108              
109             =cut
110              
111             =head1 DESCRIPTION
112              
113             A Repository is a means by which data may be stored somewhere or
114             retrieved from somewhere without
115             knowing what underlying technology is storing the data.
116              
117             A Repository is the central persistence concept within the App.
118             A Repository does not present a uniquely object-oriented view of
119             its data. Rather it presents a "logical relational" data model.
120             It does not return objects, but rows of data.
121              
122             The "logical data model" means that a developer can program to
123             the data model which usually comes out of system requirements analysis,
124             closely modelling the business. All of the changes to this
125             logical data model that are
126             incorporated during physical database design are abstracted
127             away, such as:
128              
129             * physical table naming,
130             * physical column naming,
131             * normalization of data into parent tables, and
132             * splitting of tables based on various physical constraints.
133              
134             This could be called object-to-relational mapping, but it is more
135             accurately called logical-to-physical-relational mapping.
136              
137             Despite the fact that the Repository is a relational data storage
138             abstraction, persistent objects (i.e. RepositoryObjects) can be built to
139             save and restore their state from a Repository. Furthermore, the
140             built-in support for non-scalar fields (references to arbitrarily
141             complex perl data structures) and the ability for RepositoryObjects
142             to encapsulate more than one row of data, makes the technology quite
143             fit for object-oriented development.
144              
145             The design of the Repository is based around three important uses of
146             data.
147              
148             * Transaction Processing
149             * Batch Processing
150             * Report Generation
151              
152             (more about this later)
153              
154             The Repository abstraction seeks to solve the following problems.
155              
156             * objects may have attributes that come from multiple sources
157             * caching
158             * isolated from physical database changes
159             * transactions
160             * data source independence
161             * no save/restore
162             * devel/test/prod environments
163              
164             What follows are some developing thoughts on this API...
165              
166             * The API should have two levels:
167             = physical
168             - no error-checking/defaults/security
169             - provided by the driver
170             - based on a physical table segment
171             - application should never call this (private methods)
172             = logical
173             - error-checking
174             - constraints (foreign key, check constraints)
175             - column-level and row-level security
176             - support transactions, caching, volatility
177             - auditing
178              
179             * Isolation levels
180             = do writers block readers, etc.
181              
182             =cut
183              
184             #############################################################################
185             # CLASS GROUP
186             #############################################################################
187              
188             =head1 Class Group: Repository
189              
190             The following classes might be a part of the Repository Class Group.
191              
192             =over
193              
194             =item * Class: App::Repository
195              
196             =item * Class: App::Repository::DBI
197              
198             =item * Class: App::Repository::File
199              
200             =item * Class: App::Repository::BerkeleyDB
201              
202             =item * Class: App::Repository::LDAP
203              
204             =item * Class: App::Repository::HTML
205             - for data stored in a web page
206              
207             =item * Class: App::Repository::SOAP
208             - remote data storage
209              
210             =item * Class: App::Repository::Cache
211             - use the Cache::Cache module
212              
213             =item * Class: App::Repository::SPOPS
214             - maybe?
215              
216             =item * Class: App::Repository::Tangram
217             - maybe?
218              
219             =item * Class: App::Repository::Alzabo
220             - maybe?
221              
222             =item * Class: App::Repository::ClassDBI
223             - maybe?
224              
225             =back
226              
227             =cut
228              
229             #############################################################################
230             # CLASS
231             #############################################################################
232              
233             =head1 Class: App::Repository
234              
235             A Repository is a means by which data may be stored somewhere without
236             knowing what underlying technology is storing the data.
237              
238             * Throws: App::Exception::Repository
239             * Since: 0.01
240              
241             =head2 Class Design
242              
243             ...
244              
245             =cut
246              
247             #############################################################################
248             # CONSTANTS
249             #############################################################################
250              
251             sub OK { 1; }
252              
253             #############################################################################
254             # ATTRIBUTES
255             #############################################################################
256              
257             # BASIC
258             # $self->{name} # name of this repository (often "db")
259             # $self->{conf} # hash of config file data
260              
261             # CURRENT STATE
262             # $self->{error} # most recent error generated from this module
263             # $self->{numrows}
264              
265             # METADATA - Database Types
266             # $self->{types}
267             # $self->{type}{$type}
268             # $self->{type}{$typenum}
269             # $self->{type}{$type}{type_name}
270             # $self->{type}{$type}{data_type}
271             # $self->{type}{$type}{column_size}
272             # $self->{type}{$type}{literal_prefix}
273             # $self->{type}{$type}{literal_suffix}
274             # $self->{type}{$type}{unsigned_attribute}
275             # $self->{type}{$type}{auto_unique_value}
276             # $self->{type}{$type}{quoted}
277              
278             # METADATA - Tables and Columns
279             # $self->{tables}
280             # $self->{table}{$table}{readonly}
281             # $self->{table}{$table}{columns}
282             # $self->{table}{$table}{column}{$column}
283             # $self->{table}{$table}{column}{$column}{name}
284             # $self->{table}{$table}{column}{$column}{type_name}
285             # $self->{table}{$table}{column}{$column}{type}
286             # $self->{table}{$table}{column}{$column}{notnull}
287             # $self->{table}{$table}{column}{$column}{quoted}
288              
289             #############################################################################
290             # METHODS
291             #############################################################################
292              
293             =head1 Methods
294              
295             =cut
296              
297             #############################################################################
298             # new()
299             #############################################################################
300              
301             =head2 new()
302              
303             The constructor is inherited from
304             L|App::Service/"new()">.
305              
306             =cut
307              
308             #############################################################################
309             # _connect()
310             #############################################################################
311              
312             =head2 _connect()
313              
314             * Signature: $repository->_connect();
315             * Param: void
316             * Return: void
317             * Throws: App::Exception::Repository
318             * Since: 0.01
319              
320             Sample Usage:
321              
322             $repository->_connect();
323              
324             Connects to the repository. Most repositories have some connection
325             initialization that takes time and therefore should be done once.
326             Then many operations may be executed against the repository.
327             Finally the connection to the repository is closed (_disconnect()).
328              
329             The default implementation of _connect() does nothing.
330             It is intended to be overridden in the subclass (if necessary).
331              
332             =cut
333              
334             sub _connect { 1; }
335              
336             #############################################################################
337             # _disconnect()
338             #############################################################################
339              
340             =head2 _disconnect()
341              
342             * Signature: $repository->_disconnect();
343             * Param: void
344             * Return: void
345             * Throws: App::Exception::Repository
346             * Since: 0.01
347              
348             Sample Usage:
349              
350             $repository->_disconnect();
351              
352             Disconnects from the repository.
353              
354             The default implementation of _disconnect() does nothing.
355             It is intended to be overridden in the subclass (if necessary).
356              
357             All implementations of _disconnect() by a subclass must be sensitive to
358             whether the object is actually currently connected to the repository.
359             Thus, _disconnect() should be callable without negative consequences
360             even when the repository is already disconnected.
361              
362             =cut
363              
364             sub _disconnect { 1; }
365              
366             sub _shutdown_unshareable_resources {
367             &App::sub_entry if ($App::trace);
368             my $self = shift;
369             &App::sub_exit() if ($App::trace);
370             }
371              
372             #############################################################################
373             # _is_connected()
374             #############################################################################
375              
376             =head2 _is_connected()
377              
378             * Signature: $connected = $repository->_is_connected();
379             * Param: void
380             * Return: $connected integer
381             * Throws: App::Exception::Repository
382             * Since: 0.01
383              
384             Sample Usage:
385              
386             if ($repository->_is_connected()) {
387             ...
388             }
389              
390             Reports whether a connection currently exists to the repository.
391              
392             The default implementation of _is_connected() returns true (1) always.
393             It is intended to be overridden in the subclass (if necessary).
394              
395             =cut
396              
397             sub _is_connected { 1; }
398              
399             #############################################################################
400             # PUBLIC METHODS
401             #############################################################################
402              
403             =head1 Public Methods
404              
405             =cut
406              
407             #############################################################################
408             # error()
409             #############################################################################
410              
411             =head2 error()
412              
413             * Signature: $errormsg = $repository->error();
414             * Param: void
415             * Return: $errormsg string
416             * Throws: App::Exception::Repository
417             * Since: 0.01
418              
419             Sample Usage:
420              
421             print $repository->error(), "\n";
422              
423             Returns the error string associated with the last operation
424             (or "" if there was no error).
425              
426             The default implementation of error() simply returns the attribute {error}
427             which must be cleared at the beginning of every operation and set when
428             appropriate.
429              
430             It is intended to be overridden in the subclass (if necessary).
431              
432             =cut
433              
434             sub error {
435             &App::sub_entry if ($App::trace);
436             my ($self) = @_;
437             my $error = $self->{error} || "";
438             &App::sub_exit($error) if ($App::trace);
439             return $error;
440             }
441              
442             #############################################################################
443             # numrows()
444             #############################################################################
445              
446             =head2 numrows()
447              
448             * Signature: $nrows = $repository->numrows();
449             * Param: void
450             * Return: $numrows integer
451             * Throws: App::Exception::Repository
452             * Since: 0.01
453              
454             Sample Usage:
455              
456             $nrows = $repository->numrows();
457              
458             Returns the number of rows affected by the last operation.
459              
460             The default implementation of numrows() simply returns the attribute {numrows}
461             which must be set to 0 at the beginning of every operation and set to a
462             higher number when appropriate.
463              
464             It is intended to be overridden in the subclass (if necessary).
465              
466             =cut
467              
468             sub numrows {
469             return( $_[0]->{numrows} || 0 );
470             }
471              
472             #############################################################################
473             # get()
474             #############################################################################
475              
476             =head2 get()
477              
478             * Signature: $value = $rep->get ($table, $key, $col, $options); [tbd]
479             * Signature: $value = $rep->get ($table, $params, $col, $options); [tbd]
480             * Signature: @row = $rep->get ($table, $key, $cols, $options); [tbd]
481             * Signature: @row = $rep->get ($table, $params, $cols, $options); [tbd]
482             * Param: $table string
483             * Param: $key string
484             * Param: $params undef,HASH
485             * Param: $col string
486             * Param: $cols ARRAY
487             * Param: $options undef,HASH
488             * Return: $value any
489             * Return: @row any
490             * Throws: App::Exception::Repository
491             * Since: 0.50
492              
493             Sample Usage:
494              
495             $value = $rep->get($table, $key, $col, \%options);
496             $value = $rep->get($table, \%params, $col, \%options);
497             @row = $rep->get($table, $key, \@cols, \%options);
498             @row = $rep->get($table, \%params, \@cols, \%options);
499              
500             tbd.
501              
502             =cut
503              
504             sub get {
505             &App::sub_entry if ($App::trace);
506             my ($self, $table, $params, $cols, $options) = @_;
507             die "get(): params undefined" if (!defined $params);
508             my ($row, $wantarray);
509             if (ref($cols) eq "ARRAY") {
510             $wantarray = 1;
511             }
512             else {
513             $cols = [ $cols ];
514             $wantarray = 0;
515             }
516             $row = $self->get_row($table, $params, $cols, $options);
517             if (!$row) {
518             &App::sub_exit(undef) if ($App::trace);
519             return(undef);
520             }
521             elsif ($wantarray) {
522             &App::sub_exit(@$row) if ($App::trace);
523             return(@$row);
524             }
525             else {
526             &App::sub_exit($row->[0]) if ($App::trace);
527             return($row->[0]);
528             }
529             }
530              
531             #############################################################################
532             # set()
533             #############################################################################
534              
535             =head2 set()
536              
537             * Signature: $nrows = $rep->set($table, $key, $col, $value, $options); [tbd]
538             * Signature: $nrows = $rep->set($table, $params, $col, $value, $options); [tbd]
539             * Param: $table string
540             * Param: $key string
541             * Param: $params undef,HASH
542             * Param: $col string
543             * Param: $value any
544             * Param: $options undef,HASH
545             * Return: $nrows integer
546             * Throws: App::Exception::Repository
547             * Since: 0.50
548              
549             Sample Usage:
550              
551             $nrows = $rep->set($table, $key, $col, $value, \%options);
552             $nrows = $rep->set($table, \%params, $col, $value, \%options);
553              
554             tbd.
555              
556             =cut
557              
558             sub set {
559             &App::sub_entry if ($App::trace);
560             my ($self, $table, $params, $col, $value, $options) = @_;
561             die "set(): params undefined" if (!defined $params);
562             my ($nrows);
563             if ($col && ref($col) eq "") {
564             $nrows = $self->set_row($table, $params, [$col], [$value], $options);
565             }
566             else {
567             $nrows = $self->set_row($table, $params, $col, $value, $options);
568             }
569             &App::sub_exit($nrows) if ($App::trace);
570             return($nrows);
571             }
572              
573             #############################################################################
574             # get_row()
575             #############################################################################
576              
577             =head2 get_row()
578              
579             * Signature: $row = $rep->get_row ($table, $key, $cols, $options);
580             * Signature: $row = $rep->get_row ($table, $params, $cols, $options);
581             * Param: $table string
582             * Param: $key string
583             * Param: $params undef,HASH
584             * Param: $cols ARRAY
585             * Param: $options undef,HASH
586             * Return: $row ARRAY
587             * Throws: App::Exception::Repository
588             * Since: 0.50
589              
590             Sample Usage:
591              
592             $row = $rep->get_row($table, $key, \@cols, \%options);
593             $row = $rep->get_row($table, \%params, \@cols, \%options);
594              
595             tbd.
596              
597             =cut
598              
599             sub get_row {
600             &App::sub_entry if ($App::trace);
601             my ($self, $table, $params, $cols, $options) = @_;
602             die "get_row(): params undefined" if (!defined $params);
603              
604             my ($row);
605             my $repname = $self->{table}{$table}{repository};
606             my $realtable = $self->{table}{$table}{table} || $table;
607             if (defined $repname && $repname ne $self->{name}) {
608             my $rep = $self->{context}->repository($repname);
609             $row = $rep->get_row($realtable, $params, $cols, $options);
610             }
611             elsif (defined $realtable && $realtable ne $table) {
612             $row = $self->get_row($realtable, $params, $cols, $options);
613             }
614             else {
615             $self->_load_table_metadata($table) if (! defined $self->{table}{$table}{loaded});
616              
617             if (!defined $cols) {
618             $cols = $self->_get_default_columns($table);
619             }
620             elsif (!ref($cols)) {
621             $cols = [ $cols ];
622             }
623             elsif ($#$cols == -1) {
624             my $columns = $self->_get_default_columns($table);
625             @$cols = @$columns;
626             }
627              
628             my $tabledef = $self->{table}{$table};
629             my ($sds, $hashkey, @cache_colidx_map);
630             if ($tabledef->{cache_name} && !$options->{cache_skip}) {
631             my $context = $self->{context};
632             my $cache_minimum_columns = $tabledef->{cache_minimum_columns};
633             if ($cache_minimum_columns) {
634             my (%colidx, $col);
635             my $cache_columns = [ @$cache_minimum_columns ];
636             for (my $i = 0; $i <= $#$cache_minimum_columns; $i++) {
637             $col = $cache_minimum_columns->[$i];
638             $colidx{$col} = $i;
639             }
640             foreach $col (sort @$cols) {
641             if (! defined $colidx{$col}) {
642             push(@$cache_columns, $col);
643             $colidx{$col} = $#$cache_columns;
644             }
645             }
646             for (my $i = 0; $i <= $#$cols; $i++) {
647             $col = $cols->[$i];
648             $cache_colidx_map[$i] = $colidx{$col};
649             }
650             $cols = $cache_columns;
651             }
652             $sds = $context->shared_datastore($tabledef->{cache_name});
653             my ($hash_options);
654             if (defined $options) {
655             $hash_options = { %$options };
656             delete $hash_options->{cache_skip};
657             delete $hash_options->{cache_refresh};
658             $hash_options = undef if (! %$hash_options);
659             }
660             $hashkey = $sds->hashkey([$table, $params, $cols, $hash_options, "row"]);
661             if (!$options->{cache_refresh}) {
662             $row = $sds->get_ref($hashkey);
663             }
664             }
665              
666             if (! defined $row) {
667             my ($col, $contains_expr);
668             my $column_defs = $self->{table}{$table}{column};
669             for (my $i = 0; $i <= $#$cols; $i++) {
670             $col = $cols->[$i];
671             $contains_expr = 1 if ($column_defs->{$col}{expr});
672             # TO BE IMPLEMENTED: Automatically follow relationships for column defs
673             # TO BE IMPLEMENTED: Delegated get_rows() and merge on another table
674             #for ($rel = 0; $rel <= $#rel_prefix; $rel++) {
675             # $rel_prefix = $rel_prefix[$rel];
676             # $rel_cols = $rel_cols[$rel];
677             # $rel_col_idx = $rel_col_idx[$rel];
678             # if ($col =~ /^${rel_prefix}_(.+)$/) {
679             # $col2 = $1;
680             # push(@$rel_cols, $col2);
681             # $rel_col_idx->[$#$rel_cols] = $i;
682             # last;
683             # }
684             #}
685             }
686             if ($contains_expr) {
687             $cols = $self->extend_columns($table, $cols);
688             }
689              
690             $row = $self->_get_row($table, $params, $cols, $options);
691              
692             if ($contains_expr) {
693             $self->evaluate_expressions($table, $params, $cols, [$row], $options);
694             }
695              
696             if ($sds) {
697             $sds->set_ref($hashkey, $row);
698             }
699             }
700             if ($sds && $tabledef->{cache_minimum_columns} && $row) {
701             $row = [ @{$row}[@cache_colidx_map] ];
702             }
703             }
704             &App::sub_exit($row) if ($App::trace);
705             return($row);
706             }
707              
708             #############################################################################
709             # set_row()
710             #############################################################################
711              
712             =head2 set_row()
713              
714             * Signature: $nrows = $rep->set_row($table, $key, $cols, $row, $options);
715             * Signature: $nrows = $rep->set_row($table, $params, $cols, $row, $options);
716             * Signature: $nrows = $rep->set_row($table, $params, $cols, $rowhash, $options);
717             * Signature: $nrows = $rep->set_row($table, $hash, undef, undef,$options);
718             * Signature: $nrows = $rep->set_row($table, $params, $hash, undef,$options);
719             * Param: $table string
720             * Param: $cols ARRAY
721             * Param: $row ARRAY
722             * Param: $rowhash HASH
723             * Param: $key string
724             * Param: $hash HASH
725             * Param: $params undef,HASH
726             * Param: $options undef,HASH
727             * Return: $nrows integer
728             * Throws: App::Exception::Repository
729             * Since: 0.50
730              
731             Sample Usage:
732              
733             $nrows = $rep->set_row($table, $key, \@cols, $row, \%options);
734             $nrows = $rep->set_row($table, \%params, \@cols, $row, \%options);
735             $nrows = $rep->set_row($table, undef, \@cols, $row, \%options);
736              
737             tbd.
738              
739             =cut
740              
741             sub set_row {
742             &App::sub_entry if ($App::trace);
743             my ($self, $table, $params, $cols, $row, $options) = @_;
744             die "set_row(): params undefined" if (!defined $params);
745             my $repname = $self->{table}{$table}{repository};
746             my $realtable = $self->{table}{$table}{table} || $table;
747             my ($nrows);
748             if (defined $repname && $repname ne $self->{name}) {
749             my $rep = $self->{context}->repository($repname);
750             $nrows = $rep->set_row($realtable, $params, $cols, $row, $options);
751             }
752             elsif (defined $realtable && $realtable ne $table) {
753             $nrows = $self->set_row($realtable, $params, $cols, $row, $options);
754             }
755             else {
756             $self->_load_table_metadata($table) if (! defined $self->{table}{$table}{loaded});
757              
758             my ($key_defined);
759             if ($row) {
760             my $ref = ref($row);
761             if ($ref && $ref ne "ARRAY") {
762             $row = [ @{$row}{@$cols} ];
763             }
764             $nrows = $self->_set_row($table, $params, $cols, $row, $options);
765             }
766             else {
767             my ($hash, $columns);
768             if ($cols) {
769             $hash = $cols;
770             my $table_def = $self->{table}{$table};
771             $columns = $table_def->{columns};
772             $columns = [ keys %$hash ] if (!$columns);
773             }
774             else {
775             $hash = $params; # a hashref was passed in instead of cols/row
776             my $table_def = $self->{table}{$table};
777             $columns = $table_def->{columns};
778             $columns = [ keys %$hash ] if (!$columns);
779             $params = undef;
780             }
781              
782             my (@cols, @row);
783             foreach my $col (@$columns) {
784             if (exists $hash->{$col}) {
785             push(@cols, $col);
786             push(@row, $hash->{$col});
787             }
788             }
789              
790             $key_defined = 1;
791              
792             if (!defined $params) {
793             my $primary_key = $self->{table}{$table}{primary_key};
794             $primary_key = [$primary_key] if (ref($primary_key) eq "");
795             $params = {};
796             my ($col);
797             for (my $keypos = 0; $keypos <= $#$primary_key; $keypos++) {
798             $col = $primary_key->[$keypos];
799             if (defined $hash->{$col}) {
800             $params->{$col} = $hash->{$col};
801             }
802             else {
803             $key_defined = 0;
804             last;
805             }
806             }
807             }
808              
809             if ($key_defined) {
810             $nrows = $self->_set_row($table, $params, \@cols, \@row, $options);
811             }
812             else {
813             $nrows = 0;
814             }
815             }
816             }
817              
818             &App::sub_exit($nrows) if ($App::trace);
819             return($nrows);
820             }
821              
822             #############################################################################
823             # get_column()
824             #############################################################################
825              
826             =head2 get_column()
827              
828             * Signature: $colvalues = $rep->get_column ($table, $params, $col, $options);
829             * Param: $table string
830             * Param: $params undef,HASH
831             * Param: $col string
832             * Param: $options undef,HASH
833             * Return: $colvalues ARRAY
834             * Throws: App::Exception::Repository
835             * Since: 0.50
836              
837             Sample Usage:
838              
839             $colvalues = $rep->get_column ($table, \%params, $col, \%options);
840              
841             tbd.
842              
843             =cut
844              
845             sub get_column {
846             &App::sub_entry if ($App::trace);
847             my ($self, $table, $params, $col, $options) = @_;
848             my (@colvalues, $rows, $row);
849             @colvalues = ();
850             $rows = $self->get_rows($table, $params, $col, $options);
851             foreach $row (@$rows) {
852             push(@colvalues, $row->[0]) if ($row && $#$row >= 0);
853             }
854             &App::sub_exit(\@colvalues) if ($App::trace);
855             return(\@colvalues);
856             }
857              
858             #############################################################################
859             # get_rows()
860             #############################################################################
861              
862             =head2 get_rows()
863              
864             * Signature: $rows = $rep->get_rows($table, $params, $cols, $options);
865             * Signature: $rows = $rep->get_rows($table, $keys, $cols, $options);
866             * Param: $table string
867             * Param: $params undef,HASH
868             * Param: $keys ARRAY
869             * Param: $cols ARRAY
870             * Param: $options undef,HASH
871             * Return: $rows ARRAY
872             * Throws: App::Exception::Repository
873             * Since: 0.50
874              
875             Sample Usage:
876              
877             $rows = $rep->get_rows ($table, \%params, \@cols, \%options);
878             $rows = $rep->get_rows ($table, \%params, $col, \%options);
879             $rows = $rep->get_rows ($table, \@keys, \@cols, \%options);
880              
881             tbd.
882              
883             =cut
884              
885             sub get_rows {
886             &App::sub_entry if ($App::trace);
887             my ($self, $table, $params, $cols, $options) = @_;
888             my ($rows);
889             my $repname = $self->{table}{$table}{repository};
890             my $realtable = $self->{table}{$table}{table} || $table;
891             if (defined $repname && $repname ne $self->{name}) {
892             my $rep = $self->{context}->repository($repname);
893             $rows = $rep->get_rows($realtable, $params, $cols, $options);
894             }
895             elsif (defined $realtable && $realtable ne $table) {
896             $rows = $self->get_rows($realtable, $params, $cols, $options);
897             }
898             else {
899             $self->_load_table_metadata($table) if (! defined $self->{table}{$table}{loaded});
900              
901             if (!defined $cols) {
902             $cols = $self->_get_default_columns($table);
903             }
904             elsif (!ref($cols)) {
905             $cols = [ $cols ];
906             }
907             elsif ($#$cols == -1) {
908             my $columns = $self->_get_default_columns($table);
909             @$cols = @$columns;
910             }
911              
912             my $tabledef = $self->{table}{$table};
913             my ($sds, $hashkey, @cache_colidx_map);
914             if ($tabledef->{cache_name} && !$options->{cache_skip}) {
915             my $context = $self->{context};
916             my $cache_minimum_columns = $tabledef->{cache_minimum_columns};
917             if ($cache_minimum_columns) {
918             my (%colidx, $col);
919             my $cache_columns = [ @$cache_minimum_columns ];
920             for (my $i = 0; $i <= $#$cache_minimum_columns; $i++) {
921             $col = $cache_minimum_columns->[$i];
922             $colidx{$col} = $i;
923             }
924             for (my $i = 0; $i <= $#$cols; $i++) {
925             $col = $cols->[$i];
926             if (! defined $colidx{$col}) {
927             push(@$cache_columns, $col);
928             $colidx{$col} = $#$cache_columns;
929             }
930             $cache_colidx_map[$i] = $colidx{$col};
931             }
932             $cols = $cache_columns;
933             }
934             $sds = $context->shared_datastore($tabledef->{cache_name});
935             my ($hash_options);
936             if (defined $options) {
937             $hash_options = { %$options };
938             delete $hash_options->{cache_skip};
939             delete $hash_options->{cache_refresh};
940             $hash_options = undef if (! %$hash_options);
941             }
942             $hashkey = $sds->hashkey([$table, $params, $cols, $hash_options, "row"]);
943             if (!$options->{cache_refresh}) {
944             $rows = $sds->get_ref($hashkey);
945             }
946             }
947              
948             if (! defined $rows) {
949              
950             my ($col, $contains_expr);
951             my $column_defs = $self->{table}{$table}{column};
952             for (my $i = 0; $i <= $#$cols; $i++) {
953             $col = $cols->[$i];
954             $contains_expr = 1 if ($column_defs->{$col}{expr});
955             # TO BE IMPLEMENTED: Automatically follow relationships for column defs
956             # TO BE IMPLEMENTED: Delegated get_rows() and merge on another table
957             #for ($rel = 0; $rel <= $#rel_prefix; $rel++) {
958             # $rel_prefix = $rel_prefix[$rel];
959             # $rel_cols = $rel_cols[$rel];
960             # $rel_col_idx = $rel_col_idx[$rel];
961             # if ($col =~ /^${rel_prefix}_(.+)$/) {
962             # $col2 = $1;
963             # push(@$rel_cols, $col2);
964             # $rel_col_idx->[$#$rel_cols] = $i;
965             # last;
966             # }
967             #}
968             }
969             if ($contains_expr) {
970             my $new_cols = $self->extend_columns($table, $cols);
971             # the caller wanted his column list extended
972             if ($#$new_cols > $#$cols && $options->{extend_columns}) {
973             @$cols = @$new_cols; # so copy the columns
974             }
975             $cols = $new_cols; # then point to the new columns regardless
976             }
977            
978             $rows = $self->_get_rows($table, $params, $cols, $options);
979            
980             if ($contains_expr) {
981             $self->evaluate_expressions($table, $params, $cols, $rows, $options);
982             }
983              
984             if ($sds) {
985             $sds->set_ref($hashkey, $rows);
986             }
987             }
988             if ($sds && $tabledef->{cache_minimum_columns}) {
989             my $requested_rows = [];
990             foreach my $row (@$rows) {
991             push(@$requested_rows, [ @{$row}[@cache_colidx_map] ]);
992             }
993             $rows = $requested_rows;
994             }
995             }
996             &App::sub_exit($rows) if ($App::trace);
997             return($rows);
998             }
999              
1000             sub _get_default_columns {
1001             &App::sub_entry if ($App::trace);
1002             my ($self, $table) = @_;
1003             my ($columns);
1004             my $table_def = $self->get_table_def($table);
1005             $columns = $table_def->{default_columns} || $table_def->{columns};
1006             $columns = $table_def->{columns} if ($columns eq "configured");
1007             die "Unknown default columns [$columns]" if (ref($columns) ne "ARRAY");
1008             &App::sub_exit($columns) if ($App::trace);
1009             return($columns);
1010             }
1011              
1012             # Called from get_rows()/get_row() in preparation for expression evaluation.
1013             # i.e. when there is at least one column in the selected list which is an
1014             # "expression" (i.e. {table}{foo}{column}{pi_2}{expr} => "{pi}/2").
1015             # If there are expressions defined in a get_rows() request, they may depend
1016             # on other columns which are not included in the columns requested. In that
1017             # case, we need to add them.
1018             sub extend_columns {
1019             &App::sub_entry if ($App::trace);
1020             my ($self, $table, $cols, $options) = @_;
1021             my (%colidx, $expr_columns, $expr, $extended, $col);
1022             my $OPTIONAL_DEFAULT = '(?::[-0-9\.]+)?';
1023             # Take note of which columns are alread in the list of requested columns.
1024             for (my $i = 0; $i <= $#$cols; $i++) {
1025             $col = $cols->[$i];
1026             $colidx{$col} = $i;
1027             }
1028             # Search each {expr} column for what other columns it depends on.
1029             my $table_def = $self->get_table_def($table);
1030             my $column_defs = $table_def->{column};
1031             for (my $i = 0; $i <= $#$cols; $i++) {
1032             $col = $cols->[$i];
1033             # The column may have an explicit definition of the columns it depends on.
1034             if ($column_defs->{$col}{expr_columns}) {
1035             $expr_columns = $column_defs->{$col}{expr_columns};
1036             }
1037             # or we may have to parse the {expr} itself to determine them.
1038             elsif ($column_defs->{$col}{expr}) {
1039             $expr = $column_defs->{$col}{expr};
1040             $expr =~ s/^[^\{\}]*\{//;
1041             $expr =~ s/$OPTIONAL_DEFAULT\}[^\{\}]*$//;
1042             $expr_columns = [ split(/$OPTIONAL_DEFAULT\}[^\{\}]*\{/, $expr) ];
1043             $column_defs->{$col}{expr_columns} = $expr_columns;
1044             }
1045             else {
1046             next;
1047             }
1048             # Go through each column required for the expression and ensure it's
1049             # included in the list of requested columns. If not, tack it on at
1050             # the end.
1051             foreach my $expr_col (@$expr_columns) {
1052             if (! defined $colidx{$expr_col}) {
1053             if (!$extended) {
1054             $extended = 1;
1055             if (!$options->{extend_columns}) {
1056             $cols = [ @$cols ]; # make a copy. don't extend original.
1057             }
1058             }
1059             push(@$cols, $expr_col); # extend the column list.
1060             $colidx{$expr_col} = $#$cols;
1061             }
1062             }
1063             }
1064             # Returns the column list which is suitably extended to satisfy any
1065             # expressions there might be.
1066             &App::sub_exit($cols) if ($App::trace);
1067             return($cols);
1068             }
1069              
1070             sub _contains_expr {
1071             &App::sub_entry if ($App::trace);
1072             my ($self, $table, $columns) = @_;
1073             my $contains_expr = 0;
1074             my ($column);
1075             my $table_def = $self->get_table_def($table);
1076             my $column_defs = $table_def->{column};
1077             for (my $i = 0; $i <= $#$columns; $i++) {
1078             $column = $columns->[$i];
1079             if ($column_defs->{$column}{expr}) {
1080             $contains_expr = 1;
1081             last;
1082             }
1083             }
1084             &App::sub_exit($contains_expr) if ($App::trace);
1085             return($contains_expr);
1086             }
1087              
1088             #############################################################################
1089             # set_rows()
1090             #############################################################################
1091              
1092             =head2 set_rows()
1093              
1094             * Signature: $nrows = $rep->set_rows($table, $keys, $cols, $rows, $options);
1095             * Param: $table string
1096             * Param: $keys undef,ARRAY
1097             * Param: $cols ARRAY
1098             * Param: $rows ARRAY
1099             * Param: $options undef,HASH
1100             * Return: $nrows integer
1101             * Throws: App::Exception::Repository
1102             * Since: 0.50
1103              
1104             Sample Usage:
1105              
1106             $nrows = $rep->set_rows($table, \%params, \@cols, $rows, \%options);
1107             $nrows = $rep->set_rows($table, undef, \@cols, $rows, \%options);
1108             $nrows = $rep->set_rows($table, \@keys, \@cols, $rows, \%options);
1109              
1110             tbd.
1111              
1112             =cut
1113              
1114             sub set_rows {
1115             &App::sub_entry if ($App::trace);
1116             my ($self, $table, $params, $cols, $rows, $options) = @_;
1117             die "set_rows(): params undefined" if (!defined $params);
1118             my ($nrows);
1119             my $repname = $self->{table}{$table}{repository};
1120             my $realtable = $self->{table}{$table}{table} || $table;
1121             if (defined $repname && $repname ne $self->{name}) {
1122             my $rep = $self->{context}->repository($repname);
1123             $nrows = $rep->set_rows($realtable, $params, $cols, $rows, $options);
1124             }
1125             elsif (defined $realtable && $realtable ne $table) {
1126             $nrows = $self->set_rows($realtable, $params, $cols, $rows, $options);
1127             }
1128             else {
1129             $self->_load_table_metadata($table) if (! defined $self->{table}{$table}{loaded});
1130             $nrows = $self->_set_rows($table, $params, $cols, $rows, $options);
1131             }
1132             &App::sub_exit($nrows) if ($App::trace);
1133             return($nrows);
1134             }
1135              
1136             #############################################################################
1137             # get_hash()
1138             #############################################################################
1139              
1140             =head2 get_hash()
1141              
1142             * Signature: $values = $rep->get_hash ($table, $key, $cols, $options);
1143             * Signature: $values = $rep->get_hash ($table, $params, $cols, $options);
1144             * Param: $table string
1145             * Param: $cols ARRAY,undef
1146             * Param: $key string
1147             * Param: $params undef,HASH
1148             * Param: $options undef,HASH
1149             * Return: $values HASH
1150             * Throws: App::Exception::Repository
1151             * Since: 0.50
1152              
1153             Sample Usage:
1154              
1155             $values = $rep->get_hash ($table, $key, \@cols, \%options);
1156             $values = $rep->get_hash ($table, \%params, \@cols, \%options);
1157             $values = $rep->get_hash ($table, $key, undef, \%options);
1158             $values = $rep->get_hash ($table, \%params, undef, \%options);
1159              
1160             tbd.
1161              
1162             =cut
1163              
1164             sub get_hash {
1165             &App::sub_entry if ($App::trace);
1166             my ($self, $table, $params, $cols, $options) = @_;
1167             die "get_hash(): params undefined" if (!defined $params);
1168             $cols = [] if (!$cols);
1169             my $row = $self->get_row($table, $params, $cols, $options);
1170             my ($hash, $col, $value);
1171             if ($row && $#$row > -1) {
1172             $hash = {};
1173             for (my $idx = 0; $idx <= $#$cols; $idx++) {
1174             $col = $cols->[$idx];
1175             $value = $row->[$idx];
1176             $hash->{$col} = $value;
1177             }
1178             }
1179             &App::sub_exit($hash) if ($App::trace);
1180             return($hash);
1181             }
1182              
1183             #############################################################################
1184             # get_hashes()
1185             #############################################################################
1186              
1187             =head2 get_hashes()
1188              
1189             * Signature: $hashes = $rep->get_hashes ($table, $key, $cols, $options);
1190             * Signature: $hashes = $rep->get_hashes ($table, $params, $cols, $options);
1191             * Param: $table string
1192             * Param: $cols ARRAY,undef
1193             * Param: $key string
1194             * Param: $params undef,HASH
1195             * Param: $options undef,HASH
1196             * Return: $hashes ARRAY
1197             * Throws: App::Exception::Repository
1198             * Since: 0.50
1199              
1200             Sample Usage:
1201              
1202             $hashes = $rep->get_hashes ($table, $key, \@cols, \%options);
1203             $hashes = $rep->get_hashes ($table, \%params, \@cols, \%options);
1204             $hashes = $rep->get_hashes ($table, $key, undef, \%options);
1205             $hashes = $rep->get_hashes ($table, \%params, undef, \%options);
1206              
1207             tbd.
1208              
1209             =cut
1210              
1211             sub get_hashes {
1212             &App::sub_entry if ($App::trace);
1213             my ($self, $table, $params, $cols, $options) = @_;
1214             $cols = [] if (!$cols);
1215             my $rows = $self->get_rows($table, $params, $cols, $options);
1216             my $hashes = [];
1217             my ($hash, $row, $col, $value);
1218             if ($rows && $#$rows > -1) {
1219             foreach $row (@$rows) {
1220             $hash = {};
1221             for (my $idx = 0; $idx <= $#$cols; $idx++) {
1222             $col = $cols->[$idx];
1223             $value = $row->[$idx];
1224             $hash->{$col} = $value;
1225             }
1226             push(@$hashes, $hash);
1227             }
1228             }
1229             &App::sub_exit($hashes) if ($App::trace);
1230             return($hashes);
1231             }
1232              
1233             #############################################################################
1234             # get_object()
1235             #############################################################################
1236              
1237             =head2 get_object()
1238              
1239             * Signature: $object = $rep->get_object ($table, $key, $cols, $options);
1240             * Signature: $object = $rep->get_object ($table, $params, $cols, $options);
1241             * Param: $table string
1242             * Param: $cols ARRAY,undef
1243             * Param: $key string
1244             * Param: $params undef,HASH
1245             * Param: $options undef,HASH
1246             * Return: $object App::RepositoryObject
1247             * Throws: App::Exception::Repository
1248             * Since: 0.50
1249              
1250             Sample Usage:
1251              
1252             $object = $rep->get_object ($table, $key, \@cols, \%options);
1253             $object = $rep->get_object ($table, \%params, \@cols, \%options);
1254             $object = $rep->get_object ($table, $key, undef, \%options);
1255             $object = $rep->get_object ($table, \%params, undef, \%options);
1256              
1257             tbd.
1258              
1259             =cut
1260              
1261             sub get_object {
1262             &App::sub_entry if ($App::trace);
1263             my ($self, $table, $params, $cols, $options) = @_;
1264             die "get_object(): params undefined" if (!defined $params);
1265             my $table_def = $self->get_table_def($table);
1266              
1267             my ($object);
1268             $object = $self->get_hash($table, $params, $cols, $options);
1269              
1270             if ($object) {
1271             my $class = $table_def->{class} || "App::RepositoryObject";
1272             # if $class is an ARRAY ref, we need to examine the qualifier(s) to determine the class
1273             $class = $self->_get_qualified_class($class, $object) if (ref($class));
1274             App->use($class);
1275              
1276             $object->{_repository} = $self;
1277             $object->{_table} = $table;
1278             bless $object, $class;
1279             if (!ref($params)) {
1280             $object->{_key} = $params;
1281             }
1282             else {
1283             my $primary_key = $table_def->{primary_key};
1284             $primary_key = [$primary_key] if (ref($primary_key) eq "");
1285             my ($key);
1286             if ($primary_key) {
1287             $key = undef;
1288             foreach my $column (@$primary_key) {
1289             if (defined $object->{$column}) {
1290             if (defined $key) {
1291             $key .= "," . $object->{$column};
1292             }
1293             else {
1294             $key = $object->{$column};
1295             }
1296             }
1297             else {
1298             $key = undef;
1299             last;
1300             }
1301             }
1302             $object->{_key} = $key if (defined $key);
1303             }
1304             }
1305             }
1306             &App::sub_exit($object) if ($App::trace);
1307             return($object);
1308             }
1309              
1310             #############################################################################
1311             # get_objects()
1312             #############################################################################
1313              
1314             =head2 get_objects()
1315              
1316             * Signature: $objects = $rep->get_objects ($table, $key, $cols, $options);
1317             * Signature: $objects = $rep->get_objects ($table, $params, $cols, $options);
1318             * Param: $table string
1319             * Param: $cols ARRAY,undef
1320             * Param: $key string
1321             * Param: $params undef,HASH
1322             * Param: $options undef,HASH
1323             * Return: $objects ARRAY
1324             * Throws: App::Exception::Repository
1325             * Since: 0.50
1326              
1327             Sample Usage:
1328              
1329             $objects = $rep->get_objects ($table, $key, \@cols, \%options);
1330             $objects = $rep->get_objects ($table, \%params, \@cols, \%options);
1331             $objects = $rep->get_objects ($table, $key, undef, \%options);
1332             $objects = $rep->get_objects ($table, \%params, undef, \%options);
1333              
1334             tbd.
1335              
1336             =cut
1337              
1338             sub get_objects {
1339             &App::sub_entry if ($App::trace);
1340             my ($self, $table, $params, $cols, $options) = @_;
1341             my $table_def = $self->get_table_def($table);
1342             my $objects = $self->get_hashes($table, $params, $cols, $options);
1343             my $primary_key = $table_def->{primary_key};
1344             $primary_key = [$primary_key] if (ref($primary_key) eq "");
1345             my ($key, $class, %used);
1346             foreach my $object (@$objects) {
1347             $object->{_repository} = $self;
1348             $object->{_table} = $table;
1349             $class = $table_def->{class} || "App::RepositoryObject";
1350             # if $class is an ARRAY ref, we need to examine the qualifier(s) to determine the class
1351             $class = $self->_get_qualified_class($class, $object) if (ref($class));
1352             if (!$used{$class}) {
1353             App->use($class);
1354             $used{$class} = 1;
1355             }
1356             bless $object, $class;
1357             if ($primary_key) {
1358             $key = undef;
1359             foreach my $column (@$primary_key) {
1360             if (defined $object->{$column}) {
1361             if (defined $key) {
1362             $key .= "," . $object->{$column};
1363             }
1364             else {
1365             $key = $object->{$column};
1366             }
1367             }
1368             else {
1369             $key = undef;
1370             last;
1371             }
1372             }
1373             $object->{_key} = $key if (defined $key);
1374             }
1375             }
1376             &App::sub_exit($objects) if ($App::trace);
1377             return($objects);
1378             }
1379              
1380             sub _get_qualified_class {
1381             &App::sub_entry if ($App::trace);
1382             my ($self, $class_table, $object) = @_;
1383             my ($class);
1384             if (ref($class_table) eq "ARRAY") {
1385             my ($qual, $qual_regexp, $qual_class);
1386             foreach my $qual_condition (@$class_table) {
1387             ($qual, $qual_regexp, $qual_class) = @$qual_condition;
1388             next if (!$qual_class);
1389             if ((!$qual) ||
1390             (!$qual_regexp && ! $object->{$qual}) ||
1391             ($object->{$qual} && $object->{$qual} =~ /$qual_regexp/)) {
1392             $class = $qual_class;
1393             last;
1394             }
1395             }
1396             }
1397             $class ||= "App::RepositoryObject";
1398             &App::sub_exit($class) if ($App::trace);
1399             return($class);
1400             }
1401              
1402             #############################################################################
1403             # get_hash_of_values_by_key()
1404             #############################################################################
1405              
1406             =head2 get_hash_of_values_by_key()
1407              
1408             * Signature: $hashes = $rep->get_hash_of_values_by_key ($table, $params, $valuecol, $keycol, $options);
1409             * Param: $table string
1410             * Param: $params undef,HASH
1411             * Param: $valuecol string
1412             * Param: $keycol string
1413             * Param: $options undef,HASH
1414             * Return: $hash HASH
1415             * Throws: App::Exception::Repository
1416             * Since: 0.50
1417              
1418             Sample Usage:
1419              
1420             $hash = $rep->get_hash_of_values_by_key ($table, \%params, $valuecol, $keycol, \%options);
1421              
1422             tbd.
1423              
1424             =cut
1425              
1426             sub get_hash_of_values_by_key {
1427             &App::sub_entry if ($App::trace);
1428             my ($self, $table, $params, $valuecol, $keycol, $options) = @_;
1429             my $rows = $self->get_rows($table, $params, [$keycol, $valuecol], $options);
1430             my $hash = {};
1431             if ($rows && $#$rows > -1) {
1432             foreach my $row (@$rows) {
1433             $hash->{$row->[0]} = $row->[1];
1434             }
1435             }
1436             &App::sub_exit($hash) if ($App::trace);
1437             return($hash);
1438             }
1439              
1440             #############################################################################
1441             # get_hash_of_hashes_by_key()
1442             #############################################################################
1443              
1444             =head2 get_hash_of_hashes_by_key()
1445              
1446             * Signature: $hashes = $rep->get_hash_of_hashes_by_key ($table, $params, $cols, $keycol, $options);
1447             * Param: $table string
1448             * Param: $params undef,HASH
1449             * Param: $cols ARRAY
1450             * Param: $keycol string
1451             * Param: $options undef,HASH
1452             * Return: $hash HASH
1453             * Throws: App::Exception::Repository
1454             * Since: 0.50
1455              
1456             Sample Usage:
1457              
1458             $hash = $rep->get_hash_of_hashes_by_key ($table, \%params, $cols, $keycol, \%options);
1459              
1460             tbd.
1461              
1462             =cut
1463              
1464             sub get_hash_of_hashes_by_key {
1465             &App::sub_entry if ($App::trace);
1466             my ($self, $table, $params, $cols, $keycol, $options) = @_;
1467             my $hashes = $self->get_hashes($table, $params, $cols, $options);
1468             my $hash_of_hashes = {};
1469             if ($hashes && $#$hashes > -1) {
1470             foreach my $hash (@$hashes) {
1471             $hash_of_hashes->{$hash->{$keycol}} = $hash;
1472             }
1473             }
1474             &App::sub_exit($hash_of_hashes) if ($App::trace);
1475             return($hash_of_hashes);
1476             }
1477              
1478             ###########################################################################
1479             # Indexes
1480             ###########################################################################
1481              
1482             # $self->get_index(\@rows, \@key_columns, \%options);
1483             sub get_index {
1484             &App::sub_entry if ($App::trace);
1485             my ($self, $rows, $key_columns, $options) = @_;
1486              
1487             my ($key);
1488             my $index = {};
1489             my $is_array_of_arrays = ($#$rows > -1 && ref($rows->[0]) eq "ARRAY") ? 1 : 0;
1490             if ($is_array_of_arrays) {
1491             # TBD
1492             }
1493             else {
1494             foreach my $row (@$rows) {
1495             $key = join(",", @{$row}{@$key_columns});
1496             if ($index->{$key}) {
1497             push(@{$index->{$key}}, $row);
1498             }
1499             else {
1500             $index->{$key} = [ $row ];
1501             }
1502             }
1503             }
1504             &App::sub_exit($index) if ($App::trace);
1505             return($index);
1506             }
1507              
1508             # $self->get_unique_index(\@rows, \@key_columns, \%options);
1509             sub get_unique_index {
1510             &App::sub_entry if ($App::trace);
1511             my ($self, $rows, $key_columns, $options) = @_;
1512              
1513             my ($key);
1514             my $unique_index = {};
1515             my $is_array_of_arrays = ($#$rows > -1 && ref($rows->[0]) eq "ARRAY") ? 1 : 0;
1516             if ($is_array_of_arrays) {
1517             # TBD
1518             }
1519             else {
1520             foreach my $row (@$rows) {
1521             $key = join(",", @{$row}{@$key_columns});
1522             $unique_index->{$key} = $row;
1523             }
1524             }
1525             &App::sub_exit($unique_index) if ($App::trace);
1526             return($unique_index);
1527             }
1528              
1529             # $self->get_column_values(\@rows, $key_column, \%options);
1530             sub get_column_values {
1531             &App::sub_entry if ($App::trace);
1532             my ($self, $rows, $key_column, $options) = @_;
1533              
1534             my $values = [];
1535             my (%value_seen, $value);
1536             my $is_array_of_arrays = ($#$rows > -1 && ref($rows->[0]) eq "ARRAY") ? 1 : 0;
1537             if ($is_array_of_arrays) {
1538             # TBD
1539             }
1540             else {
1541             foreach my $row (@$rows) {
1542             $value = $row->{$key_column};
1543             if (!defined $value_seen{$value}) {
1544             $value_seen{$value} = 1;
1545             push(@$values, $value);
1546             }
1547             }
1548             }
1549             &App::sub_exit($values) if ($App::trace);
1550             return($values);
1551             }
1552              
1553             sub create_temporary_object_domain {
1554             &App::sub_entry if ($App::trace);
1555             my ($self, $params, $objects_by_table, $class) = @_;
1556             $params ||= {};
1557             $objects_by_table ||= {};
1558             $class ||= "App::SessionObject::RepositoryObjectDomain";
1559             my @args = (
1560             class => $class,
1561             params => $params,
1562             temporary => 1,
1563             );
1564             my $context = $self->{context};
1565             my $object_domain = $context->session_object("temporary", @args);
1566             my ($object_set, $objects);
1567             foreach my $table (keys %$objects_by_table) {
1568             $object_set = $object_domain->get_object_set($table);
1569             $objects = $objects_by_table->{$table};
1570             $object_set->set_objects($objects_by_table->{$table});
1571             }
1572             &App::sub_exit($object_domain) if ($App::trace);
1573             return($object_domain);
1574             }
1575              
1576             sub create_temporary_object_set {
1577             &App::sub_entry if ($App::trace);
1578             my ($self, $table, $params, $columns, $objects, $class) = @_;
1579             if (!$columns && $#$objects > -1) {
1580             $columns = [ sort keys %{$objects->[0]} ];
1581             }
1582             $class ||= "App::SessionObject::RepositoryObjectSet";
1583             my @args = (
1584             class => $class,
1585             table => $table,
1586             columns => $columns,
1587             temporary => 1,
1588             );
1589             my $context = $self->{context};
1590             my $object_set = $context->session_object("temporary", @args);
1591             $object_set->set_params($params);
1592             $object_set->{objects} = $objects;
1593             &App::sub_exit($object_set) if ($App::trace);
1594             return($object_set);
1595             }
1596              
1597             #############################################################################
1598             # set_hash()
1599             #############################################################################
1600              
1601             =head2 set_hash()
1602              
1603             * Signature: $nrows = $rep->set_hash ($table, $key, $cols, $values, $options);
1604             * Signature: $nrows = $rep->set_hash ($table, $params, $cols, $values, $options);
1605             * Param: $table string
1606             * Param: $key string
1607             * Param: $params undef,HASH
1608             * Param: $cols ARRAY,undef
1609             * Param: $options undef,HASH
1610             * Return: $nrows integer
1611             * Throws: App::Exception::Repository
1612             * Since: 0.50
1613              
1614             Sample Usage:
1615              
1616             $nrows = $rep->set_hash ($table, $key, \@cols, $values, \%options);
1617             $nrows = $rep->set_hash ($table, $key, undef, $values, \%options);
1618             $nrows = $rep->set_hash ($table, undef, \@cols, $values, \%options);
1619             $nrows = $rep->set_hash ($table, undef, undef, $values, \%options);
1620             $nrows = $rep->set_hash ($table, \%params, \@cols, $values, \%options);
1621             $nrows = $rep->set_hash ($table, \%params, undef, $values, \%options);
1622              
1623             tbd.
1624              
1625             =cut
1626              
1627             sub set_hash {
1628             &App::sub_entry if ($App::trace);
1629             my ($self, $table, $params, $cols, $values, $options) = @_;
1630             die "set_hash(): params undefined" if (!defined $params);
1631             die "set_hash(): not implemented";
1632             &App::sub_exit() if ($App::trace);
1633             }
1634              
1635             sub _params_to_hashref {
1636             &App::sub_entry if ($App::trace);
1637             my ($self, $table, $params) = @_;
1638              
1639             if (!defined $params || $params eq "") {
1640             $params = {};
1641             }
1642             elsif (!ref($params)) {
1643             $params = $self->_key_to_params($table,$params); # $params is undef/scalar => $key
1644             }
1645              
1646             &App::sub_exit($params) if ($App::trace);
1647             return($params);
1648             }
1649              
1650             sub _row_matches {
1651             &App::sub_entry if ($App::trace);
1652             my ($self, $row, $table, $params, $cols, $options) = @_;
1653              
1654             $options = {} if (!$options);
1655             my $table_def = $self->get_table_def($table);
1656             my $column_defs = $table_def->{column};
1657              
1658             my ($param, $column, $repop, $colidxs, $colidx, $colvalue, $paramvalue);
1659              
1660             $colidxs = $options->{cache}{colidx};
1661             if (!defined $colidxs || ! %$colidxs) {
1662             my $columns = $table_def->{columns};
1663             die "Columns not defined for table $table" if (!$columns);
1664             if (!defined $colidxs) {
1665             $colidxs = {};
1666             $options->{cache}{colidx} = $colidxs;
1667             }
1668             for ($colidx = 0; $colidx < $#$columns; $colidx++) {
1669             $column = $columns->[$colidx];
1670             $colidxs->{$column} = $colidx;
1671             }
1672             }
1673              
1674             my ($all_params_match, $param_match);
1675             $all_params_match = 1; # assume it matches
1676              
1677             foreach $param (keys %$params) {
1678             $param_match = undef;
1679             $column = $param;
1680             $colidx = $colidxs->{$column};
1681             $colvalue = (defined $colidx) ? $row->[$colidx] : undef;
1682             $repop = "eq";
1683             # check if $column contains an embedded operation, i.e. "name.eq", "name.contains"
1684             if ($param =~ /^(.*)\.([^.]+)$/) {
1685             $column = $1;
1686             $repop = $2;
1687             }
1688              
1689             if (!defined $table_def->{column}{$column}) {
1690             if ($param =~ /^begin_(.*)/) {
1691             $column = $1;
1692             $repop = "ge";
1693             }
1694             elsif ($param =~ /^end_(.*)/) {
1695             $column = $1;
1696             $repop = "le";
1697             }
1698             }
1699             next if (!defined $table_def->{column}{$column}); # skip if the column is unknown
1700              
1701             $paramvalue = $params->{$param};
1702             if (defined $paramvalue) {
1703              
1704             if ($repop eq "contains") {
1705             $param_match = ($colvalue !~ /$paramvalue/);
1706             }
1707             elsif ($repop eq "matches") {
1708             $paramvalue =~ s/\*/\.\*/g;
1709             $paramvalue =~ s/\?/\./g;
1710             $param_match = ($colvalue !~ /^$paramvalue$/);
1711             }
1712             elsif ($repop eq "in" || $repop eq "eq") {
1713             if ($paramvalue =~ /,/ && ! $table_def->{param}{$param}{no_auto_in_param}) {
1714             $param_match = (",$paramvalue," =~ /,$colvalue,/);
1715             }
1716             elsif ($paramvalue =~ /^-?[0-9]*\.?[0-9]*$/) {
1717             $param_match = ($colvalue == $paramvalue);
1718             }
1719             else {
1720             $param_match = ($colvalue eq $paramvalue);
1721             }
1722             }
1723             elsif ($repop eq "gt") {
1724             if ($paramvalue =~ /^-?[0-9]*\.?[0-9]*$/) {
1725             $param_match = ($colvalue > $paramvalue);
1726             }
1727             else {
1728             $param_match = ($colvalue gt $paramvalue);
1729             }
1730             }
1731             elsif ($repop eq "ge") {
1732             if ($paramvalue =~ /^-?[0-9]*\.?[0-9]*$/) {
1733             $param_match = ($colvalue >= $paramvalue);
1734             }
1735             else {
1736             $param_match = ($colvalue ge $paramvalue);
1737             }
1738             }
1739             elsif ($repop eq "lt") {
1740             if ($paramvalue =~ /^-?[0-9]*\.?[0-9]*$/) {
1741             $param_match = ($colvalue < $paramvalue);
1742             }
1743             else {
1744             $param_match = ($colvalue lt $paramvalue);
1745             }
1746             }
1747             elsif ($repop eq "le") {
1748             if ($paramvalue =~ /^-?[0-9]*\.?[0-9]*$/) {
1749             $param_match = ($colvalue <= $paramvalue);
1750             }
1751             else {
1752             $param_match = ($colvalue le $paramvalue);
1753             }
1754             }
1755             elsif ($repop eq "ne") {
1756             if ($paramvalue =~ /^-?[0-9]*\.?[0-9]*$/) {
1757             $param_match = ($colvalue != $paramvalue);
1758             }
1759             else {
1760             $param_match = ($colvalue ne $paramvalue);
1761             }
1762             }
1763             else {
1764             next;
1765             }
1766             }
1767             if (!$param_match) {
1768             $all_params_match = 0;
1769             last;
1770             }
1771             }
1772              
1773             &App::sub_exit($all_params_match) if ($App::trace);
1774             return($all_params_match);
1775             }
1776              
1777             sub _row_columns {
1778             &App::sub_entry if ($App::trace);
1779             my ($self, $table, $row, $cols) = @_;
1780              
1781             my ($idx, $native_idx, $column, @newrow);
1782             $#newrow = $#$cols; # preallocate
1783             my $table_def = $self->get_table_def($table);
1784             for ($idx = 0; $idx <= $#$cols; $idx++) {
1785             $column = $cols->[$idx];
1786             $native_idx = $table_def->{column}{$column}{idx};
1787             $newrow[$idx] = (defined $native_idx) ? $row->[$native_idx] : undef;
1788             }
1789              
1790             &App::sub_exit(\@newrow) if ($App::trace);
1791             return(\@newrow);
1792             }
1793              
1794             sub _get_row {
1795             &App::sub_entry if ($App::trace);
1796             my ($self, $table, $params, $cols, $options) = @_;
1797             if (!$options) {
1798             $options = { startrow => 1, endrow => 1 };
1799             }
1800             elsif (! defined $options->{endrow}) {
1801             $options = { %$options };
1802             $options->{endrow} = $options->{startrow} || 1;
1803             }
1804             my $rows = $self->_get_rows($table, $params, $cols, $options);
1805             my ($row);
1806             $row = $rows->[0] if ($#$rows > -1);
1807             &App::sub_exit($row) if ($App::trace);
1808             return($row);
1809             }
1810              
1811             sub _get_rows {
1812             &App::sub_entry if ($App::trace);
1813             my ($self, $table, $params, $cols, $options) = @_;
1814             my $all_columns = (!defined $cols);
1815             $cols = $self->{table}{$table}{columns} if ($all_columns);
1816             $params = $self->_params_to_hashref($table, $params) if (ref($params) ne "HASH");
1817             $options = {} if (!$options);
1818             my $startrow = $options->{startrow} || 0;
1819             my $endrow = $options->{endrow} || 0;
1820              
1821             my ($rows, $row, $matched_rows, $rownum);
1822             $rows = $self->{table}{$table}{data};
1823             $matched_rows = [];
1824             if ($rows && ref($rows) eq "ARRAY") {
1825             for ($rownum = 0; $rownum <= $#$rows; $rownum++) {
1826             next if ($startrow && $rownum < $startrow-1);
1827             last if ($endrow && $rownum >= $endrow);
1828             $row = $rows->[$rownum];
1829             if ($self->_row_matches($row, $table, $params, $cols, $options)) {
1830             push(@$matched_rows, $all_columns ? $row : $self->_row_columns($table, $row, $cols));
1831             }
1832             }
1833             }
1834              
1835             &App::sub_exit($matched_rows) if ($App::trace);
1836             return($matched_rows);
1837             }
1838              
1839             sub _set_rows {
1840             &App::sub_entry if ($App::trace);
1841             my ($self, $table, $params, $cols, $rows, $options) = @_;
1842             $params = $self->_params_to_hashref($table, $params) if ($params && ref($params) ne "HASH");
1843              
1844             my $table_def = $self->{table}{$table};
1845              
1846             my ($primary_key, @keycolidx, $keypos, %keypos, $keys_supplied);
1847             my ($row, $colidx, $nrows, $success);
1848             $nrows = 0;
1849             if (! defined $params) {
1850             $primary_key = $table_def->{primary_key};
1851             $primary_key = [$primary_key] if (ref($primary_key) eq "");
1852             for ($keypos = 0; $keypos <= $#$primary_key; $keypos++) {
1853             $keypos{$primary_key->[$keypos]} = $keypos;
1854             }
1855             $keys_supplied = 0;
1856             for ($colidx = 0; $colidx <= $#$cols; $colidx++) {
1857             $keypos = $keypos{$cols->[$colidx]};
1858             if (defined $keypos) {
1859             $keycolidx[$keypos] = $colidx;
1860             $keys_supplied++;
1861             }
1862             }
1863             die "Tried to set_rows() and the primary key is not among the columns" if ($keys_supplied != $#$primary_key+1);
1864             foreach $row (@$rows) {
1865             $success = $self->_update($table, \@keycolidx, $cols, $row, $options);
1866             if ($success == 0 && $options->{create}) {
1867             $success = $self->_insert_row($table, $cols, $row, $options);
1868             }
1869             $nrows += $success;
1870             }
1871             }
1872             elsif (ref($params) eq "ARRAY") {
1873             # $curr_rows = $self->_get_rows($table, $params, $cols, $options);
1874             }
1875             else { # i.e. "HASH"
1876             # $curr_rows = $self->_get_rows($table, $params, $cols, $options);
1877             }
1878             &App::sub_exit($nrows) if ($App::trace);
1879             return($nrows);
1880             }
1881              
1882             sub _set_row {
1883             &App::sub_entry if ($App::trace);
1884             my ($self, $table, $params, $cols, $row, $options) = @_;
1885             $options = {} if (!$options);
1886              
1887             $params = $self->_params_to_hashref($table, $params) if ($params && ref($params) ne "HASH");
1888             my $nrows = $self->_update($table, $params, $cols, $row, $options);
1889             if ($nrows == 0 && $options->{create}) {
1890             $nrows = $self->_insert_row($table, $cols, $row, $options);
1891             }
1892              
1893             &App::sub_exit($nrows) if ($App::trace);
1894             return($nrows);
1895             }
1896              
1897             sub _key_to_values {
1898             &App::sub_entry if ($App::trace);
1899             my ($self, $key) = @_;
1900             # TODO: eventually, I should handle escaping of "," and nonprintable data
1901             my @values = split(/,/, $key);
1902             &App::sub_exit(@values) if ($App::trace);
1903             return(@values);
1904             }
1905              
1906             sub _values_to_key {
1907             &App::sub_entry if ($App::trace);
1908             my ($self, @values) = @_;
1909             # TODO: eventually, I should handle unescaping of "," and nonprintable data
1910             my $retval = join(",",@values);
1911             &App::sub_exit($retval) if ($App::trace);
1912             return($retval);
1913             }
1914              
1915             sub _key_to_params {
1916             &App::sub_entry if ($App::trace);
1917             my ($self, $table, $key) = @_;
1918             my %params = ();
1919             my $primary_key = $self->{table}{$table}{primary_key};
1920             die "ERROR: primary key is not defined for table [$table]\n (configure attribute {Repository}{$self->{name}}{table}{$table}{primary_key})\n"
1921             if (!defined $primary_key);
1922             $primary_key = $primary_key->[0] if (ref($primary_key) eq "ARRAY" && $#$primary_key == 0);
1923             if (ref($primary_key)) {
1924             my ($colnum, @values);
1925             if (!defined $key || $key eq "") {
1926             for ($colnum = 0; $colnum <= $#$primary_key; $colnum++) {
1927             $params{$primary_key->[$colnum]} = undef;
1928             }
1929             }
1930             else {
1931             @values = $self->_key_to_values($key);
1932             die "ERROR: values [$key] do not match columns [" . join(",",@$primary_key) . "] in primary key"
1933             if ($#$primary_key != $#values);
1934             for ($colnum = 0; $colnum <= $#$primary_key; $colnum++) {
1935             $params{$primary_key->[$colnum]} = $values[$colnum];
1936             }
1937             }
1938             $params{"_order"} = $primary_key;
1939             }
1940             else {
1941             $params{$primary_key} = $key;
1942             }
1943             &App::sub_exit(\%params) if ($App::trace);
1944             return(\%params);
1945             }
1946              
1947             # $ok = $rep->insert_row ($table, \@cols, \@row);
1948             # $ok = $rep->insert_row ($table, \%obj);
1949             sub insert_row {
1950             &App::sub_entry if ($App::trace);
1951             my ($self, $table, $cols, $row, $options) = @_;
1952             my ($retval);
1953             my $repname = $self->{table}{$table}{repository};
1954             my $realtable = $self->{table}{$table}{table} || $table;
1955             if (defined $repname && $repname ne $self->{name}) {
1956             my $rep = $self->{context}->repository($repname);
1957             $retval = $rep->insert_row($realtable, $cols, $row, $options);
1958             }
1959             elsif (defined $realtable && $realtable ne $table) {
1960             $retval = $self->insert_row($realtable, $cols, $row, $options);
1961             }
1962             else {
1963             $self->_load_table_metadata($table) if (! defined $self->{table}{$table}{loaded});
1964             my ($hash, $columns);
1965             my $ref = ref($cols);
1966             if ($ref && $ref ne "ARRAY") {
1967             $hash = $cols; # a hashref was passed in instead of cols/row
1968             my $table_def = $self->{table}{$table};
1969             $columns = [];
1970             foreach my $col (@{$table_def->{columns}}) {
1971             if (exists $hash->{$col}) {
1972             push(@$columns, $col);
1973             }
1974             }
1975             }
1976             elsif (ref($row) eq "HASH") {
1977             $hash = $row;
1978             if (ref($cols) eq "ARRAY") {
1979             $columns = $cols;
1980             }
1981             else {
1982             my $table_def = $self->{table}{$table};
1983             $columns = [];
1984             foreach my $col (@{$table_def->{columns}}) {
1985             if (exists $hash->{$col}) {
1986             push(@$columns, $col);
1987             }
1988             }
1989             }
1990             }
1991             if ($hash) {
1992             my (@cols, @row);
1993             foreach my $col (@$columns) {
1994             if (exists $hash->{$col}) {
1995             push(@cols, $col);
1996             push(@row, $hash->{$col});
1997             }
1998             }
1999             $retval = $self->_insert_row($table, \@cols, \@row, $options);
2000             }
2001             else {
2002             $retval = $self->_insert_row($table, $cols, $row, $options);
2003             }
2004             }
2005             &App::sub_exit($retval) if ($App::trace);
2006             $retval;
2007             }
2008              
2009             # NOTE: insert() is a synonym for insert_row()
2010             sub insert {
2011             &App::sub_entry if ($App::trace);
2012             my ($self, $table, $cols, $row, $options) = @_;
2013             my $retval = $self->insert_row($table, $cols, $row, $options);
2014             &App::sub_exit($retval) if ($App::trace);
2015             $retval;
2016             }
2017              
2018             # NOTE: This might be optimized somehow in the future so that I don't
2019             # need to do a select after insert. However, there might be defaults
2020             # set in the database that I don't know about, and I want them to be
2021             # reflected in the returned object.
2022             # $object = $rep->new_object($table, \@cols, \@row);
2023             # $object = $rep->new_object($table, \%obj_values);
2024             # $object = $rep->new_object($table, $col, $value);
2025             # $object = $rep->new_object($table);
2026             sub new_object {
2027             &App::sub_entry if ($App::trace);
2028             my ($self, $table, $cols, $row, $options) = @_;
2029              
2030             my $table_def = $self->get_table_def($table);
2031              
2032             my $ref = ref($cols);
2033             my ($object);
2034             if ($ref && $ref eq "ARRAY") {
2035             $object = {};
2036             for (my $i = 0; $i <= $#$cols; $i++) {
2037             $object->{$cols->[$i]} = $row->[$i];
2038             }
2039             }
2040             elsif ($ref) {
2041             $object = { %$cols };
2042             }
2043             elsif ($cols) {
2044             $object = { $cols => $row };
2045             }
2046             else {
2047             $object = {};
2048             }
2049              
2050             my $class = $table_def->{class} || "App::RepositoryObject";
2051             # if $class is an ARRAY ref, we need to examine the qualifier(s) to determine the class
2052             $class = $self->_get_qualified_class($class, $object) if (ref($class));
2053             App->use($class);
2054             bless $object, $class;
2055             $object->_init();
2056             $self->_check_default_and_required_fields($object);
2057              
2058             if (!$options->{temp}) {
2059             my $retval = $self->insert_row($table, $object, undef, $options);
2060             die "new($table) unable to create a new row" if (!$retval);
2061             my $params = $self->_last_inserted_id();
2062             if (!$params) {
2063             $params = {};
2064             foreach my $col (keys %$object) {
2065             $params->{$col . ".eq"} = $object->{$col};
2066             }
2067             }
2068             $object = $self->get_object($table, $params, undef, $options);
2069             }
2070              
2071             &App::sub_exit($object) if ($App::trace);
2072             $object;
2073             }
2074              
2075             sub _check_default_and_required_fields {
2076             &App::sub_entry if ($App::trace);
2077             my ($self, $table, $hash) = @_;
2078             my $table_def = $self->get_table_def($table);
2079             my $column_defs = $table_def->{column};
2080             if ($column_defs) {
2081             foreach my $column (keys %$column_defs) {
2082             if (!defined $hash->{$column}) {
2083             if (defined $column_defs->{$column}{default}) {
2084             $hash->{$column} = $column_defs->{$column}{default};
2085             }
2086             elsif (defined $column_defs->{$column}{not_null}) {
2087             die "Illegal object value for $table: $column cannot be NULL (i.e. undef)";
2088             }
2089             }
2090             }
2091             }
2092             my $primary_key = $table_def->{primary_key};
2093             if ($primary_key) {
2094             # Watch out for auto-generated primary keys. It's OK for them to be NULL.
2095             #if ($#$primary_key > 0) {
2096             # foreach my $column (@$primary_key) {
2097             # if (!defined $hash->{$column}) {
2098             # die "Illegal object value for $table: $column cannot be NULL because it exists in the primary key";
2099             # }
2100             # }
2101             #}
2102             }
2103             my $alternate_keys = $table_def->{alternate_key};
2104             if ($alternate_keys) {
2105             foreach my $alternate_key (@$alternate_keys) {
2106             foreach my $column (@$alternate_key) {
2107             if (!defined $hash->{$column}) {
2108             die "Illegal object value for $table: $column cannot be NULL because it exists in an alternate key";
2109             }
2110             }
2111             }
2112             }
2113             &App::sub_exit() if ($App::trace);
2114             }
2115              
2116             sub last_inserted_id {
2117             my ($self, $table) = @_;
2118             my $repname = $self->{table}{$table}{repository};
2119             my $realtable = $self->{table}{$table}{table} || $table;
2120             my ($id);
2121             if (defined $repname && $repname ne $self->{name}) {
2122             my $rep = $self->{context}->repository($repname);
2123             $id = $rep->last_inserted_id($realtable);
2124             }
2125             elsif (defined $realtable && $realtable ne $table) {
2126             $id = $self->last_inserted_id($realtable);
2127             }
2128             else {
2129             $id = $self->_last_inserted_id($table);
2130             }
2131             return($id);
2132             }
2133              
2134             sub _last_inserted_id {
2135             my ($self, $table) = @_;
2136             return(undef); # sorry. maybe some subclass will know how to do this.
2137             }
2138              
2139             # $nrows = $rep->insert_rows ($table, \@cols, \@rows);
2140             sub insert_rows {
2141             &App::sub_entry if ($App::trace);
2142             my ($self, $table, $cols, $rows, $options) = @_;
2143             my $repname = $self->{table}{$table}{repository};
2144             my $realtable = $self->{table}{$table}{table} || $table;
2145             my ($nrows);
2146             if (defined $repname && $repname ne $self->{name}) {
2147             my $rep = $self->{context}->repository($repname);
2148             $nrows = $rep->insert_rows($realtable, $cols, $rows, $options);
2149             }
2150             elsif (defined $realtable && $realtable ne $table) {
2151             $nrows = $self->insert_rows($realtable, $cols, $rows, $options);
2152             }
2153             else {
2154             $self->_load_table_metadata($table) if (! defined $self->{table}{$table}{loaded});
2155             my ($hashes, $hash, $columns);
2156             if (ref($cols) eq "ARRAY" && ref($cols->[0]) eq "HASH") {
2157             $hashes = $cols; # an array of hashrefs was passed in instead of cols/rows
2158             $hash = $hashes->[0];
2159             my $table_def = $self->{table}{$table};
2160             $columns = $table_def->{columns};
2161             $columns = [ keys %$hash ] if (!$columns);
2162             }
2163             elsif (ref($rows) eq "ARRAY" && ref($rows->[0]) eq "HASH") {
2164             $hashes = $rows;
2165             $hash = $hashes->[0];
2166             if (ref($cols) eq "ARRAY") {
2167             $columns = $cols;
2168             }
2169             else {
2170             my $table_def = $self->{table}{$table};
2171             $columns = $table_def->{columns};
2172             $columns = [ keys %$hash ] if (!$columns || $#$columns == -1);
2173             }
2174             }
2175             if ($hashes) {
2176             my (@cols, @rows, $col, $row);
2177             foreach $col (@$columns) {
2178             if (exists $hash->{$col}) {
2179             push(@cols, $col);
2180             }
2181             }
2182             foreach $hash (@$hashes) {
2183             $row = [];
2184             foreach $col (@cols) {
2185             push(@$row, $hash->{$col});
2186             }
2187             push(@rows, $row);
2188             }
2189             $nrows = $self->_insert_rows($table, \@cols, \@rows, $options);
2190             }
2191             else {
2192             $nrows = $self->_insert_rows($table, $cols, $rows, $options);
2193             }
2194             }
2195             &App::sub_exit($nrows) if ($App::trace);
2196             $nrows;
2197             }
2198              
2199             sub delete {
2200             &App::sub_entry if ($App::trace);
2201             my ($self, $table, $params, $cols, $row, $options) = @_;
2202             die "delete(): params undefined" if (!defined $params);
2203             my $repname = $self->{table}{$table}{repository};
2204             my $realtable = $self->{table}{$table}{table} || $table;
2205             my ($retval);
2206             if (defined $repname && $repname ne $self->{name}) {
2207             my $rep = $self->{context}->repository($repname);
2208             $retval = $rep->delete($realtable, $cols, $row, $options);
2209             }
2210             elsif (defined $realtable && $realtable ne $table) {
2211             $retval = $self->delete($realtable, $cols, $row, $options);
2212             }
2213             else {
2214             $self->_load_table_metadata($table) if (! defined $self->{table}{$table}{loaded});
2215             $retval = $self->_delete($table,$params,$cols,$row,$options);
2216             }
2217             &App::sub_exit($retval) if ($App::trace);
2218             return($retval);
2219             }
2220              
2221             sub update {
2222             &App::sub_entry if ($App::trace);
2223             my ($self, $table, $params, $cols, $row, $options) = @_;
2224             die "update(): params undefined" if (!defined $params);
2225             my $repname = $self->{table}{$table}{repository};
2226             my $realtable = $self->{table}{$table}{table} || $table;
2227             my ($retval);
2228             if (defined $repname && $repname ne $self->{name}) {
2229             my $rep = $self->{context}->repository($repname);
2230             $retval = $rep->update($realtable, $cols, $row, $options);
2231             }
2232             elsif (defined $realtable && $realtable ne $table) {
2233             $retval = $self->update($realtable, $cols, $row, $options);
2234             }
2235             else {
2236             $self->_load_table_metadata($table) if (! defined $self->{table}{$table}{loaded});
2237             $retval = $self->_update($table,$params,$cols,$row,$options);
2238             }
2239             &App::sub_exit($retval) if ($App::trace);
2240             return($retval);
2241             }
2242              
2243             sub _insert_row {
2244             &App::sub_entry if ($App::trace);
2245             my ($self, $table, $cols, $row, $options) = @_;
2246             $self->{error} = "";
2247             my $retval = 0;
2248             die "_insert_row(): not yet implemented";
2249             &App::sub_exit($retval) if ($App::trace);
2250             return($retval);
2251             }
2252              
2253             sub _insert_rows {
2254             &App::sub_entry if ($App::trace);
2255             my ($self, $table, $cols, $rows, $options) = @_;
2256             $self->{error} = "";
2257             my $retval = 0;
2258             die "_insert_rows(): not yet implemented";
2259             &App::sub_exit($retval) if ($App::trace);
2260             return($retval);
2261             }
2262              
2263             sub _delete {
2264             &App::sub_entry if ($App::trace);
2265             my ($self, $table, $params, $cols, $row, $options) = @_;
2266              
2267             $self->{error} = "";
2268             my $retval = 0;
2269             die "_delete(): not yet implemented";
2270              
2271             &App::sub_exit($retval) if ($App::trace);
2272             return($retval);
2273             }
2274              
2275             # $nrows = $rep->_update($table, \%params, \@cols, \@row, \%options);
2276             # $nrows = $rep->_update($table, \@keycolidx, \@cols, \@row, \%options);
2277             # $nrows = $rep->_update($table, \@paramcols, \@cols, \@row, \%options);
2278             # $nrows = $rep->_update($table, $key, \@cols, \@row, \%options);
2279             # $nrows = $rep->_update($table, undef, \@cols, \@row, \%options);
2280             sub _update {
2281             &App::sub_entry if ($App::trace);
2282             my ($self, $table, $params, $cols, $row, $options) = @_;
2283              
2284             $self->{error} = "";
2285             my $retval = 0;
2286              
2287             my $get_options = { cache => {}, };
2288             my $rows = $self->_get_rows($table, $params, undef, $get_options);
2289             my $colidxs = $get_options->{cache}{colidx};
2290             my ($idx, $colidx, $column, $tablerow);
2291             foreach $tablerow (@$rows) {
2292             for ($idx = 0; $idx <= $#$cols; $idx++) {
2293             $column = $cols->[$idx];
2294             $colidx = $colidxs->{$column};
2295             if (defined $colidx) {
2296             $tablerow->[$colidx] = $row->[$idx];
2297             }
2298             }
2299             }
2300             $retval = $#$rows + 1;
2301              
2302             &App::sub_exit($retval) if ($App::trace);
2303             return($retval);
2304             }
2305              
2306             #############################################################################
2307             # format_repdate()
2308             #############################################################################
2309              
2310             =head2 format_repdate()
2311              
2312             * Signature: $date = $repository->format_repdate($freeform_date);
2313             * Param: $freeform_date string
2314             * Return: $date string
2315             * Throws: App::Exception::Repository
2316             * Since: 0.01
2317              
2318             Sample Usage:
2319              
2320             foreach $freeform_date ("1/2/01", "1-Jan-2003", "january 13, 2000",
2321             "2000/1/5", "15 jan 99") {
2322             print "$freeform_date: ", $rep->format_repdate($freeform_date), "\n";
2323             }
2324              
2325             The format_repdate() method takes a free-form date string (such as a human
2326             might type into a form field) using many varieties of upper and lower case,
2327             punctuation, and ordering, and turns it into a date in canonical
2328             YYYY-MM-DD form for storage in the repository.
2329              
2330             =cut
2331              
2332             # $repdate = $rep->format_repdate($date_string); # free-form date string as entered by a person
2333             sub format_repdate {
2334             my ($self, $datetext) = @_;
2335             my ($monthtext, $mon, $day, $year, %mon, $date);
2336             if ($datetext =~ /\b([a-zA-Z]+)[- ]+([0-9]{1,2})[- ,]+([0-9]{2,4})\b/) { # i.e. December 31, 1999, 9-march-01
2337             $monthtext = $1;
2338             $day = $2;
2339             $year = $3;
2340             }
2341             elsif ($datetext =~ /\b([0-9]{1,2})[- ]+([a-zA-Z]+)[- ]+([0-9]{2,4})\b/) { # i.e. 31-Dec-1999, 9 march 01
2342             $day = $1;
2343             $monthtext = $2;
2344             $year = $3;
2345             }
2346             elsif ($datetext =~ /\b([0-9]{4})([0-9]{2})([0-9]{2})\b/) { # i.e. 19991231, 20010309
2347             $year = $1;
2348             $mon = $2;
2349             $day = $3;
2350             }
2351             elsif ($datetext =~ m!\b([0-9]{4})[- /]+([0-9]{1,2})[- /]+([0-9]{1,2})\b!) { # i.e. 1999-12-31, 2001/3/09
2352             $year = $1;
2353             $mon = $2;
2354             $day = $3;
2355             }
2356             elsif ($datetext =~ m!\b([0-9]{1,2})[- /]+([0-9]{1,2})[- /]+([0-9]{2,4})\b!) { # i.e. 12/31/1999, 3-9-01
2357             $mon = $1;
2358             $day = $2;
2359             $year = $3;
2360             }
2361             else {
2362             return("");
2363             }
2364             if ($monthtext) {
2365             if ($monthtext =~ /^jan/i) { $mon = 1; }
2366             elsif ($monthtext =~ /^feb/i) { $mon = 2; }
2367             elsif ($monthtext =~ /^mar/i) { $mon = 3; }
2368             elsif ($monthtext =~ /^apr/i) { $mon = 4; }
2369             elsif ($monthtext =~ /^may/i) { $mon = 5; }
2370             elsif ($monthtext =~ /^jun/i) { $mon = 6; }
2371             elsif ($monthtext =~ /^jul/i) { $mon = 7; }
2372             elsif ($monthtext =~ /^aug/i) { $mon = 8; }
2373             elsif ($monthtext =~ /^sep/i) { $mon = 9; }
2374             elsif ($monthtext =~ /^oct/i) { $mon = 10; }
2375             elsif ($monthtext =~ /^nov/i) { $mon = 11; }
2376             elsif ($monthtext =~ /^dec/i) { $mon = 12; }
2377             else { return(""); }
2378             }
2379             if ($year < 0) { return(""); }
2380             elsif ($year < 50) { $year += 2000; }
2381             elsif ($year < 100) { $year += 1900; }
2382             elsif ($year < 1000) { return(""); }
2383             return("") if ($mon > 12);
2384             return("") if ($day > 31);
2385             sprintf("%04d-%02d-%02d",$year,$mon,$day);
2386             }
2387              
2388             #############################################################################
2389             # get_type_names()
2390             #############################################################################
2391              
2392             =head2 get_type_names()
2393              
2394             * Signature: $typenames = $repository->get_type_names();
2395             * Param: void
2396             * Return: $typenames []
2397             * Throws: App::Exception::Repository
2398             * Since: 0.01
2399              
2400             Sample Usage:
2401              
2402             $typenames = $rep->get_type_names();
2403             print join(",", @$typenames), "\n";
2404              
2405             Returns the standard set of type names for columns in a repository.
2406             These are perl-friendly type names which are useful to do data validation.
2407              
2408             * string
2409             * text
2410             * integer
2411             * float
2412             * date
2413             * time
2414             * datetime
2415             * binary
2416              
2417             =cut
2418              
2419             sub get_type_names {
2420             my ($self) = @_;
2421             $self->{types};
2422             }
2423              
2424             #############################################################################
2425             # get_type_labels()
2426             #############################################################################
2427              
2428             =head2 get_type_labels()
2429              
2430             * Signature: $typelabels = $repository->get_type_labels();
2431             * Param: void
2432             * Return: $typelabels {}
2433             * Throws: App::Exception::Repository
2434             * Since: 0.01
2435              
2436             Sample Usage:
2437              
2438             $typelabels = $rep->get_type_labels();
2439             foreach (sort keys %$typelabels) {
2440             print "$_ => $typelabels->{$_}\n";
2441             }
2442              
2443             Returns a hash of all of the repository types and the labels
2444             which should be used when displaying them to the user through
2445             the user interface.
2446              
2447             * string => "Characters"
2448             * text => "Text"
2449             * integer => "Integer"
2450             * float => "Number"
2451             * date => "Date"
2452             * time => "Time"
2453             * datetime => "Date and Time"
2454             * binary => "Binary Data"
2455              
2456             =cut
2457              
2458             sub get_type_labels {
2459             my ($self) = @_;
2460             $self->{type_labels};
2461             }
2462              
2463             #############################################################################
2464             # get_type_def()
2465             #############################################################################
2466              
2467             =head2 get_type_def()
2468              
2469             * Signature: $typedef = $rep->get_type_def($typename);
2470             * Param: $typename string
2471             * Return: $typedef {}
2472             * Throws: App::Exception::Repository
2473             * Since: 0.01
2474              
2475             Sample Usage:
2476              
2477             $typedef = $rep->get_type_def("string");
2478             print "$typedef->{name} $typedef->{label}\n";
2479              
2480             Gets a reference to a "type definition", which allows you to access all
2481             of the attributes of the requested type
2482             (currently only "name" and "label").
2483              
2484             =cut
2485              
2486             sub get_type_def {
2487             my ($self, $type) = @_;
2488             $self->{type}{$type};
2489             }
2490              
2491             #############################################################################
2492             # get_table_names()
2493             #############################################################################
2494              
2495             =head2 get_table_names()
2496              
2497             * Signature: $tablenames = $rep->get_table_names();
2498             * Param: void
2499             * Return: $tablenames []
2500             * Throws: App::Exception::Repository
2501             * Since: 0.01
2502              
2503             Sample Usage:
2504              
2505             $tablenames = $rep->get_table_names();
2506             print join(",", @$tablenames), "\n";
2507              
2508             Returns the set of table names in the repository.
2509              
2510             =cut
2511              
2512             sub get_table_names {
2513             my ($self) = @_;
2514             $self->{tables};
2515             }
2516              
2517             #############################################################################
2518             # get_table_labels()
2519             #############################################################################
2520              
2521             =head2 get_table_labels()
2522              
2523             * Signature: $tablelabels = $rep->get_table_labels();
2524             * Param: void
2525             * Return: $tablelabels {}
2526             * Throws: App::Exception::Repository
2527             * Since: 0.01
2528              
2529             Sample Usage:
2530              
2531             $tablelabels = $rep->get_table_labels();
2532             foreach (sort keys %$tablelabels) {
2533             print "$_ => $tablelabels->{$_}\n";
2534             }
2535              
2536             Returns a hash of all of the tables and the labels
2537             which should be used when displaying them to the user through
2538             the user interface.
2539              
2540             =cut
2541              
2542             sub get_table_labels {
2543             my ($self) = @_;
2544             $self->{table_labels};
2545             }
2546              
2547             #############################################################################
2548             # get_table_def()
2549             #############################################################################
2550              
2551             =head2 get_table_def()
2552              
2553             * Signature: $table_def = $rep->get_table_def($tablename);
2554             * Param: $tablename string
2555             * Return: $table_def {}
2556             * Throws: App::Exception::Repository
2557             * Since: 0.01
2558              
2559             Sample Usage:
2560              
2561             $table_def = $rep->get_table_def($tablename);
2562             print "$table_def->{name} $table_def->{label}\n";
2563              
2564             Gets a reference to a "table definition", which allows you to access all
2565             of the attributes of the requested table.
2566             By default, this is only "name" and "label".
2567             However, for various types of repositories, there may be additional
2568             attributes for a table.
2569              
2570             =cut
2571              
2572             sub get_table_def {
2573             &App::sub_entry if ($App::trace);
2574             my ($self, $table, $options) = @_;
2575             my $repname = $self->{table}{$table}{repository};
2576             my $realtable = $self->{table}{$table}{table} || $table;
2577             my ($table_def);
2578             if (defined $repname && $repname ne $self->{name}) {
2579             my $rep = $self->{context}->repository($repname);
2580             $table_def = $rep->get_table_def($realtable, $options);
2581             }
2582             elsif (defined $realtable && $realtable ne $table) {
2583             $table_def = $self->get_table_def($realtable, $options);
2584             }
2585             else {
2586             $self->_load_table_metadata($table) if (! defined $self->{table}{$table}{loaded});
2587             if ($options->{table_def}) {
2588             $table_def = $options->{table_def};
2589             App::Reference->overlay($table_def, $self->{table}{$table});
2590             }
2591             else {
2592             $table_def = $self->{table}{$table};
2593             }
2594             }
2595             &App::sub_exit($table_def) if ($App::trace);
2596             return($table_def);
2597             }
2598              
2599             #############################################################################
2600             # get_column_names()
2601             #############################################################################
2602              
2603             =head2 get_column_names()
2604              
2605             * Signature: $columnnames = $rep->get_column_names($tablename);
2606             * Param: $tablename string
2607             * Return: $columnnames []
2608             * Throws: App::Exception::Repository
2609             * Since: 0.01
2610              
2611             Sample Usage:
2612              
2613             $columnnames = $rep->get_column_names($tablename);
2614             print join(",", @$columnnames), "\n";
2615              
2616             Returns the set of column names for the requested table in a repository.
2617              
2618             =cut
2619              
2620             sub get_column_names {
2621             my ($self, $table) = @_;
2622             my $table_def = $self->get_table_def($table);
2623             my $columns = $table_def->{columns};
2624             return($columns);
2625             }
2626              
2627             sub get_phys_column_names {
2628             my ($self, $table) = @_;
2629             my $table_def = $self->get_table_def($table);
2630             my $columns = $table_def->{phys_columns};
2631             return($columns);
2632             }
2633              
2634             #############################################################################
2635             # get_column_labels()
2636             #############################################################################
2637              
2638             =head2 get_column_labels()
2639              
2640             * Signature: $columnlabels = $rep->get_column_labels($tablename);
2641             * Param: $tablename string
2642             * Return: $columnlabels {}
2643             * Throws: App::Exception::Repository
2644             * Since: 0.01
2645              
2646             Sample Usage:
2647              
2648             $columnlabels = $rep->get_column_labels($tablename);
2649             foreach (sort keys %$columnlabels) {
2650             print "$_ => $columnlabels->{$_}\n";
2651             }
2652              
2653             Returns a hash of all of the column names and the labels
2654             which should be used when displaying them to the user through
2655             the user interface.
2656              
2657             =cut
2658              
2659             sub get_column_labels {
2660             my ($self, $table, $labelcolumn) = @_;
2661             my $table_def = $self->get_table_def($table);
2662             my ($labels);
2663             if (!$labelcolumn) {
2664             $labels = $table_def->{column_labels};
2665             }
2666             else {
2667             $labels = {};
2668             my $column_defs = $table_def->{column};
2669             foreach my $column (keys %$column_defs) {
2670             $labels->{$column} = $column_defs->{$column}{$labelcolumn};
2671             }
2672             }
2673             return($labels);
2674             }
2675              
2676             #############################################################################
2677             # get_column_def()
2678             #############################################################################
2679              
2680             =head2 get_column_def()
2681              
2682             * Signature: $column_def = $rep->get_column_def($tablename,$columnname);
2683             * Param: $tablename string
2684             * Param: $columnname string
2685             * Return: $column_def {}
2686             * Throws: App::Exception::Repository
2687             * Since: 0.01
2688              
2689             Sample Usage:
2690              
2691             $column_def = $rep->get_column_def($tablename,$columnname);
2692             print "$column_def->{name} $column_def->{label} $column_def->{type}\n";
2693              
2694             Gets a reference to a "column definition", which allows you to access all
2695             of the attributes of the requested column.
2696              
2697             By default, this is only "name", "label", and "type".
2698             However, for various types of repositories, there may be additional
2699             attributes for a column.
2700              
2701             =cut
2702              
2703             # $column = $rep->get_column_def($tablename,$columnname); # print "%$column\n";
2704             sub get_column_def {
2705             my ($self, $table, $column) = @_;
2706             my $table_def = $self->get_table_def($table);
2707             my $column_def = $table_def->{column}{$column};
2708             return($column_def);
2709             }
2710              
2711             sub get_relationship_name {
2712             my ($self, $table, $column, $table_def) = @_;
2713              
2714             $table_def = $self->get_table_def($table) if (!$table_def);
2715             my $column_def = $table_def->{column}{$column};
2716             my $relationship_name = $column_def->{relationship_name};
2717              
2718             if (!$relationship_name) {
2719             # Determine the order in which we will process relationships
2720             my $relationship_defs = $table_def->{relationship};
2721             my $relationships = $table_def->{relationships}; # maybe it's explicit
2722             if (!$relationships && $relationship_defs) { # otherwise ...
2723             $relationships = [
2724             reverse
2725             sort {
2726             ($relationship_defs->{$a}{qualifying_keys} ? 0 : 1) <=> ($relationship_defs->{$b}{qualifying_keys} ? 0 : 1) ||
2727             $a cmp $b
2728             }
2729             keys %$relationship_defs
2730             ]; # *reverse* sort (x_y before x)
2731             }
2732             $relationships = [] if (!$relationships);
2733              
2734             my ($rel_name);
2735             for (my $rel = 0; $rel <= $#$relationships; $rel++) {
2736             $rel_name = $relationships->[$rel];
2737             if (! $column_def->{is_key} && $column =~ /^${rel_name}_/) {
2738             $relationship_name = $rel_name;
2739             last;
2740             }
2741             }
2742             }
2743              
2744             return($relationship_name);
2745             }
2746              
2747             #############################################################################
2748             # METHODS
2749             #############################################################################
2750              
2751             =head1 Methods: Transaction Control
2752              
2753             =cut
2754              
2755             #############################################################################
2756             # begin_work()
2757             #############################################################################
2758              
2759             =head2 begin_work()
2760              
2761             * Signature: $rep->begin_work();
2762             * Param: void
2763             * Return: void
2764             * Throws: App::Exception::Repository
2765             * Since: 0.01
2766              
2767             Sample Usage:
2768              
2769             $rep->begin_work();
2770              
2771             =cut
2772              
2773             sub begin_work {
2774             my $self = shift;
2775             }
2776              
2777             #############################################################################
2778             # commit()
2779             #############################################################################
2780              
2781             =head2 commit()
2782              
2783             * Signature: $rep->commit();
2784             * Param: void
2785             * Return: void
2786             * Throws: App::Exception::Repository
2787             * Since: 0.01
2788              
2789             Sample Usage:
2790              
2791             $rep->commit();
2792              
2793             =cut
2794              
2795             sub commit {
2796             my $self = shift;
2797             my ($table, $rows, $rowidx, $rowchange, $change, $colref, $prikeyidx, $nrows);
2798              
2799             my ($table_def);
2800              
2801             $nrows = 0;
2802             foreach $table (@{$self->{tables}}) {
2803             $table_def = $self->get_table_def($table);
2804              
2805             $rowchange = $table_def->{cache}{rowchange};
2806              
2807             if ($rowchange && $#$rowchange > -1) {
2808              
2809             $prikeyidx = $table_def->{prikeyidx};
2810             if (!$prikeyidx) {
2811             $self->{context}->add_message("Table '$table' not configured for updating ('prikey' not set in commit())");
2812             next;
2813             }
2814              
2815             $rows = $table_def->{cache}{rows};
2816             $colref = $table_def->{cache}{columns};
2817              
2818             for ($rowidx = 0; $rowidx <= $#$rows; $rowidx++) {
2819             $change = $rowchange->[$rowidx];
2820             next if (!defined $change);
2821             if ($change eq "U") {
2822             $self->_update($table, $colref, $rows->[$rowidx], $prikeyidx);
2823             $rowchange->[$rowidx] = "";
2824             $nrows++;
2825             }
2826             elsif ($change eq "I") {
2827             $self->insert_row($table, $colref, $rows->[$rowidx]);
2828             $rowchange->[$rowidx] = "";
2829             $nrows++;
2830             }
2831             if ($App::DEBUG && $self->{context}->dbg(7)) {
2832             my $context = $self->{context};
2833             $context->dbgprint("rep->commit(): [$self->{sql}]");
2834             $context->dbgprint(" [", join("|",@{$rows->[$rowidx]}), "]");
2835             }
2836             }
2837             }
2838             }
2839             $self->{context}->dbgprint("rep->commit(): nrows=$nrows")
2840             if ($App::DEBUG && $self->{context}->dbg(2));
2841             }
2842              
2843             #############################################################################
2844             # rollback()
2845             #############################################################################
2846              
2847             =head2 rollback()
2848              
2849             * Signature: $rep->rollback();
2850             * Param: void
2851             * Return: void
2852             * Throws: App::Exception::Repository
2853             * Since: 0.01
2854              
2855             Sample Usage:
2856              
2857             $rep->rollback();
2858              
2859             =cut
2860              
2861             sub rollback {
2862             my $self = shift;
2863             }
2864              
2865             #############################################################################
2866             # METHODS
2867             #############################################################################
2868              
2869             =head1 Methods: Import/Export Data From File
2870              
2871             =cut
2872              
2873             #############################################################################
2874             # import_rows()
2875             #############################################################################
2876              
2877             =head2 import_rows()
2878              
2879             * Signature: $rep->import_rows($table, $columns, $file);
2880             * Signature: $rep->import_rows($table, $columns, $file, $options);
2881             * Param: $table string
2882             * Param: $columns ARRAY names of columns of the fields in the file
2883             * Param: $file string
2884             * Param: $options named
2885             * Param: replace boolean rows should replace existing rows based on unique indexes
2886             * Param: field_sep char character which separates the fields in the file (can by "\t")
2887             * Param: field_quote char character which optionally encloses the fields in the file (i.e. '"')
2888             * Param: field_escape char character which escapes the quote chars within quotes (i.e. "\")
2889             * Return: void
2890             * Throws: App::Exception::Repository
2891             * Since: 0.01
2892              
2893             Sample Usage:
2894              
2895             $rep->import_rows("usr","usr.dat");
2896              
2897             # root:x:0:0:root:/root:/bin/bash
2898             $rep->import_rows("usr",
2899             [ "username", "password", "uid", "gid", "comment", "home_directory", "shell" ],
2900             "/etc/passwd" ,
2901             { field_sep => ":", });
2902              
2903             =cut
2904              
2905             sub import_rows {
2906             &App::sub_entry if ($App::trace);
2907             my ($self, $table, $columns, $file, $options) = @_;
2908             $columns = $self->_get_default_columns($table) if (!$columns);
2909             my $field_sep = $options->{field_sep} || ",";
2910             my $field_quote = $options->{field_quote};
2911             my $field_escape = $options->{field_escape};
2912              
2913             open(App::Repository::DBI::FILE, "< $file") || die "Unable to open $file for reading: $!";
2914             my (@row, $quoted_field_regexp, $field_regexp);
2915             while () {
2916             chomp;
2917             if ($field_quote) {
2918             @row = ();
2919             # TODO: use the _read_rows_from_file() method
2920             # TODO: incorporate escaping
2921             $field_regexp = "$field_sep?$field_quote([^$field_quote]*)$field_quote";
2922             $quoted_field_regexp = "$field_sep?([^$field_sep]*)";
2923             while ($_) {
2924             if ($_ =~ s/^$quoted_field_regexp//) {
2925             push(@row, $1);
2926             }
2927             elsif ($_ =~ s/^$field_regexp//) {
2928             push(@row, $1);
2929             }
2930             else {
2931             die "Imported data doesn't match quoted or unquoted field [$_]";
2932             }
2933             }
2934             }
2935             else {
2936             @row = split(/$field_sep/);
2937             }
2938             # TODO: use insert_rows() instead of insert_row()
2939             $self->insert_row($table, $columns, \@row);
2940             }
2941             close(App::Repository::DBI::FILE);
2942              
2943             &App::sub_exit() if ($App::trace);
2944             }
2945              
2946             #############################################################################
2947             # export_rows()
2948             #############################################################################
2949              
2950             =head2 export_rows()
2951              
2952             * Signature: $rep->export_rows($table, $columns, $file);
2953             * Signature: $rep->export_rows($table, $columns, $file, $options);
2954             * Param: $table string
2955             * Param: $file string
2956             * Param: $options named
2957             * Param: columns ARRAY names of columns of the fields in the file
2958             * Param: replace boolean rows should replace existing rows based on unique indexes
2959             * Param: field_sep char character which separates the fields in the file (can by "\t")
2960             * Param: field_quote char character which optionally encloses the fields in the file (i.e. '"')
2961             * Param: field_escape char character which escapes the quote chars within quotes (i.e. "\")
2962             * Return: void
2963             * Throws: App::Exception::Repository
2964             * Since: 0.01
2965              
2966             Sample Usage:
2967              
2968             $rep->export_rows("usr","usr.dat");
2969              
2970             # root:x:0:0:root:/root:/bin/bash
2971             $rep->export_rows("usr", "passwd.dat" ,{
2972             field_sep => ":",
2973             columns => [ "username", "password", "uid", "gid", "comment", "home_directory", "shell" ],
2974             });
2975              
2976             =cut
2977              
2978             sub export_rows {
2979             &App::sub_entry if ($App::trace);
2980             my ($self, $table, $columns, $file, $options) = @_;
2981             $columns = $self->_get_default_columns($table) if (!$columns);
2982             my $rows = $self->get_rows($table, {}, $columns);
2983             my $field_sep = $options->{field_sep} || ",";
2984             my $field_quote = $options->{field_quote};
2985             my $field_escape = $options->{field_escape};
2986              
2987             open(App::Repository::DBI::FILE, "> $file") || die "Unable to open $file for writing: $!";
2988             my ($i, $value);
2989             foreach my $row (@$rows) {
2990             if ($field_quote) {
2991             for ($i = 0; $i <= $#$row; $i++) {
2992             print App::Repository::DBI::FILE $field_sep if ($i > 0);
2993             $value = $row->[$i];
2994             if ($value =~ /$field_sep/) {
2995             if ($field_escape) {
2996             $value =~ s/$field_escape/$field_escape$field_escape/g;
2997             $value =~ s/$field_quote/$field_escape$field_quote/g;
2998             }
2999             print App::Repository::DBI::FILE $field_quote, $value, $field_quote;
3000             }
3001             else {
3002             print App::Repository::DBI::FILE $value;
3003             }
3004             }
3005             }
3006             else {
3007             print App::Repository::DBI::FILE join($field_sep, @$row), "\n";
3008             }
3009             }
3010             close(App::Repository::DBI::FILE);
3011              
3012             &App::sub_exit() if ($App::trace);
3013             }
3014              
3015             sub _read_rows_from_file {
3016             &App::sub_entry if ($App::trace);
3017             my ($self, $fh, $cols, $options) = @_;
3018             my $maxrows = $options->{maxrows};
3019             my $null_value = $options->{null_value};
3020             $null_value = '\N' if (!defined $null_value);
3021             my $field_sep = $options->{field_sep} || ",";
3022             my $field_quote = $options->{field_quote} || "";
3023             my $field_escape = $options->{field_escape} || "";
3024             die "TODO: field_escape not yet implemented" if ($field_escape);
3025             my $fieldsep_regexp = ($field_sep eq "|") ? '\|' : $field_sep;
3026             my $quoted_field_regexp = "$field_sep?$field_quote([^$field_quote]*)$field_quote";
3027             my $field_regexp = "$field_sep?([^$field_sep]*)";
3028             my $num_cols = $#$cols + 1;
3029             my $rows_read = 0;
3030             my $rows = [];
3031             my ($num_values_read, $line, $line_remainder, $row);
3032             while (<$fh>) {
3033             chomp;
3034             $line = $_;
3035             if ($line) {
3036             if (!$field_quote && !$field_escape) {
3037             $row = [ map { $_ eq $null_value ? undef : $_ } split(/$fieldsep_regexp/, $line) ];
3038             $num_values_read = $#$row + 1;
3039             }
3040             else {
3041             $num_values_read = 0;
3042             $line_remainder = $line;
3043             $row = [];
3044             while ($line_remainder) {
3045             if ($line_remainder =~ s/^$quoted_field_regexp//) {
3046             push(@$row, $1 eq $null_value ? undef : $1);
3047             }
3048             elsif ($line_remainder =~ s/^$field_regexp//) {
3049             push(@$row, $1 eq $null_value ? undef : $1);
3050             }
3051             else {
3052             die "Imported data [$line] doesn't match quoted or unquoted field at [$line_remainder]";
3053             }
3054             }
3055             }
3056             die "In imported data [$line], num values on line [$num_values_read] != num columns expected [$num_cols]"
3057             if ($num_values_read != $num_cols);
3058             push(@$rows, $row);
3059             $rows_read ++;
3060             if ($maxrows && $rows_read >= $maxrows) {
3061             last;
3062             }
3063             }
3064             }
3065             &App::sub_exit($rows) if ($App::trace);
3066             return($rows);
3067             }
3068              
3069             #############################################################################
3070             # METHODS
3071             #############################################################################
3072              
3073             =head1 Methods: Locking (Concurrency Management)
3074              
3075             =cut
3076              
3077             # this is a write lock for the table
3078             sub _lock_table {
3079             &App::sub_entry if ($App::trace);
3080             my ($self, $table) = @_;
3081             if (! $self->{locked}) { # I have locked it myself, so I don't need to again
3082             my ($name, $dbname, $context, $rlock);
3083             $name = $self->{name};
3084             $dbname = $self->{dbname};
3085             $context = $self->{context};
3086             $rlock = $context->resource_locker($name); # get the one that corresponds to this repository
3087             $rlock->lock("db.$dbname.$table");
3088             $self->{locked} = 1;
3089             }
3090             &App::sub_exit() if ($App::trace);
3091             }
3092              
3093             # unlocks the write lock for the table
3094             sub _unlock_table {
3095             &App::sub_entry if ($App::trace);
3096             my ($self, $table) = @_;
3097             if ($self->{locked}) {
3098             my ($name, $dbname, $context, $rlock);
3099             $name = $self->{name};
3100             $dbname = $self->{dbname};
3101             $context = $self->{context};
3102             $rlock = $context->resource_locker($name); # get the one that corresponds to this repository
3103             $rlock->unlock("db.$dbname.$table");
3104             delete $self->{locked};
3105             }
3106             &App::sub_exit() if ($App::trace);
3107             }
3108              
3109             #############################################################################
3110             # METHODS
3111             #############################################################################
3112              
3113             =head1 Methods: Miscellaneous
3114              
3115             =cut
3116              
3117             #############################################################################
3118             # summarize_rows()
3119             #############################################################################
3120              
3121             =head2 summarize_rows()
3122              
3123             * Signature: $summarized_rows = $rep->summarize_rows($table, $rows, $columns, $summary_keys, $options);
3124             * Param: $table string
3125             * Param: $rows [][]
3126             * Param: $columns []
3127             * Param: $summary_keys []
3128             * Param: $formulas {}
3129             * Return: $summarized_rows []
3130             * Throws: App::Exception::Repository
3131             * Since: 0.01
3132              
3133             Sample Usage:
3134              
3135             @rows = (
3136             [ 5, "Jim", "Green", 13.5, 320, ],
3137             [ 3, "Bob", "Green", 4.2, 230, ],
3138             [ 9, "Ken", "Green", 27.4, 170, ],
3139             [ 2, "Kim", "Blue", 11.7, 440, ],
3140             [ 7, "Jan", "Blue", 55.1, 90, ],
3141             [ 1, "Ben", "Blue", 22.6, 195, ],
3142             );
3143             @columns = ( "id", "name", "team", "rating", "score" );
3144             @summary_keys = ( "team" );
3145              
3146             $summarized_rows = $rep->summarize_rows(\@rows, \@columns, \@summary_keys, \%formulas);
3147              
3148             @rows = (
3149             { id=>5, name=>"Jim", team=>"Green", rating=>13.5, score=>320, },
3150             { id=>3, name=>"Bob", team=>"Green", rating=> 4.2, score=>230, },
3151             { id=>9, name=>"Ken", team=>"Green", rating=>27.4, score=>170, },
3152             { id=>2, name=>"Kim", team=>"Blue", rating=>11.7, score=>440, },
3153             { id=>7, name=>"Jan", team=>"Blue", rating=>55.1, score=> 90, },
3154             { id=>1, name=>"Ben", team=>"Blue", rating=>22.6, score=>195, },
3155             );
3156             @columns = ( "rating", "score" ); # summarize a subset of the columns
3157             @summary_keys = ( "team" );
3158             %options = (
3159             ext_summaries => \%summaries, # extended summaries
3160             ext_summary_columns => [ "rating", "score", "team", ], # optional
3161             ext_summary_functions => { # optional
3162             sum => 1,
3163             count => 1,
3164             sum_sq => 1,
3165             distinct => 1,
3166             min => 1,
3167             max => 1,
3168             average => 1, # requires sum, count
3169             median => 1, # requires distinct
3170             mode => 1, # requires min, max
3171             stddev => 1, # requires sum, sum_sq, count
3172             },
3173             );
3174              
3175             # returns the "natural" summaries
3176             $summarized_rows = $rep->summarize_rows(\@rows, \@columns, \@summary_keys, \%options);
3177              
3178             =cut
3179              
3180             sub summarize_rows {
3181             &App::sub_entry if ($App::trace);
3182             my ($self, $table, $rows, $columns, $summary_keys, $options) = @_;
3183              
3184             #print STDERR "summarize_rows($table, ..., ..., [@$summary_keys], ...)\n";
3185             #foreach my $row (@$rows) {
3186             # print STDERR "summarize_rows(IN) {", join("|", %$row), "}\n";
3187             #}
3188              
3189             $summary_keys = [] if (!$summary_keys);
3190              
3191             my $table_def = $self->get_table_def($table);
3192             my $column_defs = $table_def->{column};
3193              
3194             my ($ext_summaries, $ext_column_summary, $ext_summary_columns, $ext_summary_functions);
3195             $ext_summaries = $options->{ext_summaries};
3196             if ($ext_summaries) {
3197             $ext_summary_columns = $options->{ext_summary_columns};
3198             $ext_summary_functions = $options->{ext_summary_functions} || { # do all of them
3199             count => 1,
3200             distinct => 1,
3201             sum => 1,
3202             sum_sq => 1,
3203             min => 1,
3204             max => 1,
3205             average => 1, # requires sum, count
3206             median => 1, # requires distinct
3207             mode => 1, # requires min, max
3208             stddev => 1, # requires sum, sum_sq, count
3209             };
3210             }
3211              
3212             my (@summary_rows, $summary_row, %summary_row);
3213             my ($key, $row, $hash_rows, $hash_row, $i, $rowidx, $colidx, $column, $value);
3214             my $row_type = "ARRAY";
3215              
3216             if (!$rows || $#$rows == -1) {
3217             # do nothing
3218             }
3219             else {
3220             $row_type = (ref($rows->[0]) eq "ARRAY") ? "ARRAY" : "HASH";
3221              
3222             # if we are summarizing HASH rows, convert to ARRAY
3223             if ($row_type eq "HASH") {
3224             $row = $rows->[0];
3225             if (!$columns) {
3226             $columns = [ ];
3227             foreach $column (sort keys %$row) {
3228             push(@$columns, $column) if (defined $column_defs->{$column});
3229             }
3230             }
3231             $hash_rows = $rows;
3232             $rows = [];
3233             foreach $hash_row (@$hash_rows) {
3234             $row = [ @{$hash_row}{@$columns} ];
3235             push(@$rows, $row);
3236             }
3237             }
3238              
3239             # find the indexes for each of the columns
3240             my (%colidx);
3241             for ($i = 0; $i <= $#$columns; $i++) {
3242             $colidx{$columns->[$i]} = $i;
3243             }
3244              
3245             # get the indexes for the summary keys
3246             my (@summary_key_idx, @summary_key_values);
3247             if ($summary_keys) {
3248             for ($i = 0; $i <= $#$summary_keys; $i++) {
3249             $colidx = $colidx{$summary_keys->[$i]};
3250             push(@summary_key_idx, $colidx) if (defined $colidx);
3251             }
3252             }
3253              
3254             my $alternate_aggregate = []; # assume there are no alternate aggregates
3255             my ($alternate_aggregate_key_idx, $alternate_aggregate_row_idx, $alternate_aggregate_key);
3256              
3257             # determine which columns should be summable and which have expressions
3258             my $sum_column_idx = [];
3259             my $expr_column_idx = [];
3260             my $contains_expr = 0;
3261             $row = $rows->[0];
3262             my ($rel_name, %rel_aggregate, $key_column);
3263             for ($i = 0; $i <= $#$columns; $i++) {
3264             $column = $columns->[$i];
3265             $value = $row->[$i];
3266             $rel_name = $self->get_relationship_name($table, $column, $table_def);
3267             if ($rel_name && !defined $rel_aggregate{$rel_name} && $table_def->{relationship}{$rel_name}{qualifying_keys}) {
3268             # TODO: should this block be made a separate method?
3269             my $key_idx = [];
3270             my %key_idx_used = ();
3271             foreach $key_column (@$summary_keys) {
3272             push(@$key_idx, $colidx{$key_column});
3273             $key_idx_used{$key_column} = 1;
3274             }
3275             for (my $j = 0; $j <= $#$columns; $j++) {
3276             $key_column = $columns->[$j];
3277             if ($column_defs->{$key_column}{is_key} &&
3278             $table_def->{relationship}{$rel_name}{qualifying_keys}{$key_column} &&
3279             !$key_idx_used{$key_column}) {
3280             push(@$key_idx, $j);
3281             $key_idx_used{$key_column} = 1;
3282             }
3283             }
3284             $rel_aggregate{$rel_name} = {
3285             key_idx => $key_idx,
3286             row_idx => {},
3287             };
3288             }
3289             if ($column_defs->{$column}{expr}) {
3290             push(@$expr_column_idx, $i);
3291             $contains_expr = 1;
3292             }
3293             elsif ($column_defs->{$column}{is_key}) {
3294             # do nothing
3295             }
3296             elsif ($column_defs->{$column}{type} && $column_defs->{$column}{type} =~ /^(integer|float)$/) {
3297             push(@$sum_column_idx, $i);
3298             $alternate_aggregate->[$i] = $rel_aggregate{$rel_name};
3299             }
3300             elsif (defined $column_defs->{$column}{default} && $column_defs->{$column}{default} =~ /^-?[0-9\.]+$/) {
3301             push(@$sum_column_idx, $i);
3302             $alternate_aggregate->[$i] = $rel_aggregate{$rel_name};
3303             }
3304             elsif (defined $value && $value =~ /^-?[0-9\.]+$/) {
3305             push(@$sum_column_idx, $i);
3306             $alternate_aggregate->[$i] = $rel_aggregate{$rel_name};
3307             }
3308             }
3309              
3310             # accumulate the sums of the summable columns
3311             for ($rowidx = 0; $rowidx <= $#$rows; $rowidx++) {
3312             $row = $rows->[$rowidx];
3313             $key = ($#summary_key_idx > -1) ? join(",", @{$row}[@summary_key_idx]) : "";
3314             $summary_row = $summary_row{$key};
3315             if (!$summary_row) {
3316             $summary_row = [];
3317             if ($#summary_key_idx > -1) {
3318             foreach $i (@summary_key_idx) {
3319             $summary_row->[$i] = $row->[$i];
3320             }
3321             }
3322             foreach $i (@$sum_column_idx) {
3323             $summary_row->[$i] = undef;
3324             }
3325             $summary_row{$key} = $summary_row;
3326             push(@summary_key_values, $key);
3327             }
3328             foreach $i (@$sum_column_idx) {
3329             if (defined $row->[$i]) {
3330             if ($alternate_aggregate->[$i]) {
3331             $alternate_aggregate_key_idx = $alternate_aggregate->[$i]{key_idx};
3332             $alternate_aggregate_key = join(",", @{$row}[@$alternate_aggregate_key_idx]);
3333             $alternate_aggregate_row_idx = $alternate_aggregate->[$i]{row_idx};
3334             if (! defined $alternate_aggregate_row_idx->{$alternate_aggregate_key}) {
3335             $alternate_aggregate_row_idx->{$alternate_aggregate_key} = $rowidx;
3336             }
3337             if ($alternate_aggregate_row_idx->{$alternate_aggregate_key} == $rowidx) {
3338             if (defined $row->[$i]) {
3339             if (defined $summary_row->[$i]) {
3340             $summary_row->[$i] += $row->[$i];
3341             }
3342             else {
3343             $summary_row->[$i] = $row->[$i];
3344             }
3345             }
3346             }
3347             }
3348             else {
3349             if (defined $row->[$i]) {
3350             if (defined $summary_row->[$i]) {
3351             $summary_row->[$i] += $row->[$i];
3352             }
3353             else {
3354             $summary_row->[$i] = $row->[$i];
3355             }
3356             }
3357             }
3358             }
3359             }
3360             if ($ext_summaries) {
3361             foreach $i (@$sum_column_idx, @$expr_column_idx) {
3362             $column = $columns->[$i];
3363             $value = $row->[$i];
3364             if (defined $value) {
3365             $ext_column_summary = $ext_summaries->{$column};
3366             if (!$ext_column_summary) {
3367             $ext_column_summary = {};
3368             $ext_summaries->{$column} = $ext_column_summary;
3369             }
3370             $ext_column_summary->{count} ++;
3371             $ext_column_summary->{distinct}{$value} ++;
3372             $ext_column_summary->{sum} += $value;
3373             $ext_column_summary->{sum_sq} += $value*$value;
3374             if (!defined $ext_column_summary->{min} || $ext_column_summary->{min} > $value) {
3375             $ext_column_summary->{min} = $value;
3376             }
3377             if (!defined $ext_column_summary->{max} || $ext_column_summary->{max} < $value) {
3378             $ext_column_summary->{max} = $value;
3379             }
3380             }
3381             }
3382             }
3383             }
3384             if ($ext_summaries) {
3385             my ($count, $sum, $sum_sq, $num, $median_count, $median, $mode_count, $mode);
3386             foreach $i (@$sum_column_idx, @$expr_column_idx) {
3387             $column = $columns->[$i];
3388             $ext_column_summary = $ext_summaries->{$column};
3389             if ($ext_column_summary && $ext_column_summary->{count}) {
3390             $ext_column_summary->{average} = $ext_column_summary->{sum}/$ext_column_summary->{count};
3391             $count = $ext_column_summary->{count};
3392             $mode = undef;
3393             $mode_count = 0;
3394             if ($count > 1) {
3395             $sum = $ext_column_summary->{sum};
3396             $sum_sq = $ext_column_summary->{sum_sq};
3397             $value = ($count * $sum_sq - $sum * $sum)/($count * ($count - 1));
3398             if ($value > 0) {
3399             $ext_column_summary->{stddev} = sqrt($value);
3400             }
3401             }
3402             if ($count % 2 == 1) {
3403             $num = 0;
3404             $median_count = ($count - 1)/2 + 1;
3405             foreach $value (sort { $a <=> $b } keys %{$ext_column_summary->{distinct}}) {
3406             $num += $ext_column_summary->{distinct}{$value};
3407             if ($count > $mode_count || ($count == $mode_count && $num <= $median_count)) {
3408             $mode = $value;
3409             $mode_count = $count;
3410             }
3411             if ($num >= $median_count) {
3412             $ext_column_summary->{median} = $value;
3413             last;
3414             }
3415             }
3416             }
3417             else {
3418             $num = 0;
3419             $median_count = $count/2;
3420             $median = undef;
3421             foreach $value (sort { $a <=> $b } keys %{$ext_column_summary->{distinct}}) {
3422             $num += $ext_column_summary->{distinct}{$value};
3423             if ($count > $mode_count || ($count == $mode_count && $num <= $median_count)) {
3424             $mode = $value;
3425             $mode_count = $count;
3426             }
3427             if (!defined $median) {
3428             if ($num >= $median_count + 1) {
3429             $ext_column_summary->{median} = $value;
3430             last;
3431             }
3432             elsif ($num == $median_count) {
3433             $median = $value;
3434             }
3435             }
3436             else {
3437             if ($num >= $median_count + 1) {
3438             $ext_column_summary->{median} = ($median + $value)/2;
3439             last;
3440             }
3441             }
3442             }
3443             }
3444             $ext_column_summary->{mode} = $mode;
3445             }
3446             }
3447             }
3448              
3449             # put the summarized rows in the results array
3450             foreach $key (@summary_key_values) {
3451             push(@summary_rows, $summary_row{$key});
3452             }
3453              
3454             # evaluate the expressions of the summarized rows (if they exist)
3455             if ($contains_expr) {
3456             my $params = {};
3457             $self->evaluate_expressions($table, $params, $columns, \@summary_rows, $options);
3458             }
3459              
3460             # if we started out summarizing HASH rows, convert back from ARRAY to HASH
3461             if ($row_type eq "HASH") {
3462             $rows = [ @summary_rows ];
3463             $hash_rows = [];
3464             @summary_rows = ();
3465             foreach $row (@$rows) {
3466             $hash_row = {};
3467             for ($i = 0; $i <= $#$columns; $i++) {
3468             $hash_row->{$columns->[$i]} = $row->[$i];
3469             }
3470             push(@summary_rows, $hash_row);
3471             }
3472             }
3473             }
3474              
3475             #foreach my $row (@summary_rows) {
3476             # print STDERR "summarize_rows(OUT) {", join("|", %$row), "}\n";
3477             #}
3478            
3479             &App::sub_exit(\@summary_rows) if ($App::trace);
3480             return(\@summary_rows);
3481             }
3482              
3483             #############################################################################
3484             # sort()
3485             #############################################################################
3486              
3487             =head2 sort()
3488              
3489             * Signature: $sorted_rows = $rep->sort($rows, $sortkeys);
3490             * Signature: $sorted_rows = $rep->sort($rows, $sortkeys, $sorttype);
3491             * Signature: $sorted_rows = $rep->sort($rows, $sortkeys, $sorttype, $sortdir);
3492             * Param: $rows [][]
3493             * Param: $sortkeys []
3494             * Param: $sorttype []
3495             * Param: $sortdir []
3496             * Return: $sorted_rows []
3497             * Throws: App::Exception::Repository
3498             * Since: 0.01
3499              
3500             Sample Usage: (to sort arrayrefs)
3501              
3502             @rows = (
3503             [ 5, "Jim", "Green", 13.5, 320, ],
3504             [ 3, "Bob", "Green", 4.2, 230, ],
3505             [ 9, "Ken", "Green", 27.4, 170, ],
3506             [ 2, "Kim", "Blue", 11.7, 440, ],
3507             [ 7, "Jan", "Blue", 55.1, 90, ],
3508             [ 1, "Ben", "Blue", 22.6, 195, ],
3509             );
3510             # @columns = ( "id", "name", "team", "rating", "score" ); # not needed
3511             @sortkeys = ( 2, 4 ); # "team", "score" (descending)
3512             @sorttype = ( "C", "N" ); # Character, Numeric
3513             @sortdir = ( "asc", "desc" ); # Ascending, Descending
3514             $sorted_rows = $rep->sort(\@rows, \@sortkeys, \@sorttype, \@sortdir);
3515              
3516             OR (to sort hashrefs)
3517              
3518             @rows = (
3519             { id => 5, name => "Jim", team => "Green", rating => 13.5, score => 320, },
3520             { id => 3, name => "Bob", team => "Green", rating => 4.2, score => 230, },
3521             { id => 9, name => "Ken", team => "Green", rating => 27.4, score => 170, },
3522             { id => 2, name => "Kim", team => "Blue", rating => 11.7, score => 440, },
3523             { id => 7, name => "Jan", team => "Blue", rating => 55.1, score => 90, },
3524             { id => 1, name => "Ben", team => "Blue", rating => 22.6, score => 195, },
3525             );
3526             # @columns = ( "id", "name", "team", "rating", "score" ); # not needed
3527             @sortkeys = ( "team", "score" ); # "team", "score" (descending)
3528             @sorttype = ( "C", "N" ); # Character, Numeric
3529             @sortdir = ( "asc", "desc" ); # Ascending, Descending
3530             $sorted_rows = $rep->sort(\@rows, \@sortkeys, \@sorttype, \@sortdir);
3531              
3532             =cut
3533              
3534             sub sort {
3535             &App::sub_entry if ($App::trace);
3536             my ($self, $rows, $sortkeys, $sorttype, $sortdir) = @_;
3537              
3538             @App::Repository::sort_keys = @$sortkeys;
3539             @App::Repository::sort_types = ($sorttype ? @$sorttype : ());
3540             @App::Repository::sort_dirs = ($sortdir ? @$sortdir : ());
3541              
3542             my ($sorted_rows);
3543             if ($rows && ref($rows) eq "ARRAY" && $#$rows > 0) {
3544             if (ref($rows->[0]) eq "ARRAY") {
3545             $sorted_rows = [ sort rows_by_indexed_values @$rows ];
3546             }
3547             else {
3548             $sorted_rows = [ sort hashes_by_indexed_values @$rows ];
3549             }
3550             }
3551             else {
3552             $sorted_rows = $rows;
3553             }
3554             &App::sub_exit($sorted_rows) if ($App::trace);
3555             return($sorted_rows);
3556             }
3557              
3558             #############################################################################
3559             # evaluate_expressions()
3560             #############################################################################
3561              
3562             =head2 evaluate_expressions()
3563              
3564             * Signature: $nrows = $rep->evaluate_expressions($table, $params, $cols, $rows, $options);
3565             * Param: $table string
3566             * Param: $params HASH,scalar
3567             * Param: $cols ARRAY
3568             * Param: $rows ARRAY
3569             * Param: $options undef,HASH
3570             * Return: $nrows integer
3571             * Throws: App::Exception::Repository
3572             * Since: 0.50
3573              
3574             Sample Usage:
3575              
3576             $rep->evaluate_expressions($table, $params, \@cols, $rows, \%options);
3577              
3578             tbd.
3579              
3580             =cut
3581              
3582             sub evaluate_expressions {
3583             &App::sub_entry if ($App::trace);
3584             my ($self, $table, $params, $cols, $rows, $options) = @_;
3585             $options ||= {};
3586             my %options = %$options;
3587             my $table_def = $self->get_table_def($table);
3588             my $column_defs = $table_def->{column};
3589             my (@expr_col_idx, @expr_col, $col, %colidx);
3590              
3591             for (my $i = 0; $i <= $#$cols; $i++) {
3592             $col = $cols->[$i];
3593             $colidx{$col} = $i;
3594             if ($column_defs->{$col}{expr}) {
3595             push(@expr_col, $col);
3596             push(@expr_col_idx, $i);
3597             }
3598             }
3599              
3600             if ($#expr_col > -1) {
3601             foreach my $row (@$rows) {
3602             for (my $i = 0; $i <= $#expr_col; $i++) {
3603             $col = $expr_col[$i];
3604             $row->[$expr_col_idx[$i]] = $self->evaluate_expression($column_defs->{$col}{expr}, $row, \%colidx, $column_defs);
3605             }
3606             }
3607             }
3608              
3609             &App::sub_exit() if ($App::trace);
3610             }
3611              
3612             sub evaluate_expression {
3613             &App::sub_entry if ($App::trace);
3614             my ($self, $expr, $values, $validx, $column_defs) = @_;
3615             my $OPTIONAL_DEFAULT = '(?::([-0-9\.]+))?';
3616              
3617             my $value = $expr;
3618             if ($values) {
3619             my ($col, $val, $idx, $default);
3620             if (ref($values) eq "ARRAY") {
3621             while ($value =~ /\{([^{}:]+)$OPTIONAL_DEFAULT\}/) {
3622             $col = $1;
3623             $default = $2;
3624             $idx = $validx->{$col};
3625             if (defined $idx) {
3626             $val = $values->[$idx];
3627             if (!defined $val) {
3628             if ($column_defs->{$col}{expr}) {
3629             $val = $self->evaluate_expression($column_defs->{$col}{expr}, $values, $validx, $column_defs);
3630             $values->[$idx] = $val;
3631             }
3632             if (!defined $val) {
3633             $val = ($default ne "") ? $default : $column_defs->{$col}{default};
3634             }
3635             }
3636             $val = "undef" if (!defined $val);
3637             $val = "($val)" if ($val =~ /[-\+\*\/]/);
3638             }
3639             else {
3640             $val = "undef";
3641             }
3642             $value =~ s/\{$col$OPTIONAL_DEFAULT\}/$val/g || last;
3643             }
3644             }
3645             else {
3646             while ($value =~ /\{([^{}:]+)$OPTIONAL_DEFAULT\}/) {
3647             $col = $1;
3648             $default = $2;
3649             $val = $values->{$col};
3650             $val = App::Reference->get($col, $values) if (!defined $val && $col =~ /[\[\]\{\}\.]/);
3651             if (!defined $val) {
3652             if ($column_defs->{$col}{expr}) {
3653             $val = $self->evaluate_expression($column_defs->{$col}{expr}, $values, $validx, $column_defs);
3654             $values->{$col} = $val;
3655             }
3656             if (!defined $val) {
3657             $val = ($default ne "") ? $default : $column_defs->{$col}{default};
3658             }
3659             }
3660             $val = "undef" if (!defined $val);
3661             $val = "($val)" if ($val =~ /[-\+\*\/]/);
3662             $value =~ s/\{$col$OPTIONAL_DEFAULT\}/$val/g || last;
3663             }
3664             }
3665             }
3666             $value = $self->evaluate_constant_expression($value);
3667              
3668             &App::sub_exit($value) if ($App::trace);
3669             return($value);
3670             }
3671              
3672             sub evaluate_constant_expression {
3673             &App::sub_entry if ($App::trace);
3674             my ($self, $value) = @_;
3675             my $NUM = '-?[0-9\.]+(?:[eE][+-]?[0-9]+)?';
3676              
3677             if (!defined $value || $value =~ /^$NUM$/) {
3678             # do nothing
3679             }
3680             else {
3681             my ($func, $found, $val, $val2, @vals);
3682             while ($value =~ /([a-z_]+)\(([^()]*)\)/) {
3683             #print "EXPR: BEFORE [$value] (func subst)\n";
3684             $func = $1;
3685             $found = $2;
3686             $val = undef;
3687             @vals = ();
3688             foreach $val2 (split(/,/, $found)) {
3689             push(@vals, $self->evaluate_constant_expression($val2));
3690             }
3691             if ($func eq "max") {
3692             $val = undef;
3693             foreach $val2 (@vals) {
3694             if ($val2 =~ /^$NUM$/) {
3695             if (! defined $val || $val2 > $val) {
3696             $val = $val2;
3697             }
3698             }
3699             else {
3700             $val = undef;
3701             last;
3702             }
3703             }
3704             }
3705             elsif ($func eq "min") {
3706             $val = undef;
3707             foreach $val2 (@vals) {
3708             if ($val2 =~ /^$NUM$/) {
3709             if (! defined $val || $val2 < $val) {
3710             $val = $val2;
3711             }
3712             }
3713             else {
3714             $val = undef;
3715             last;
3716             }
3717             }
3718             }
3719             elsif ($func eq "abs") {
3720             if ($#vals == 0 && $vals[0] =~ /^$NUM$/) {
3721             $val = $vals[0] < 0 ? -$vals[0] : $vals[0];
3722             }
3723             }
3724             elsif ($func eq "ifnull") {
3725             if ($#vals == 1) {
3726             if (! defined $vals[0] || $vals[0] eq "undef") {
3727             $val = $vals[1];
3728             }
3729             else {
3730             $val = $vals[0];
3731             }
3732             }
3733             }
3734             elsif ($func eq "if") {
3735             if ($#vals == 2) {
3736             $val = $vals[0] ? $vals[1] : $vals[2];
3737             }
3738             }
3739             elsif ($func eq "case") {
3740             if (! defined $vals[0]) {
3741             for (my $i = 1; $i <= $#vals; $i += 2) {
3742             if ($i == $#vals) { # the default
3743             $val = $vals[$i];
3744             last;
3745             }
3746             elsif (! defined $vals[$i]) {
3747             $val = $vals[$i+1];
3748             last;
3749             }
3750             }
3751             }
3752             else {
3753             for (my $i = 1; $i <= $#vals; $i += 2) {
3754             if ($i == $#vals) { # the default
3755             $val = $vals[$i];
3756             last;
3757             }
3758             elsif (defined $vals[$i] && $vals[$i] == $vals[0]) {
3759             $val = $vals[$i+1];
3760             last;
3761             }
3762             }
3763             }
3764             }
3765             else { # unknown function name
3766             $val = "undef";
3767             }
3768             $val = "undef" if (! defined $val);
3769             $value =~ s/$func\([^\(\)]*\)/$val/;
3770             #print "EXPR: AFTER [$value] (func subst)\n";
3771             }
3772             while ($value =~ /\(([^()]*)\)/) {
3773             #print "EXPR: BEFORE $value\n";
3774             $found = $1;
3775             $val = $self->evaluate_constant_expression($found);
3776             $val = "undef" if (! defined $val);
3777             $value =~ s/\([^\(\)]*\)/$val/;
3778             #print "EXPR: AFTER $value\n";
3779             }
3780             if ($value =~ m!^[-\+\*/0-9\.\s]+$!) { # all numeric expression
3781             $value =~ s/\s+//g;
3782             }
3783             while ($value =~ s~($NUM)\s*([\*/])\s*($NUM)~(!defined $1 || !defined $3) ? "undef" : (($2 eq "*") ? ($1 * $3) : (($3 && $3 != 0.0) ? ($1 / $3) : "undef"))~e) {
3784             #print "EXPR: $1 $2 $3 = $value\n";
3785             # nothing else needed
3786             }
3787             while ($value =~ s~($NUM)\s*([\+-])\s*($NUM)~(!defined $1 || !defined $3) ? "undef" : (($2 eq "+") ? ($1 + $3) : ($1 - $3))~e) {
3788             #print "EXPR: $1 $2 $3 = $value\n";
3789             # nothing else needed
3790             }
3791             while ($value =~ s~($NUM)\s*(!=|=|<|<=|>|>=)\s*($NUM)~(!defined $1 || !defined $3) ? "undef" : $self->evaluate_boolean_expression($1,$2,$3)~e) {
3792             #print "EXPR: $1 $2 $3 = $value\n";
3793             # nothing else needed
3794             }
3795             $value = undef if ($value =~ /undef/);
3796             }
3797              
3798             &App::sub_exit($value) if ($App::trace);
3799             return($value);
3800             }
3801              
3802             sub evaluate_boolean_expression {
3803             &App::sub_entry if ($App::trace);
3804             my ($self, $lhs, $op, $rhs) = @_;
3805             my $value = 0;
3806             if (defined $lhs && defined $rhs) {
3807             if ($op eq "=") {
3808             $value = ($lhs == $rhs) ? 1 : 0;
3809             }
3810             elsif ($op eq "!=") {
3811             $value = ($lhs != $rhs) ? 1 : 0;
3812             }
3813             elsif ($op eq "<=") {
3814             $value = ($lhs <= $rhs) ? 1 : 0;
3815             }
3816             elsif ($op eq "<") {
3817             $value = ($lhs < $rhs) ? 1 : 0;
3818             }
3819             elsif ($op eq ">=") {
3820             $value = ($lhs >= $rhs) ? 1 : 0;
3821             }
3822             elsif ($op eq ">") {
3823             $value = ($lhs > $rhs) ? 1 : 0;
3824             }
3825             }
3826             &App::sub_exit($value) if ($App::trace);
3827             return($value);
3828             }
3829              
3830             #############################################################################
3831             # serial()
3832             #############################################################################
3833              
3834             =head2 serial()
3835              
3836             * Signature: $serial_num = $repository->serial($category);
3837             * Param: $category string
3838             * Return: $serial_num integer
3839             * Throws: App::Exception::Repository
3840             * Since: 0.01
3841              
3842             Sample Usage:
3843              
3844             $serial_num = $repository->serial($category);
3845              
3846             =cut
3847              
3848             my %serial_number;
3849             sub serial {
3850             &App::sub_entry if ($App::trace);
3851             my ($self, $category) = @_;
3852             my ($serial);
3853             if (!defined $serial_number{$category}) {
3854             $serial_number{$category} = 1;
3855             $serial = 1;
3856             }
3857             else {
3858             $serial = ++$serial_number{$category};
3859             }
3860             &App::sub_exit($serial) if ($App::trace);
3861             return($serial);
3862             }
3863              
3864             #############################################################################
3865             # METHODS
3866             #############################################################################
3867              
3868             =head1 Methods: Metadata
3869              
3870             =cut
3871              
3872             #############################################################################
3873             # _load_rep_metadata()
3874             #############################################################################
3875              
3876             =head2 _load_rep_metadata()
3877              
3878             * Signature: $repository->_load_rep_metadata();
3879             * Param: void
3880             * Return: void
3881             * Throws: App::Exception::Repository
3882             * Since: 0.01
3883              
3884             Sample Usage:
3885              
3886             $self->_load_rep_metadata();
3887              
3888             Initializes the repository metadata information from the config.
3889              
3890             * List of tables (+ displayable labels)
3891             * List of column types (+ displayable labels)
3892              
3893             Then it calls _load_rep_metadata_from_source() in order for the repository
3894             itself to be consulted for its metadata information.
3895              
3896             =cut
3897              
3898             sub _load_rep_metadata {
3899             &App::sub_entry if ($App::trace);
3900             my ($self) = @_;
3901              
3902             my ($table, $tables, $table_defs, $table_def, $native_table, $idx, $label, @label);
3903              
3904             # load up all possible information from the native metadata
3905             $self->_load_rep_metadata_from_source();
3906              
3907             # start with the list of tables that was configured (or the empty list)
3908             $tables = $self->{tables};
3909             if (!defined $tables) {
3910             $tables = [];
3911             $self->{tables} = $tables;
3912             }
3913              
3914             # start with the hash of tables defined (or the empty hash)
3915             $table_defs = $self->{table};
3916             if (!defined $table_defs) {
3917             $table_defs = {};
3918             $self->{table} = $table_defs;
3919             }
3920              
3921             # for each table named in the configuration, give it a number up front
3922             for ($idx = 0; $idx <= $#$tables; $idx++) {
3923             $table = $tables->[$idx];
3924             $table_defs->{$table}{idx} = $idx;
3925             }
3926              
3927             # for each table in the hash (random order), add them to the end
3928             foreach $table (keys %$table_defs) {
3929             $table_def = $table_defs->{$table};
3930             $table_def->{name} = $table;
3931             if (! $table_def->{label}) {
3932             $label = $table;
3933             if ($self->{auto_label}) {
3934             $label = lc($label);
3935             $label =~ s/^([a-z])/uc($1)/e;
3936             $label =~ s/(_[a-z])/uc($1)/eg;
3937             $label =~ s/_+/ /g;
3938             }
3939             $table_def->{label} = $label;
3940             }
3941            
3942             # table has not been added to the list and it's not explicitly "hidden", so add it
3943             if (!defined $table_def->{idx} && ! $table_def->{hide}) {
3944             push(@$tables, $table);
3945             $table_def->{idx} = $#$tables;
3946              
3947             # we're not hiding physical tables and a native table was defined, so make an entry
3948             if (! $self->{hide_physical}) {
3949             $native_table = $table_def->{native_table};
3950             if (defined $native_table) {
3951             $table_defs->{$native_table} = $table_defs->{$table};
3952             }
3953             }
3954             }
3955              
3956             $self->{table_labels}{$table} = $table_def->{label};
3957             }
3958              
3959             my ($type, $types, $type_defs);
3960              
3961             # start with the hash of types defined (or the empty hash)
3962             $type_defs = $self->{type};
3963             if (!defined $type_defs) {
3964             $type_defs = {};
3965             $self->{type} = $type_defs;
3966             }
3967              
3968             # define the standard list of Repository types
3969             $types = [ "string", "text", "integer", "float", "date", "time", "datetime", "binary" ];
3970             $self->{types} = $types;
3971              
3972             # define the standard list of Repository labels
3973             $self->{type_labels} = {
3974             "string" => "Characters",
3975             "text" => "Text",
3976             "integer" => "Integer",
3977             "float" => "Number",
3978             "date" => "Date",
3979             "time" => "Time",
3980             "datetime" => "Date and Time",
3981             "binary" => "Binary Data",
3982             };
3983              
3984             # figure the index in the array of each type
3985             for ($idx = 0; $idx <= $#$types; $idx++) {
3986             $type = $types->[$idx];
3987             $self->{type}{$type}{idx} = $idx;
3988             }
3989             &App::sub_exit() if ($App::trace);
3990             }
3991              
3992             #############################################################################
3993             # _load_rep_metadata_from_source()
3994             #############################################################################
3995              
3996             =head2 _load_rep_metadata_from_source()
3997              
3998             * Signature: $repository->_load_rep_metadata_from_source();
3999             * Param: void
4000             * Return: void
4001             * Throws: App::Exception::Repository
4002             * Since: 0.01
4003              
4004             Sample Usage:
4005              
4006             $repository->_load_rep_metadata_from_source();
4007              
4008             Loads repository metadata from the repository itself
4009             (to complement metadata in the configuration and perhaps
4010             override it).
4011              
4012             The default implementation does nothing.
4013             It is intended to be overridden in the subclass
4014             (if the repository has any sort of metadata).
4015              
4016             =cut
4017              
4018             sub _load_rep_metadata_from_source {
4019             my ($self) = @_;
4020             }
4021              
4022             #############################################################################
4023             # _load_table_metadata()
4024             #############################################################################
4025              
4026             =head2 _load_table_metadata()
4027              
4028             * Signature: $self->_load_table_metadata();
4029             * Param: void
4030             * Return: void
4031             * Throws: App::Exception::Repository
4032             * Since: 0.01
4033              
4034             Sample Usage:
4035              
4036             $self->_load_table_metadata();
4037              
4038             First it calls _load_table_metadata_from_source() in order for the repository
4039             itself to be consulted for any metadata information for the about the
4040             table.
4041              
4042             Then it initializes
4043             the repository metadata information for that table from the config
4044             information.
4045              
4046             * List of columns (+ displayable labels, types)
4047             * List of column types (+ displayable labels)
4048              
4049             Then it determines the set of required columns whenever selecting
4050             data from the table and clears the cache of selected rows
4051             for the table.
4052              
4053             =cut
4054              
4055             sub _load_table_metadata {
4056             &App::sub_entry if ($App::trace);
4057             my ($self, $table) = @_;
4058              
4059             # if it's already been loaded, don't do it again
4060             return if (defined $self->{table}{$table}{loaded});
4061              
4062             my ($table_def, $columns, $column, $column_def, $idx, $native_column);
4063              
4064             $table_def = $self->{table}{$table};
4065             if (!$table_def) {
4066             my $options = $self->{options};
4067             my $prefix = $options->{prefix};
4068             my $conf_type = $options->{conf_type} || "pl";
4069             my $table_file = "$prefix/etc/app/Repository/$self->{name}/$table.$conf_type";
4070             if (-r $table_file) {
4071             $table_def = App::Conf::File->create({ conf_file => $table_file });
4072             if ($table_def->{overlay}) {
4073             delete $table_def->{overlay};
4074             App::Reference->overlay($self->{context}{conf}, $table_def); # Caution. Use with care.
4075             }
4076             else {
4077             $self->{table}{$table} = $table_def;
4078             }
4079             }
4080             }
4081              
4082             $self->{table}{$table}{loaded} = 1; # mark it as having been loaded
4083              
4084             return if (!defined $table_def);
4085              
4086             # load up all additional information from the native metadata
4087             $self->_load_table_metadata_from_source($table);
4088              
4089             $columns = $table_def->{columns};
4090             if (! defined $columns) {
4091             $columns = [];
4092             $table_def->{columns} = $columns;
4093             }
4094              
4095             my $column_defs = $table_def->{column};
4096              
4097             # for each column named in the configuration, give it a number up front
4098             for ($idx = 0; $idx <= $#$columns; $idx++) {
4099             $column = $columns->[$idx];
4100             $column_defs->{$column}{idx} = $idx;
4101             }
4102              
4103             # for each column in the hash (random order), add them to the end
4104             my ($label);
4105             foreach $column (keys %$column_defs) {
4106             $column_def = $column_defs->{$column};
4107             $column_def->{name} = $column;
4108             if (! $column_def->{label}) {
4109             $label = $column;
4110             if ($self->{auto_label}) {
4111             $label = lc($label);
4112             $label =~ s/^([a-z])/uc($1)/e;
4113             $label =~ s/(_[a-z])/uc($1)/eg;
4114             $label =~ s/_+/ /g;
4115             }
4116             $column_def->{label} = $label;
4117             }
4118            
4119             # column has not been added to the list and it's not explicitly "hidden", so add it
4120             if (!defined $column_def->{idx} && ! $column_def->{hide}) {
4121             push(@$columns, $column);
4122             $idx = $#$columns;
4123             $column_def->{idx} = $idx;
4124             $column_def->{alias} = "c$idx" if (!defined $column_def->{alias});
4125              
4126             # we're not hiding physical columns and a native table was defined, so make an entry
4127             if (! $self->{hide_physical}) {
4128             $native_column = $column_def->{native_column};
4129             if (defined $native_column &&
4130             $native_column ne $column &&
4131             !defined $table_def->{column}{$native_column}) {
4132             $table_def->{column}{$native_column} = $table_def->{column}{$column};
4133             }
4134             }
4135             }
4136              
4137             $table_def->{column_labels}{$column} = $column_def->{label};
4138             }
4139              
4140             ######################################################################
4141             # primary key
4142             ######################################################################
4143             # if a non-reference scalar, assume it's a comma-separated list and split it
4144             if ($table_def->{primary_key} && ! ref($table_def->{primary_key})) {
4145             $table_def->{primary_key} = [ split(/ *, */, $table_def->{primary_key}) ];
4146             }
4147              
4148             ####################################################################################
4149             # Determine what columns would normally be considered keys
4150             ####################################################################################
4151             my (%is_key);
4152             my $primary_key = $table_def->{primary_key} || $table_def->{prikey};
4153             if ($primary_key) {
4154             if (!ref($primary_key)) {
4155             foreach $column (split(/,/,$primary_key)) {
4156             $is_key{$column} = 1;
4157             }
4158             }
4159             elsif (ref($primary_key) eq "ARRAY") {
4160             foreach $column (@$primary_key) {
4161             $is_key{$column} = 1;
4162             }
4163             }
4164             }
4165              
4166             my $alternate_keys = $table_def->{alternate_key};
4167             if ($alternate_keys && ref($alternate_keys) eq "ARRAY") {
4168             foreach my $alternate_key (@$alternate_keys) {
4169             if (ref($alternate_key) eq "ARRAY") {
4170             foreach $column (@$alternate_key) {
4171             $is_key{$column} = 1;
4172             }
4173             }
4174             }
4175             }
4176              
4177             ####################################################################################
4178             # Determine which columns *are* keys
4179             ####################################################################################
4180             foreach $column (keys %$column_defs) {
4181             $column_def = $column_defs->{$column};
4182             if (!defined $column_def->{is_key}) {
4183             if ($is_key{$column}) {
4184             $column_def->{is_key} = 1;
4185             }
4186             elsif ($column_def->{type} && $column_def->{type} eq "string") {
4187             $column_def->{is_key} = 1;
4188             }
4189             }
4190             }
4191             &App::sub_exit() if ($App::trace);
4192             }
4193              
4194             #############################################################################
4195             # _load_table_metadata_from_source()
4196             #############################################################################
4197              
4198             =head2 _load_table_metadata_from_source()
4199              
4200             * Signature: $repository->_load_table_metadata_from_source();
4201             * Param: void
4202             * Return: void
4203             * Throws: App::Exception::Repository
4204             * Since: 0.01
4205              
4206             Sample Usage:
4207              
4208             $self->_load_table_metadata_from_source();
4209              
4210             Loads metadata for an individual table from the repository itself
4211             (to complement metadata in the configuration and perhaps
4212             override it).
4213              
4214             The default implementation does nothing.
4215             It is intended to be overridden in the subclass
4216             (if the repository has any sort of metadata).
4217              
4218             =cut
4219              
4220             sub _load_table_metadata_from_source {
4221             my ($self, $table) = @_;
4222             }
4223              
4224             #############################################################################
4225             # METHODS
4226             #############################################################################
4227              
4228             =head1 Methods: Miscellaneous
4229              
4230             =cut
4231              
4232             #####################################################################
4233             # _init()
4234             #####################################################################
4235              
4236             =head2 _init()
4237              
4238             * Signature: $repository->_init();
4239             * Param: defer_connection integer
4240             * Return: void
4241             * Throws: App::Exception::Repository
4242             * Since: 0.01
4243              
4244             Sample Usage:
4245              
4246             $self->_init();
4247              
4248             Every Service constructor (Repository is derived from Service) will
4249             invoke the _init() method near the end of object construction.
4250              
4251             The standard behavior for repositories (implemented here) in _init() is
4252             to initialize the "numrows" and "error" attributes,
4253             call _init2(), connect to the repository,
4254             and load the repository metadata.
4255              
4256             =cut
4257              
4258             sub _init {
4259             &App::sub_entry if ($App::trace);
4260             my ($self) = @_;
4261              
4262             $self->{numrows} = 0;
4263             $self->{error} = "";
4264              
4265             $self->_init2();
4266              
4267             if (!$self->{defer_connection} && !$self->_connect()) {
4268             print STDERR "Error on connect():";
4269             foreach (keys %$self) {
4270             print STDERR " $_=[", $self->{$_}, "]";
4271             }
4272             print STDERR "\n";
4273             return(undef);
4274             }
4275              
4276             $self->_load_rep_metadata();
4277             &App::sub_exit() if ($App::trace);
4278             }
4279              
4280             #############################################################################
4281             # _init2()
4282             #############################################################################
4283              
4284             =head2 _init2()
4285              
4286             * Signature: $repository->_init2();
4287             * Param: defer_connection integer
4288             * Return: void
4289             * Throws: App::Exception::Repository
4290             * Since: 0.01
4291              
4292             Sample Usage:
4293              
4294             $self->_init2();
4295              
4296             The default behavior of _init2() does nothing
4297             and is intended to be overridden (if necessary) in the subclass which
4298             implements the details of access to the physical data store.
4299              
4300             =cut
4301              
4302             sub _init2 { # OVERRIDE IN SUBCLASS TO GET NON-DEFAULT CAPABILITIES
4303             my $self = shift;
4304             }
4305              
4306             #############################################################################
4307             # service_type()
4308             #############################################################################
4309              
4310             =head2 service_type()
4311              
4312             Returns 'Repository'.
4313              
4314             * Signature: $service_type = App::Repository->service_type();
4315             * Param: void
4316             * Return: $service_type string
4317             * Since: 0.01
4318              
4319             $service_type = $widget->service_type();
4320              
4321             =cut
4322              
4323             sub service_type () { 'Repository'; }
4324              
4325             #############################################################################
4326             # current_datetime()
4327             #############################################################################
4328              
4329             =head2 current_datetime()
4330              
4331             Returns 'Repository'.
4332              
4333             * Signature: $current_datetime = App::Repository->current_datetime();
4334             * Param: void
4335             * Return: $current_datetime string
4336             * Since: 0.01
4337              
4338             $current_datetime = $widget->current_datetime();
4339              
4340             =cut
4341              
4342             sub current_datetime {
4343             return (time2str("%Y-%m-%d %H:%M:%S",time()));
4344             }
4345              
4346             #############################################################################
4347             # rows_by_indexed_values()
4348             #############################################################################
4349              
4350             =head2 rows_by_indexed_values()
4351              
4352             * Signature: &App::Repository::rows_by_indexed_values($a,$b);
4353             * Param: $a []
4354             * Param: $b []
4355             * Return: void
4356             * Throws: App::Exception::Repository
4357             * Since: 0.01
4358              
4359             Sample Usage:
4360              
4361             @data = (
4362             [ 5, "Jim", "Red", 13.5, ],
4363             [ 3, "Bob", "Green", 4.2, ],
4364             [ 9, "Ken", "Blue", 27.4, ],
4365             [ 2, "Kim", "Yellow", 11.7, ],
4366             [ 7, "Jan", "Purple", 55.1, ],
4367             );
4368              
4369             @App::Repository::sort_keys = ( 1, 3, 2 );
4370             @App::Repository::sort_types = ("C", "N", "C");
4371             @App::Repository::sort_dirs = ("asc", "desc", "desc");
4372             # OR @App::Repository::sort_dirs = ("_asc", "_desc", "_desc");
4373             # OR @App::Repository::sort_dirs = ("UP", "DOWN", "DOWN");
4374              
4375             @sorted_data = sort rows_by_indexed_values @data;
4376              
4377             The rows_by_indexed_values() function is used to sort rows of data
4378             based on indexes, data types, and directions.
4379              
4380             =cut
4381              
4382             sub rows_by_indexed_values {
4383             my ($pos, $idx, $type, $dir, $sign);
4384             for ($pos = 0; $pos <= $#App::Repository::sort_keys; $pos++) {
4385             $idx = $App::Repository::sort_keys[$pos];
4386             $type = $App::Repository::sort_types[$pos];
4387             $dir = $App::Repository::sort_dirs[$pos];
4388             if (defined $type && $type eq "N") {
4389             $sign = ($a->[$idx] <=> $b->[$idx]);
4390             }
4391             else {
4392             $sign = ($a->[$idx] cmp $b->[$idx]);
4393             }
4394             if ($sign) {
4395             $sign = -$sign if (defined $dir && $dir =~ /^_?[Dd]/); # ("DOWN", "desc", "_desc", etc.)
4396             return ($sign);
4397             }
4398             }
4399             return 0;
4400             }
4401              
4402             #############################################################################
4403             # hashes_by_indexed_values()
4404             #############################################################################
4405              
4406             =head2 hashes_by_indexed_values()
4407              
4408             * Signature: &App::Repository::hashes_by_indexed_values($a,$b);
4409             * Param: $a []
4410             * Param: $b []
4411             * Return: void
4412             * Throws: App::Exception::Repository
4413             * Since: 0.01
4414              
4415             Sample Usage:
4416              
4417             @data = (
4418             { size => 5, name => "Jim", color => "Red", score => 13.5, ],
4419             { size => 3, name => "Bob", color => "Green", score => 4.2, ],
4420             { size => 9, name => "Ken", color => "Blue", score => 27.4, ],
4421             { size => 2, name => "Kim", color => "Yellow", score => 11.7, ],
4422             { size => 7, name => "Jan", color => "Purple", score => 55.1, ],
4423             );
4424              
4425             @App::Repository::sort_keys = ( "size", "color", "name" );
4426             @App::Repository::sort_types = ("C", "N", "C");
4427             @App::Repository::sort_dirs = ("asc", "desc", "desc");
4428             # OR @App::Repository::sort_dirs = ("_asc", "_desc", "_desc");
4429             # OR @App::Repository::sort_dirs = ("UP", "DOWN", "DOWN");
4430              
4431             @sorted_data = sort hashes_by_indexed_values @data;
4432              
4433             The hashes_by_indexed_values() function is used to sort rows of data
4434             based on indexes, data types, and directions.
4435              
4436             =cut
4437              
4438             sub hashes_by_indexed_values {
4439             my ($pos, $key, $type, $dir, $sign);
4440             for ($pos = 0; $pos <= $#App::Repository::sort_keys; $pos++) {
4441             $key = $App::Repository::sort_keys[$pos];
4442             $type = $App::Repository::sort_types[$pos];
4443             $dir = $App::Repository::sort_dirs[$pos];
4444             if (defined $type && $type eq "N") {
4445             $sign = ($a->{$key} <=> $b->{$key});
4446             }
4447             else {
4448             $sign = ($a->{$key} cmp $b->{$key});
4449             }
4450             if ($sign) {
4451             $sign = -$sign if (defined $dir && $dir =~ /^_?[Dd]/); # ("DOWN", "desc", "_desc", etc.)
4452             return ($sign);
4453             }
4454             }
4455             return 0;
4456             }
4457              
4458             #############################################################################
4459             # _get_timer()
4460             #############################################################################
4461              
4462             sub _get_timer {
4463             my ($self) = @_;
4464             my ($seconds_start, $microseconds_start) = gettimeofday;
4465             my $timer = { seconds_start => $seconds_start, microseconds_start => $microseconds_start };
4466             return($timer);
4467             }
4468              
4469             #############################################################################
4470             # _read_timer()
4471             #############################################################################
4472              
4473             sub _read_timer {
4474             my ($self, $timer, $reset) = @_;
4475             my ($seconds_finish, $microseconds_finish) = gettimeofday;
4476             my $seconds_elapsed = $seconds_finish - $timer->{seconds_start};
4477             my $microseconds_elapsed = $microseconds_finish - $timer->{microseconds_start};
4478             if ($microseconds_elapsed < 0) {
4479             $microseconds_elapsed += 1000000;
4480             $seconds_elapsed -= 1;
4481             }
4482             my $time_elapsed = sprintf("%d.%06d", $seconds_elapsed, $microseconds_elapsed);
4483             if (defined $reset) {
4484             # store values. don't reset the timer.
4485             if ($reset == 0) {
4486             $timer->{seconds_start} = $seconds_finish;
4487             $timer->{microseconds_start} = $microseconds_finish;
4488             delete $timer->{time_elapsed};
4489             }
4490             # reset the timer to be ready for another reading.
4491             elsif ($reset) {
4492             $timer->{seconds_finish} = $seconds_finish;
4493             $timer->{microseconds_finish} = $microseconds_finish;
4494             $timer->{time_elapsed} = $time_elapsed;
4495             }
4496             }
4497             return($time_elapsed);
4498             }
4499              
4500             #############################################################################
4501             # DESTROY()
4502             #############################################################################
4503              
4504             =head2 DESTROY()
4505              
4506             * Signature: $self->DESTROY();
4507             * Param: void
4508             * Return: void
4509             * Throws: App::Exception::Repository
4510             * Since: 0.01
4511              
4512             Sample Usage:
4513              
4514             $self->DESTROY(); # never called explicitly. called by Perl itself.
4515              
4516             The DESTROY() method is called when the repository object is release from
4517             memory. This happen when the calling program lets the variable holding the
4518             object reference go out of scope, sets the variable to something else,
4519             or exits the program without otherwise releasing the object.
4520              
4521             The DESTROY() method simply calls disconnect() to make sure that all
4522             connection-related resources are freed. This is safe, assuming (correctly)
4523             that the disconnect() method may be called without negative consequences
4524             even when already disconnected from the repository.
4525              
4526             =cut
4527              
4528             sub DESTROY {
4529             my $self = shift;
4530             $self->_disconnect();
4531             }
4532              
4533             =head1 ACKNOWLEDGEMENTS
4534              
4535             * Author: Stephen Adkins
4536             * License: This is free software. It is licensed under the same terms as Perl itself.
4537              
4538             =head1 SEE ALSO
4539              
4540             L|App::Context>,
4541             L|App::Service>
4542              
4543             =cut
4544              
4545             1;
4546              
4547             __END__