File Coverage

blib/lib/PGObject.pm
Criterion Covered Total %
statement 34 121 28.1
branch 1 42 2.3
condition 2 26 7.6
subroutine 10 13 76.9
pod 6 6 100.0
total 53 208 25.4


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