File Coverage

blib/lib/PostgreSQL/PLPerl/Call.pm
Criterion Covered Total %
statement 17 84 20.2
branch 0 40 0.0
condition 0 11 0.0
subroutine 5 13 38.4
pod 0 1 0.0
total 22 149 14.7


line stmt bran cond sub pod time code
1             # vim: ts=8:sw=4:sts=4:et
2             package PostgreSQL::PLPerl::Call;
3             our $VERSION = '1.007';
4              
5             =head1 NAME
6              
7             PostgreSQL::PLPerl::Call - Simple interface for calling SQL functions from PostgreSQL PL/Perl
8              
9             =head1 VERSION
10              
11             version 1.007
12              
13             =head1 SYNOPSIS
14              
15             use PostgreSQL::PLPerl::Call;
16              
17             Returning single-row single-column values:
18              
19             $pi = call('pi'); # 3.14159265358979
20              
21             $net = call('network(inet)', '192.168.1.5/24'); # '192.168.1.0/24';
22              
23             $seqn = call('nextval(regclass)', $sequence_name);
24              
25             $dims = call('array_dims(text[])', '{a,b,c}'); # '[1:3]'
26              
27             # array arguments can be perl array references:
28             $ary = call('array_cat(int[], int[])', [1,2,3], [2,1]); # '{1,2,3,2,1}'
29              
30             Returning multi-row single-column values:
31              
32             @ary = call('generate_series(int,int)', 10, 15); # (10,11,12,13,14,15)
33              
34             Returning single-row multi-column values:
35              
36             # assuming create function func(int) returns table (r1 text, r2 int) ...
37             $row = call('func(int)', 42); # returns hash ref { r1=>..., r2=>... }
38              
39             Returning multi-row multi-column values:
40              
41             @rows = call('pg_get_keywords'); # ({...}, {...}, ...)
42              
43             Alternative method-call syntax:
44              
45             $pi = PG->pi();
46             $seqn = PG->nextval($sequence_name);
47              
48             Here C simply means PostgreSQL. (C is actually an imported constant whose
49             value is the name of a package containing an AUTOLOAD function that dispatches
50             to C. In case you wanted to know.)
51              
52             =head1 DESCRIPTION
53              
54             The C function provides a simple efficient way to call SQL functions
55             from PostgreSQL PL/Perl code.
56              
57             The first parameter is a I that specifies the name of the function
58             to call and, optionally, the types of the arguments.
59              
60             Any further parameters are used as argument values for the function being called.
61              
62             =head2 Signature
63              
64             The first parameter to C is a I that specifies the name of
65             the function.
66              
67             Immediately after the function name, in parenthesis, a comma separated list of
68             type names can be given. For example:
69              
70             'pi'
71             'generate_series(int,int)'
72             'array_cat(int[], int[])'
73             'myschema.myfunc(date, float8)'
74              
75             The types specify how the I to the call should be interpreted.
76             They don't have to exactly match the types used to declare the function you're
77             calling.
78              
79             You also don't have to specify types for I the arguments, just the
80             left-most arguments that need types.
81              
82             The function name should be given in the same way it would in an SQL statement,
83             so if identifier quoting is needed it should be specified already enclosed in
84             double quotes. For example:
85              
86             call('myschema."Foo Bar"');
87              
88             =head2 Array Arguments
89              
90             The argument value corresponding to a type that contains 'C<[]>' can be a
91             string formated as an array literal, or a reference to a perl array. In the
92             later case the array reference is automatically converted into an array literal
93             using the C function.
94              
95             =head2 Varadic Functions
96              
97             Functions with C arguments can be called with a fixed number of
98             arguments by repeating the type name in the signature the same number of times.
99             For example, given:
100              
101             create function vary(VARIADIC int[]) as ...
102              
103             you can call that function with three arguments using:
104              
105             call('vary(int,int,int)', $int1, $int2, $int3);
106              
107             Alternatively, you can append the string 'C<...>' to the last type in the
108             signature to indicate that the argument is variadic. For example:
109              
110             call('vary(int...)', @ints);
111              
112             Type names must be included in the signature in order to call variadic functions.
113              
114             Functions with a variadic argument must have at least one value for that
115             argument. Otherwise you'll get a "function ... does not exist" error.
116              
117             =head2 Method-call Syntax
118              
119             An alternative syntax can be used for making calls:
120              
121             PG->function_name(@args)
122              
123             For example:
124              
125             $pi = PG->pi();
126             $seqn = PG->nextval($sequence_name);
127              
128             Using this form you can't easily specify a schema name or argument types, and
129             you can't call variadic functions. (For various technical reasons.)
130             In cases where a signature is needed, like variadic or polymorphic functions,
131             you might get a somewhat confusing error message. For example:
132              
133             PG->generate_series(10,20);
134              
135             fails with the error "there is no parameter $1". The underlying problem is that
136             C is a polymorphic function: different versions of the
137             function are executed depending on the type of the arguments.
138              
139             =head2 Wrapping and Currying
140              
141             It's simple to wrap a call into an anonymous subroutine and pass that code
142             reference around. For example:
143              
144             $nextval_fn = sub { PG->nextval(@_) };
145             ...
146             $val = $nextval_fn->($sequence_name);
147              
148             or
149              
150             $some_func = sub { call('some_func(int, date[], int)', @_) };
151             ...
152             $val = $some_func->($foo, \@dates, $debug);
153              
154             You can take this approach further by specifying some of the arguments in the
155             anonymous subroutine so they don't all have to be provided in the call:
156              
157             $some_func = sub { call('some_func(int, date[], int)', $foo, shift, $debug) };
158             ...
159             $val = $some_func->(\@dates);
160              
161              
162             =head2 Results
163              
164             The C function processes return values in one of four ways depending on
165             two criteria: single column vs. multi-column results, and list context vs scalar context.
166              
167             If the results contain a single column with the same name as the function that
168             was called, then those values are extracted and returned directly. This makes
169             simple calls very simple:
170              
171             @ary = call('generate_series(int,int)', 10, 15); # (10,11,12,13,14,15)
172              
173             Otherwise, the rows are returned as references to hashes:
174              
175             @rows = call('pg_get_keywords'); # ({...}, {...}, ...)
176              
177             If the C function was executed in list context then all the values/rows
178             are returned, as shown above.
179              
180             If the function was executed in scalar context then an exception will be thrown
181             if more than one row is returned. For example:
182              
183             $foo = call('generate_series(int,int)', 10, 10); # 10
184             $bar = call('generate_series(int,int)', 10, 11); # dies
185              
186             If you only want the first result you can use list context;
187              
188             ($bar) = call('generate_series(int,int)', 10, 11);
189             $bar = (call('generate_series(int,int)', 10, 11))[0];
190              
191             =head1 ENABLING
192              
193             In order to use this module you need to arrange for it to be loaded when
194             PostgreSQL initializes a Perl interpreter.
195              
196             Create a F file in the same directory as your
197             F file, if it doesn't exist already.
198              
199             In the F file write the code to load this module.
200              
201             =head2 PostgreSQL 8.x
202              
203             Set the C before starting postgres, to something like this:
204              
205             PERL5OPT='-e "require q{plperlinit.pl}"'
206              
207             The code in the F should also include C
208             to avoid any problems with nested invocations of perl, e.g., via a C
209             function.
210              
211             =head2 PostgreSQL 9.0
212              
213             For PostgreSQL 9.0 you can still use the C method described above.
214             Alternatively, and preferably, you can use the C configuration
215             variable in the F file.
216              
217             plperl.on_init='require q{plperlinit.pl};'
218              
219             =head1 plperl
220              
221             You can use the L module to make the
222             call() function available for use in the C language:
223              
224             use PostgreSQL::PLPerl::Injector;
225             inject_plperl_with_names_from(PostgreSQL::PLPerl::Call => 'call');
226              
227             =head1 OTHER INFORMATION
228              
229             =head2 Performance
230              
231             Internally C uses C to create a plan to execute the
232             function with the typed arguments.
233              
234             The plan is cached using the call 'signature' as the key. Minor variations in
235             the signature will still reuse the same plan.
236              
237             For variadic functions, separate plans are created and cached for each distinct
238             number of arguments the function is called with.
239              
240             =head2 Limitations and Caveats
241              
242             Requires PostgreSQL 9.0 or later.
243              
244             Types that contain a comma can't be used in the call signature. That's not a
245             problem in practice as it only affects 'C' and 'C'
246             and the 'C<,s>' part isn't needed. Typically the 'C<(p,s)>' portion isn't used in
247             signatures.
248              
249             The return value of functions that have a C return type should not be
250             relied upon, naturally.
251              
252             =head2 Author and Copyright
253              
254             Tim Bunce L
255              
256             Copyright (c) Tim Bunce, Ireland, 2010. All rights reserved.
257             You may use and distribute on the same terms as Perl 5.10.1.
258              
259             With thanks to L for sponsoring development.
260              
261             =cut
262              
263 2     2   1622 use strict;
  2         5  
  2         59  
264 2     2   10 use warnings;
  2         4  
  2         51  
265 2     2   13 use Exporter;
  2         4  
  2         60  
266 2     2   10 use Carp;
  2         4  
  2         433  
267              
268             our @ISA = qw(Exporter);
269             our @EXPORT = qw(call PG);
270              
271             my %sig_cache;
272             our $debug = 0;
273              
274             # encapsulated package to provide an AUTOLOAD interface to call()
275 2         4 use constant PG => do {
276             package PostgreSQL::PLPerl::Call::PG;
277 2         4 our $VERSION = '1.007';
278              
279             sub AUTOLOAD {
280             #(my $function = our $AUTOLOAD) =~ s/.*:://;
281 0     0     our $AUTOLOAD =~ s/.*:://;
282 0           shift;
283 0           return PostgreSQL::PLPerl::Call::call($AUTOLOAD, @_);
284             }
285              
286 2         2371 __PACKAGE__;
287 2     2   16 };
  2         4  
288              
289              
290             sub call {
291 0     0 0   my $sig = shift;
292              
293 0           my $arity = scalar @_; # argument count to handle variadic subs
294              
295 0   0       my $how = $sig_cache{"$sig.$arity"} ||= do {
296              
297             # get a normalized signature to recheck the cache with
298             # and also extract the SP name and argument types
299 0 0         my ($stdsig, $fullspname, $spname, $arg_types) = _parse_signature($sig, $arity)
300             or croak "Can't parse '$sig'";
301 0 0         warn "parsed call($sig) => $stdsig\n"
302             if $debug;
303              
304             # recheck the cache with with the normalized signature
305 0   0       $sig_cache{"$stdsig.$arity"} ||= [ # else a new entry (for both caches)
306             $spname, # is name of column for single column results
307             scalar _mk_process_args($arg_types),
308             scalar _mk_process_call($fullspname, $arity, $arg_types),
309             $fullspname, # is name used in SQL to make the call
310             $stdsig,
311             ];
312             };
313              
314 0           my ($spname, $prepargs, $callsub) = @$how;
315              
316 0 0         my $rv = $callsub->( $prepargs ? $prepargs->(@_) : @_ );
317              
318 0           my $rows = $rv->{rows};
319 0 0         my $row1 = $rows->[0] # peek at first row
320             or return; # no row: undef in scalar context else empty list
321              
322 0   0       my $is_single_column = (keys %$row1 == 1 and exists $row1->{$spname});
323              
324 0 0         if (wantarray) { # list context - all rows
    0          
325              
326 0 0         return map { $_->{$spname} } @$rows if $is_single_column;
  0            
327 0           return @$rows;
328             }
329             elsif (defined wantarray) { # scalar context - single row
330              
331 0 0         croak "$sig was called in scalar context but returned more than one row"
332             if @$rows > 1;
333              
334 0 0         return $row1->{$spname} if $is_single_column;
335 0           return $row1;
336             }
337             # else void context - nothing to do
338 0           return;
339             }
340              
341              
342             sub _parse_signature {
343 0     0     my ($sig, $arity) = @_;
344              
345             # extract types from signature, if any
346 0           my $arg_types;
347 0 0         if ($sig =~ s/\s*\((.*?)\)\s*$//) {
348 0           $arg_types = [ split(/\s*,\s*/, lc($1), -1) ];
349 0           s/^\s+// for @$arg_types;
350 0           s/\s+$// for @$arg_types;
351              
352             # if variadic, replace '...' marker with the appropriate number
353             # of copies of the preceding type name
354 0 0 0       if (@$arg_types and $arg_types->[-1] =~ s/\s*\.\.\.//) {
355 0           my $variadic_type = pop @$arg_types;
356 0           push @$arg_types, $variadic_type
357             until @$arg_types >= $arity;
358             }
359             }
360              
361             # the full name is what's left in sig
362 0           my $fullspname = $sig;
363              
364             # extract the function name and un-escape it to get the column name
365 0           (my $spname = $fullspname) =~ s/.*\.//; # remove schema, if any
366 0 0         if ($spname =~ s/^"(.*)"$/$1/) { # unescape
367 0           $spname =~ s/""/"/;
368             }
369              
370             # compose a normalized signature
371 0 0         my $stdsig = "$fullspname".
372             ($arg_types ? "(".join(",",@$arg_types).")" : "");
373              
374 0           return ($stdsig, $fullspname, $spname, $arg_types);
375             }
376              
377              
378             sub _mk_process_args {
379 0     0     my ($arg_types) = @_;
380              
381 0 0         return undef unless $arg_types;
382              
383             # return a closure that pre-processes the arguments of the call
384             # else undef if no argument pre-processing is required
385              
386 0           my $hooks;
387 0           my $i = 0;
388 0           for my $type (@$arg_types) {
389 0 0         if ($type =~ /\[/) { # ARRAY
390 0     0     $hooks->{$i} = sub { return ::encode_array_literal(shift) };
  0            
391             }
392 0           ++$i;
393             }
394              
395 0 0         return undef unless $hooks;
396              
397             my $sub = sub {
398 0     0     my @args = @_;
399 0           while ( my ($argidx, $preproc) = each %$hooks ) {
400 0           $args[$argidx] = $preproc->($args[$argidx]);
401             }
402 0           return @args;
403 0           };
404              
405 0           return $sub;
406             }
407              
408              
409             sub _mk_process_call {
410 0     0     my ($fullspname, $arity, $arg_types) = @_;
411              
412             # return a closure that will execute the query and return result ref
413              
414 0           my $placeholders = join ",", map { '$'.$_ } 1..$arity;
  0            
415 0           my $sql = "select * from $fullspname($placeholders)";
416 0 0         my $plan = eval { ::spi_prepare($sql, $arg_types ? @$arg_types : ()) };
  0            
417 0 0         if ($@) { # internal error, should never happen
418 0           chomp $@;
419 0           croak "$@ while preparing $sql";
420             }
421              
422             my $sub = sub {
423             # XXX need to catch exceptions from here and rethrow using croak
424             # to appear to come from the callers location (outside this package)
425 0 0   0     warn "calling $sql(@_) [@{$arg_types||[]}]"
  0 0          
426             if $debug;
427 0           return ::spi_exec_prepared($plan, @_)
428 0           };
429              
430 0           return $sub;
431             }
432              
433             1;
434              
435             =head1 LICENSE
436              
437             Copyright (C) Veesh Goldman 2020 -
438              
439             This library is free software; you can redistribute it and/or modify
440             it under the same terms as Perl itself.
441              
442             =head1 AUTHOR
443              
444             Veesh Goldman Erabbiveesh@gmail.comE
445              
446             =begin Pod::Coverage
447              
448             call
449              
450             =end Pod::Coverage