File Coverage

blib/lib/PGObject.pm
Criterion Covered Total %
statement 34 122 27.8
branch 1 44 2.2
condition 2 24 8.3
subroutine 10 13 76.9
pod 6 6 100.0
total 53 209 25.3


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