File Coverage

blib/lib/PGObject.pm
Criterion Covered Total %
statement 34 119 28.5
branch 1 44 2.2
condition 2 24 8.3
subroutine 10 13 76.9
pod 6 6 100.0
total 53 206 25.7


line stmt bran cond sub pod time code
1              
2             =head1 NAME
3              
4             PGObject - A toolkit integrating intelligent PostgreSQL dbs into Perl objects
5              
6             =cut
7              
8             package PGObject;
9 5     5   413186 use strict;
  5         50  
  5         155  
10 5     5   27 use warnings;
  5         10  
  5         143  
11              
12 5     5   27 use Carp;
  5         7  
  5         301  
13 5     5   3392 use Memoize;
  5         13265  
  5         272  
14 5     5   2304 use PGObject::Type::Registry;
  5         13  
  5         187  
15              
16 5     5   35 use List::MoreUtils qw/pairwise/;
  5         9  
  5         31  
17              
18             =head1 VERSION
19              
20             Version 2.2.0
21              
22             =cut
23              
24             our $VERSION = '2.2.0';
25              
26             =head1 SYNPOSIS
27              
28             To use without caching:
29              
30             use PGObject;
31              
32             To use with caching:
33              
34             use PGObject ':cache';
35              
36             To get basic info from a function
37              
38             my $f_info = PGObject->function_info(
39             dbh => $dbh,
40             funcname => $funcname,
41             funcschema => 'public',
42             );
43              
44             To get info about a function, filtered by first argument type
45              
46             my $f_info = PGObject->function_info(
47             dbh => $dbh,
48             funcname => $funcname,
49             funcschema => 'public',
50             funcprefix => 'test__',
51             objtype => 'invoice',
52             objschema => 'public',
53             );
54              
55             To call a function with enumerated arguments
56              
57             my @results = PGObject->call_procedure(
58             dbh => $dbh,
59             funcname => $funcname,
60             funcprefix => 'test__',
61             funcschema => $funcname,
62             args => [$arg1, $arg2, $arg3],
63             );
64              
65             To do the same with a running total
66              
67             my @results = PGObject->call_procedure(
68             dbh => $dbh,
69             funcname => $funcname,
70             funcschema => $funcname,
71             args => [$arg1, $arg2, $arg3],
72             running_funcs => [{agg => 'sum(amount)', alias => 'running_total'}],
73             );
74              
75             =cut
76              
77             sub import {
78 5     5   57 my @directives = @_;
79 5 50       13 memoize 'function_info' if grep { $_ eq ':cache' } @directives;
  7         34  
80             PGObject::Type::Registry->new_registry($_)
81 5         12 for grep { $_ !~ /^\:/; } @directives;
  7         39  
82             }
83              
84             =head1 DESCRIPTION
85              
86             PGObject contains the base routines for object management using discoverable
87             stored procedures in PostgreSQL databases. This module contains only common
88             functionality and support structures, and low-level API's. Most developers will
89             want to use more functional modules which add to these functions.
90              
91             The overall approach here is to provide the basics for a toolkit that other
92             modules can extend. This is thus intended to be a component for building
93             integration between PostgreSQL user defined functions and Perl objects.
94              
95             Because decisions such as state handling are largely outside of the scope of
96             this module, this module itself does not do any significant state handling.
97             Database handles (using DBD::Pg 2.0 or later) must be passed in on every call.
98             This decision was made in order to allow for diversity in this area, with the
99             idea that wrapper classes would be written to implement this.
100              
101             =head1 FUNCTIONS
102              
103             =head2 clear_info_cache
104              
105             This function clears the info cache if this was loaded with caching enabled.
106              
107             The cache is also automatically cleared when a function that was run could not
108             be found (this could be caused by updating the db).
109              
110             =cut
111              
112             sub clear_info_cache {
113 0     0 1 0 local ($@);
114 0         0 eval { Memoize::flush_cache('function_info') };
  0         0  
115             }
116              
117             =head2 function_info(%args)
118              
119             Arguments:
120              
121             =over
122              
123             =item dbh (required)
124              
125             Database handle
126              
127             =item funcname (required)
128              
129             function name
130              
131             =item funcschema (optional, default 'public')
132              
133             function schema
134              
135             =item funcprefix (optiona, default '')
136              
137             Prefix for the function. This can be useful for separating functions by class.
138              
139             =item argtype1 (optional)
140              
141             Name of first argument type. If not provided, does not filter on this criteria.
142              
143             =item argschema (optional)
144              
145             Name of first argument type's schema. If not provided defaults to 'public'
146              
147             =back
148              
149             This function looks up basic mapping information for a function. If more than
150             one function is found, an exception is raised. This function is primarily
151             intended to be used by packages which extend this one, in order to accomplish
152             stored procedure to object mapping.
153              
154             Return data is a hashref containing the following elements:
155              
156             =over
157              
158             =item args
159              
160             This is an arrayref of hashrefs, each of which contains 'name' and 'type'
161              
162             =item name
163              
164             The name of the function
165              
166             =item num_args
167              
168             The number of arguments
169              
170             =back
171              
172             =cut
173              
174             sub function_info {
175 0     0 1 0 my ( $self, %args ) = @_;
176 0   0     0 $args{funcschema} ||= 'public';
177 0   0     0 $args{funcprefix} ||= '';
178 0         0 $args{funcname} = $args{funcprefix} . $args{funcname};
179 0   0     0 $args{argschema} ||= 'public';
180              
181 0   0     0 my $dbh = $args{dbh} || croak 'No dbh provided';
182              
183 0         0 my $query = qq|
184             SELECT proname, pronargs, proargnames,
185             string_to_array(array_to_string(proargtypes::regtype[], ' '),
186             ' ') as argtypes
187             FROM pg_proc
188             JOIN pg_namespace pgn ON pgn.oid = pronamespace
189             WHERE proname = ? AND nspname = ?
190             |;
191 0         0 my @queryargs = ( $args{funcname}, $args{funcschema} );
192 0 0       0 if ( $args{argtype1} ) {
193 0         0 $query .= qq|
194             AND (proargtypes::int[])[0] IN (select t.oid
195             from pg_type t
196             join pg_namespace n
197             ON n.oid = typnamespace
198             where typname = ?
199             AND n.nspname = ?
200             )|;
201 0         0 push @queryargs, $args{argtype1};
202 0         0 push @queryargs, $args{argschema};
203             }
204              
205 0   0     0 my $sth = $dbh->prepare($query) || die $!;
206 0 0       0 $sth->execute(@queryargs) || die $dbh->errstr . ": " . $query;
207 0         0 my $ref = $sth->fetchrow_hashref('NAME_lc');
208 0 0       0 croak "transaction already aborted" if $dbh->state eq '25P02';
209 0 0       0 croak "No such function" if !$ref;
210 0 0       0 croak 'Ambiguous discovery criteria' if $sth->fetchrow_hashref('NAME_lc');
211              
212 0         0 my $f_args;
213 0         0 for my $n ( @{ $ref->{proargnames} } ) {
  0         0  
214 0         0 push @$f_args, { name => $n, type => shift @{ $ref->{argtypes} } };
  0         0  
215             }
216              
217             return {
218             name => $ref->{proname},
219             num_args => $ref->{pronargs},
220 0         0 args => $f_args,
221             };
222              
223             }
224              
225             =head2 call_procedure(%args)
226              
227             Arguments:
228              
229             =over
230              
231             =item funcname
232              
233             The function name
234              
235             =item funcschema
236              
237             The schema in which the function resides
238              
239             =item funcprefix (optiona, default '')
240              
241             Prefix for the function. This can be useful for separating functions by class.
242              
243             =item args
244              
245             This is an arrayref. Each item is either a literal value, an arrayref, or a
246             hashref of extended information. In the hashref case, the type key specifies
247             the string to use to cast the type in, and value is the value.
248              
249             =item orderby
250              
251             The list (arrayref) of columns on output for ordering.
252              
253             =item running_funcs
254              
255             An arrayref of running windowed aggregates. Each contains two keys, namely 'agg' for the aggregate and 'alias' for the function name.
256              
257             These are aggregates, each one has appended 'OVER (ROWS UNBOUNDED PRECEDING)'
258             to it.
259              
260             =item registry
261              
262             This is the name of the registry used for type conversion. It can be omitted
263             and defaults to 'default.' Note that use of a non-standard registry currently
264             does *not* merge changes from the default registry, so you need to reregister
265             types in non-default registries when you create them.
266              
267             Please note, these aggregates are not intended to be user-supplied. Please only
268             allow whitelisted values here or construct in a tested framework elsewhere.
269             Because of the syntax here, there is no sql injection prevention possible at
270             the framework level for this parameter.
271              
272             =back
273              
274             =cut
275              
276             sub call_procedure {
277 0     0 1 0 my ( $self, %args ) = @_;
278 0         0 local $@;
279 0   0     0 $args{funcschema} ||= 'public';
280 0   0     0 $args{funcprefix} ||= '';
281 0         0 $args{funcname} = $args{funcprefix} . $args{funcname};
282 0   0     0 $args{registry} ||= 'default';
283              
284 0         0 my $dbh = $args{dbh};
285 0 0       0 croak "No database handle provided" unless $dbh;
286 0 0       0 croak "dbh not a database handle" unless eval { $dbh->isa('DBI::db') };
  0         0  
287              
288 0         0 my $wf_string = '';
289              
290             $wf_string = join ', ', map {
291             $_->{agg}
292             . ' OVER (ROWS UNBOUNDED PRECEDING) AS '
293             . $_->{alias}
294 0 0       0 } @{ $args{running_funcs} } if $args{running_funcs};
  0         0  
  0         0  
295 0 0       0 $wf_string = ', ' . $wf_string if $wf_string;
296              
297             my @qargs = map {
298 0         0 my $arg = $_;
299 0         0 local ($@);
300 0 0       0 $arg = $arg->to_db if eval { $arg->can('to_db') };
  0         0  
301 0 0       0 $arg = $arg->pgobject_to_db if eval { $arg->can('pgobject_to_db') };
  0         0  
302 0         0 $arg;
303 0         0 } @{ $args{args} };
  0         0  
304              
305             my $argstr = join ', ', map {
306 0 0 0     0 ( ref $_ and eval { $_->{cast} } ) ? "?::$_->{cast}" : '?';
307 0         0 } @{ $args{args} };
  0         0  
308              
309 0         0 my $order = '';
310 0 0       0 if ( $args{orderby} ) {
311             $order = join(
312             ', ',
313             map {
314 0         0 my $dir = undef;
315 0 0       0 if (s/\s+(ASC|DESC)\s*$//i) {
316 0         0 $dir = $1;
317             }
318 0 0       0 defined $dir
319             ? $dbh->quote_identifier($_) . " $dir"
320             : $dbh->quote_identifier($_);
321 0         0 } @{ $args{orderby} }
  0         0  
322             );
323             }
324             my $query = qq|
325             SELECT * $wf_string
326             FROM |
327             . $dbh->quote_identifier( $args{funcschema} ) . '.'
328             . $dbh->quote_identifier( $args{funcname} )
329 0         0 . qq|($argstr) |;
330 0 0       0 if ($order) {
331 0         0 $query .= qq|
332             ORDER BY $order |;
333             }
334              
335 0   0     0 my $sth = $dbh->prepare($query) || die $!;
336              
337 0         0 my $place = 1;
338              
339 0         0 foreach my $carg (@qargs) {
340 0 0       0 if ( ref($carg) =~ /HASH/ ) {
341             $sth->bind_param( $place, $carg->{value},
342 0         0 { pg_type => $carg->{type} } );
343             }
344             else {
345              
346             # This is used to support arrays of db-aware types. Long-run
347             # I think we should merge bytea support into this framework. --CT
348 0 0       0 if ( ref($carg) =~ /ARRAY/ ) {
349 0         0 local ($@);
350 0 0       0 if ( eval { $carg->[0]->can('to_db') } ) {
  0         0  
351 0         0 for my $ref (@$carg) {
352 0         0 $ref = $ref->to_db;
353             }
354             }
355             }
356              
357 0         0 $sth->bind_param( $place, $carg );
358             }
359 0         0 ++$place;
360             }
361              
362 0 0       0 $sth->execute() || die $dbh->errstr . ": " . $query;
363              
364 0 0       0 clear_info_cache() if $dbh->state eq '42883'; # (No Such Function)
365              
366 0         0 my @rows = ();
367             my $row_deserializer =
368             PGObject::Type::Registry->rowhash_deserializer(
369             registry => $args{registry},
370             types => $sth->{pg_type},
371             columns => $sth->{NAME_lc},
372 0         0 );
373 0         0 while (my $row = $sth->fetchrow_hashref('NAME_lc')) {
374 0         0 push @rows, $row_deserializer->( $row );
375             }
376 0         0 return @rows;
377             }
378              
379             =head2 new_registry($registry_name)
380              
381             Creates a new registry if it does not exist. This is useful when segments of
382             an application must override existing type mappings.
383              
384             This is deprecated and throws a warning.
385              
386             Use PGObject::Type::Registry->new_registry($registry_name) instead.
387              
388             This no longer returns anything of significance.
389              
390             =cut
391              
392             sub new_registry {
393 5     5 1 182 my ( $self, $registry_name ) = @_;
394 5         51 carp "Deprecated use of PGObject->new_registry()";
395 5         2062 PGObject::Type::Registry->new_registry($registry_name);
396             }
397              
398             =head2 register_type(pgtype => $tname, registry => $regname, perl_class => $pm)
399              
400             DEPRECATED
401              
402             Registers a type as a class. This means that when an attribute of type $pg_type
403             is returned, that PGObject will automatically return whatever
404             $perl_class->from_db returns. This allows you to have a db-specific constructor
405             for such types.
406              
407             The registry argument is optional and defaults to 'default'
408              
409             If the registry does not exist, an error is raised. if the pg_type is already
410             registered to a different type, this returns 0. Returns 1 on success.
411              
412             Use PGObject::Type::Registry->register_type() instead.
413              
414             =cut
415              
416             sub register_type {
417 11     11 1 2490 carp 'Use of deprecated method register_type of PGObject module';
418 11         3471 my ( $self, %args ) = @_;
419              
420             PGObject::Type::Registry->register_type(
421             registry => $args{registry},
422             dbtype => $args{pg_type},
423             apptype => $args{perl_class}
424 11         70 );
425 7         37 return 1;
426             }
427              
428             =head2 unregister_type(pgtype => $tname, registry => $regname)
429              
430             Deprecated.
431              
432             Tries to unregister the type. If the type does not exist, returns 0, otherwise
433             returns 1. This is mostly useful for when a specific type must make sure it has
434             the slot. This is rarely desirable. It is usually better to use a subregistry
435             instead.
436              
437             =cut
438              
439             sub unregister_type {
440 3     3 1 732 carp 'Use of deprecated method unregister_type of PGObject';
441 3         998 my ( $self, %args ) = @_;
442              
443 3   100     19 $args{registry} ||= 'default';
444             PGObject::Type::Registry->unregister_type(
445             registry => $args{registry},
446             dbtype => $args{pg_type}
447 3         14 );
448             }
449              
450             =head1 WRITING PGOBJECT-AWARE HELPER CLASSES
451              
452             One of the powerful features of PGObject is the ability to declare methods in
453             types which can be dynamically detected and used to serialize data for query
454             purposes. Objects which contain a pgobject_to_db() or a to_db() method, that
455             method will be called and the return value used in place of the object. This
456             can allow arbitrary types to serialize themselves in arbitrary ways.
457              
458             For example a date object could be set up with such a method which would export
459             a string in yyyy-mm-dd format. An object could look up its own definition and
460             return something like :
461              
462             { cast => 'dbtypename', value => '("A","List","Of","Properties")'}
463              
464             If a scalar is returned that is used as the serialized value. If a hashref is
465             returned, it must follow the type format:
466              
467             type => variable binding type,
468             cast => db cast type
469             value => literal representation of type, as intelligible by DBD::Pg
470              
471             =head2 REQUIRED INTERFACES
472              
473             Registered types MUST implement a $class->from_db function accepts the string
474             from the database as its only argument, and returns the object of the desired
475             type.
476              
477             Any type MAY present an $object->to_db() interface, requiring no arguments, and returning a valid value. These can be hashrefs as specified above, arrayrefs
478             (converted to PostgreSQL arrays by DBD::Pg) or scalar text values.
479              
480             =head2 UNDERSTANDING THE REGISTRY SYSTEM
481              
482             Note that 2.0 moves the registry to a service module which handles both
483             registry and deserialization of database types. This is intended to be both
484             cleaner and more flexible than the embedded system in 1.x.
485              
486             The registry system allows Perl classes to "claim" PostgreSQL types within a
487             certain domain. For example, if I want to ensure that all numeric types are
488             turned into Math::BigFloat objects, I can build a wrapper class with appropriate
489             interfaces, but PGObject won't know to convert numeric types to this new class,
490             so this is what registration is for.
491              
492             By default, these mappings are fully global. Once a class claims a type, unless
493             another type goes through the trouble of unregisterign the first type and making
494             sure it gets the authoritative spot, all items of that type get turned into the
495             appropriate Perl object types. While this is sufficient for the vast number of
496             applications, however, there may be cases where names conflict across schemas or
497             the like. To address this application components may create their own
498             registries. Each registry is fully global, but application components can
499             specify non-standard registries when calling procedures, and PGObject will use
500             only those components registered on the non-standard registry when checking rows
501             before output.
502              
503             =head3 Backwards Incompatibilities from 1.x
504              
505             Deserialization occurs in a context which specifies a registry. In 1.x there
506             were no concerns about default mappings but now this triggers a warning. The
507             most basic and frequently used portions of this have been kept but return values
508             for registering types has changed. We no longer provide a return variable but
509             throw an exception if the type cannot be safely registered.
510              
511             This follows a philosophy of throwing exceptions when guarantees cannot be met.
512              
513             We now throw warnings when the default registry is used.
514              
515             Longer-run, deserializers should use the PGObject::Type::Registry interface
516             directly.
517              
518             =head1 WRITING TOP-HALF OBJECT FRAMEWORKS FOR PGOBJECT
519              
520             PGObject is intended to be the database-facing side of a framework for objects.
521             The intended structure is for three tiers of logic:
522              
523             =over
524              
525             =item Database facing, low-level API's
526              
527             =item Object management modules
528              
529             =item Application handlers with things like database connection management.
530              
531             =back
532              
533             By top half, we are referring to the second tier. The third tier exists in the
534             client application.
535              
536             The PGObject module provides only low-level API's in that first tier. The job
537             of this module is to provide database function information to the upper level
538             modules.
539              
540             We do not supply type information, If your top-level module needs this, please
541             check out https://code.google.com/p/typeutils/ which could then be used via our
542             function mapping APIs here.
543              
544             =head2 Safely Handling Memoization of Catalog Lookups
545              
546             It is important to remember, when writing PGObject top half frameworks that the
547             catalog lookups may be memoized and may come back as a data structure. This
548             means that changes to the structures returned from get_function_info() in this
549             module and similar functions in other catalog-bound modules may not be safe to
550             modify in arbitrary ways. Therefore we recommend that the return values from
551             catalog-lookup functions are treated as immutable.
552              
553             Normalizing output is safe provided there are no conflicts between naming
554             conventions. This is usually true since different naming conventions would
555             interfere withmapping. However, there could be cases where it is not true, for
556             example, where two different mapping modules agree on a subset of normalization
557             conventions but differ on some details. The two might safely handle the same
558             conventions but normalize differently resulting in conflicts of both were used.
559              
560             =head1 A BRIEF GUIDE TO THE NAMESPACE LAYOUT
561              
562             Most names underneath PGObject can be assumed to be top-half modules and modules
563             under those can be generally assumed to be variants on those. There are,
564             however, a few reserved names:
565              
566             =over
567              
568             =item ::Debug is reserved for debugging information. For example, functions
569             which retrieve sources of functions, or grab diagnostics, or the like would go
570             here.
571              
572             =item ::Test is reserved for test framework extensions applible only here
573              
574             =item ::Type is reserved for PG aware type classes.
575              
576             For example, one might have PGObject::Type::BigFloat for a Math::Bigfloat
577             wrapper, or PGObject::Type::DateTime for a DateTime wrapper.
578              
579             =item ::Util is reserved for utility functions and classes.
580              
581             =back
582              
583             =head1 AUTHOR
584              
585             Chris Travers, C<< >>
586              
587             =head1 BUGS
588              
589             Please report any bugs or feature requests to C, or through
590             the web interface at L. I will be notified, and then you'll
591             automatically be notified of progress on your bug as I make changes.
592              
593             =head1 SUPPORT
594              
595             You can find documentation for this module with the perldoc command.
596              
597             perldoc PGObject
598              
599              
600             You can also look for information at:
601              
602             =over
603              
604             =item * RT: CPAN's request tracker (report bugs here)
605              
606             L
607              
608             =item * AnnoCPAN: Annotated CPAN documentation
609              
610             L
611              
612             =item * CPAN Ratings
613              
614             L
615              
616             =item * Search CPAN
617              
618             L
619              
620             =back
621              
622             =head1 ACKNOWLEDGEMENTS
623              
624             This code has been loosely based on code written for the LedgerSMB open source
625             accounting and ERP project. While that software uses the GNU GPL v2 or later,
626             this is my own reimplementation, based on my original contributions to that
627             project alone, and it differs in significant ways. This being said, without
628             LedgerSMB, this module wouldn't exist, and without the lessons learned there,
629             and the great people who have helped make this possible, this framework would
630             not be half of what it is today.
631              
632              
633             =head1 SEE ALSO
634              
635             =over
636              
637             =item PGObject::Simple - Simple mapping of object properties to stored proc args
638              
639             =item PGObject::Simple::Role - Moose-enabled wrapper for PGObject::Simple
640              
641             =back
642              
643             =head1 COPYRIGHT
644              
645             COPYRIGHT (C) 2013-2014 Chris Travers
646             COPYRIGHT (C) 2014-2017 The LedgerSMB Core Team
647              
648             Redistribution and use in source and compiled forms with or without
649             modification, are permitted provided that the following conditions are met:
650              
651             =over
652              
653             =item
654              
655             Redistributions of source code must retain the above
656             copyright notice, this list of conditions and the following disclaimer as the
657             first lines of this file unmodified.
658              
659             =item
660              
661             Redistributions in compiled form must reproduce the above copyright
662             notice, this list of conditions and the following disclaimer in the
663             source code, documentation, and/or other materials provided with the
664             distribution.
665              
666             =back
667              
668             =head1 LICENSE
669              
670             THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) "AS IS" AND
671             ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
672             WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
673             DISCLAIMED. IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR
674             ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
675             (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
676             LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
677             ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
678             (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
679             SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
680              
681             =cut
682              
683             1;