PostgreSQL-PLPerl-Call
view release on metacpan or search on metacpan
lib/PostgreSQL/PLPerl/Call.pm view on Meta::CPAN
=head2 Limitations and Caveats
Requires PostgreSQL 9.0 or later.
Types that contain a comma can't be used in the call signature. That's not a
problem in practice as it only affects 'C<numeric(p,s)>' and 'C<decimal(p,s)>'
and the 'C<,s>' part isn't needed. Typically the 'C<(p,s)>' portion isn't used in
signatures.
The return value of functions that have a C<void> return type should not be
relied upon, naturally.
=head2 Author and Copyright
Tim Bunce L<http://www.tim.bunce.name>
Copyright (c) Tim Bunce, Ireland, 2010. All rights reserved.
You may use and distribute on the same terms as Perl 5.10.1.
With thanks to L<http://www.TigerLead.com> for sponsoring development.
=cut
use strict;
use warnings;
use Exporter;
use Carp;
our @ISA = qw(Exporter);
our @EXPORT = qw(call PG);
my %sig_cache;
our $debug = 0;
# encapsulated package to provide an AUTOLOAD interface to call()
use constant PG => do {
package PostgreSQL::PLPerl::Call::PG;
our $VERSION = '1.007';
sub AUTOLOAD {
#(my $function = our $AUTOLOAD) =~ s/.*:://;
our $AUTOLOAD =~ s/.*:://;
shift;
return PostgreSQL::PLPerl::Call::call($AUTOLOAD, @_);
}
__PACKAGE__;
};
sub call {
my $sig = shift;
my $arity = scalar @_; # argument count to handle variadic subs
my $how = $sig_cache{"$sig.$arity"} ||= do {
# get a normalized signature to recheck the cache with
# and also extract the SP name and argument types
my ($stdsig, $fullspname, $spname, $arg_types) = _parse_signature($sig, $arity)
or croak "Can't parse '$sig'";
warn "parsed call($sig) => $stdsig\n"
if $debug;
# recheck the cache with with the normalized signature
$sig_cache{"$stdsig.$arity"} ||= [ # else a new entry (for both caches)
$spname, # is name of column for single column results
scalar _mk_process_args($arg_types),
scalar _mk_process_call($fullspname, $arity, $arg_types),
$fullspname, # is name used in SQL to make the call
$stdsig,
];
};
my ($spname, $prepargs, $callsub) = @$how;
my $rv = $callsub->( $prepargs ? $prepargs->(@_) : @_ );
my $rows = $rv->{rows};
my $row1 = $rows->[0] # peek at first row
or return; # no row: undef in scalar context else empty list
my $is_single_column = (keys %$row1 == 1 and exists $row1->{$spname});
if (wantarray) { # list context - all rows
return map { $_->{$spname} } @$rows if $is_single_column;
return @$rows;
}
elsif (defined wantarray) { # scalar context - single row
croak "$sig was called in scalar context but returned more than one row"
if @$rows > 1;
return $row1->{$spname} if $is_single_column;
return $row1;
}
# else void context - nothing to do
return;
}
sub _parse_signature {
my ($sig, $arity) = @_;
# extract types from signature, if any
my $arg_types;
if ($sig =~ s/\s*\((.*?)\)\s*$//) {
$arg_types = [ split(/\s*,\s*/, lc($1), -1) ];
s/^\s+// for @$arg_types;
s/\s+$// for @$arg_types;
# if variadic, replace '...' marker with the appropriate number
# of copies of the preceding type name
if (@$arg_types and $arg_types->[-1] =~ s/\s*\.\.\.//) {
my $variadic_type = pop @$arg_types;
push @$arg_types, $variadic_type
until @$arg_types >= $arity;
}
}
# the full name is what's left in sig
my $fullspname = $sig;
# extract the function name and un-escape it to get the column name
(my $spname = $fullspname) =~ s/.*\.//; # remove schema, if any
if ($spname =~ s/^"(.*)"$/$1/) { # unescape
$spname =~ s/""/"/;
}
# compose a normalized signature
my $stdsig = "$fullspname".
($arg_types ? "(".join(",",@$arg_types).")" : "");
return ($stdsig, $fullspname, $spname, $arg_types);
}
sub _mk_process_args {
my ($arg_types) = @_;
return undef unless $arg_types;
# return a closure that pre-processes the arguments of the call
# else undef if no argument pre-processing is required
my $hooks;
my $i = 0;
for my $type (@$arg_types) {
if ($type =~ /\[/) { # ARRAY
$hooks->{$i} = sub { return ::encode_array_literal(shift) };
}
++$i;
}
return undef unless $hooks;
my $sub = sub {
my @args = @_;
while ( my ($argidx, $preproc) = each %$hooks ) {
$args[$argidx] = $preproc->($args[$argidx]);
}
return @args;
};
return $sub;
}
sub _mk_process_call {
my ($fullspname, $arity, $arg_types) = @_;
# return a closure that will execute the query and return result ref
my $placeholders = join ",", map { '$'.$_ } 1..$arity;
my $sql = "select * from $fullspname($placeholders)";
my $plan = eval { ::spi_prepare($sql, $arg_types ? @$arg_types : ()) };
if ($@) { # internal error, should never happen
chomp $@;
croak "$@ while preparing $sql";
}
my $sub = sub {
# XXX need to catch exceptions from here and rethrow using croak
# to appear to come from the callers location (outside this package)
warn "calling $sql(@_) [@{$arg_types||[]}]"
if $debug;
return ::spi_exec_prepared($plan, @_)
};
return $sub;
}
1;
( run in 1.329 second using v1.01-cache-2.11-cpan-71847e10f99 )