The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
CREATE FUNCTION call_test10() RETURNS text
LANGUAGE plperlu AS $func$

use PostgreSQL::PLPerl::Call;

# XXX server process needs to have a working STDOUT
# else "ERROR:  Can't dup STDOUT:  Bad file descriptor" error
# from Test::Builder.

use Test::More 'no_plan';
my $Test = Test::More->builder;
$Test->output(\my $test_output);

my $row;
my @ary;

# ====== single-value single-row function ======

# --- no arguments
like call('pi()'), qr/^3.14159/;
# with schema
like call('pg_catalog.pi()'), qr/^3.14159/;
# without parens/types
like call('pi'),              qr/^3.14159/;
like call('pg_catalog.pi'),   qr/^3.14159/;

# bad calls
eval { call('pi()', 42) };
like $@, qr/there is no parameter \$1/;

# --- method call syntax
like PG->pi, qr/^3.14159/;
# bad calls
eval { PG->pi(42) };
like $@, qr/there is no parameter \$1/;

# --- one argument, simple types
is call('abs(int)', -42), 42;
is call('abs(float)', -42.5), '42.5';
is call('bit_length(text)', 'jose'), 32;

# --- one argument, multi-word types
is call('abs(double precision)', -42.5), '42.5';
is call('bit_length(character varying(90))', 'jose'), 32;

# --- lock calls
call('pg_try_advisory_lock_shared(bigint)', 1234);
call('pg_advisory_unlock_all()');

# bad calls
eval { call('abs(int)', -42.5) };
like $@, qr/invalid input syntax for integer/;
eval { call('abs(text)', -42.5) };
like $@, qr/function abs\(text\) does not exist/;
eval { call('abs(nonesuchtype)', -42.5) };
like $@, qr/type "nonesuchtype" does not exist/;

# --- multi-argument, simple types
is call('trunc(numeric,int)', 42.4382, 2), '42.43';

# --- unusual types from strings
is call('host(inet)',    '192.168.1.5/24'), '192.168.1.5';
is call('network(inet)', '192.168.1.5/24'), '192.168.1.0/24';
is call('abbrev(cidr)',  '10.1.0.0/16'),    '10.1/16';
is call('numnode(tsquery)', '(fat & rat) | cat'), 5;

spi_exec_query('create temp sequence seqn1 start with 42');
is call('nextval(regclass)', 'seqn1'), 42;
is call('nextval(text)',     'seqn1'), 43;

is call('string_to_array(text, text)', 'xx~^~yy~^~zz', '~^~'), '{xx,yy,zz}';

# --- array and array reference handling
is call('array_dims(text[])', '{a,b,c}'), '[1:3]';
is call('array_dims(text[])', [qw(a b c)]), '[1:3]';
is call('array_dims(text[])', [[1,2,3], [4,5,6]]), '[1:2][1:3]';
is call('array_cat(int[], int[])', [1,2,3], [2,1]), '{1,2,3,2,1}';


# ====== single-value multi-row function ======

@ary = call('unnest(int[])', '{11,12,13}');
is scalar @ary, 3;
is_deeply \@ary, [ 11, 12, 13 ];

@ary = call('generate_series(int,int)', 10, 19);
is scalar @ary, 10;
is_deeply \@ary, [ 10..19 ];

@ary = call('generate_series(int,int,int)', 10, 19, 4);
is_deeply \@ary, [ 10, 14, 18 ];

@ary = call('generate_series(timestamp,timestamp,interval)', '2008-03-01', '2008-03-02', '12 hours');
is_deeply \@ary, [ '2008-03-01 00:00:00', '2008-03-01 12:00:00', '2008-03-02 00:00:00' ];

# bad calls
eval { scalar call('generate_series(int,int)', 10, 19) };
like $@, qr/returned more than one row/;

# ====== multi-value (record) returning functions ======

@ary = call('pg_get_keywords()');
cmp_ok scalar @ary, '>', 200;
ok $row = $ary[0];
is ref $row, 'HASH';
ok exists $row->{word},    'should contain a word column';
ok exists $row->{catcode}, 'should contain a catcode column';
ok exists $row->{catdesc}, 'should contain a catdesc column';

# single-record
spi_exec_query(q{
	create or replace function f1(out r1 text, out r2 int) language plperl as $$
		return { r1=>10, r2=>11 };
	$$
});
@ary = PG->f1();
is scalar @ary, 1;
ok $row = $ary[0];
is $row->{r1}, 10;
is $row->{r2}, 11;
spi_exec_query('drop function f1()');

# multi-record
spi_exec_query(q{
	create or replace function f2() returns table (r1 text, r2 int) language plperl as $$
		return_next { r1 => $_, r2 => $_+1 } for 1..5;
		return undef;
	$$
});
@ary = PG->f2();
is scalar @ary, 5;
is $ary[-1]->{r1}, 5;
is $ary[-1]->{r2}, 6;
spi_exec_query('drop function f2()');

# ====== functions with defaults ======

spi_exec_query(q{
	create or replace function f3(int default 42) returns int language plperl as $$
		return shift() + 1;
	$$
});
is call('f3()'), 43;
spi_exec_query('drop function f3(int)');

# ====== functions with strange names ======

spi_exec_query(q{create or replace function "q 1"() returns int language plperl as 'return 42'});
is call('"q 1"'), 42;
spi_exec_query('drop function "q 1"()');

# ====== functions variadic args ======

spi_exec_query(q{
	create or replace function f4(VARIADIC numeric[]) returns float language plperlu as $$
		use PostgreSQL::PLPerl::Call;
		my $sum = 100;
		$sum += $_ for call('unnest(numeric[])', $_[0]);
		return $sum;
	$$
});
# call variadic with explicit number of args in the signature
is call('f4(numeric, numeric)',          10,11   ), 121;
is call('f4(numeric, numeric, numeric)', 10,11,12), 133;

# call variadic using '...' in the signature
is call('f4(numeric, numeric ...)',     10,11,12), 133;
is call('f4(numeric ...)',              10,11,12), 133;
is call('f4(numeric ...)',              10,11   ), 121;
is call('f4(numeric ...)',              10      ), 110;

spi_exec_query('drop function f4(variadic numeric[])');

# === finish up

$Test->_ending;
my $failed = grep { !$_ } Test::More->builder->summary;
warn "Test results:\n$test_output" if $failed;

return ($failed) ? "FAIL" : "PASS";

$func$;