use v5.36; use Test::More; plan skip_all => $@ if !eval { require FU::PG; } && $@ =~ /Unable to load libpq/; plan skip_all => 'Please set FU_TEST_DB to a PostgreSQL connection string to run these tests' if !$ENV{FU_TEST_DB}; sub okerr($sev, $act, $msg) { is ref $@, 'FU::PG::error'; is $@->{severity}, $sev; is $@->{action}, $act; like "$@", $msg; } ok !eval { FU::PG->connect("invalid") }; okerr FATAL => connect => qr/missing "=" after "invalid"/; ok FU::PG::lib_version() > 100000; my $conn = FU::PG->connect($ENV{FU_TEST_DB}); $conn->_debug_trace(0); is ref $conn, 'FU::PG::conn'; ok $conn->server_version > 100000; is $conn->lib_version, FU::PG::lib_version(); subtest '$conn->exec', sub { ok !eval { $conn->exec('COPY (SELECT 1) TO STDOUT'); }; okerr FATAL => exec => qr/unexpected status code/; ok !eval { $conn->exec('SELEXT'); }; okerr ERROR => exec => qr/syntax error/; ok !defined $conn->exec(''); is $conn->exec('SELECT 1'), 1; ok !eval { $conn->q('SELEXT')->params; }; okerr ERROR => prepare => qr/syntax error/; }; subtest '$st prepare & exec', sub { { my $st = $conn->q('SELECT 1'); is_deeply $st->params, []; is_deeply $st->columns, [{ name => '?column?', oid => 23 }]; is $conn->exec('SELECT 1 FROM pg_prepared_statements'), 1; is $st->exec, 1; } is $conn->exec('SELECT 1 FROM pg_prepared_statements'), 0; ok !eval { $conn->q('SELECT 1', 1)->exec; 1 }; okerr ERROR => exec => qr/bind message supplies 1 parameters, but prepared statement/; ok !eval { $conn->q('SELECT $1')->exec; 1 }; okerr ERROR => exec => qr/bind message supplies 0 parameters, but prepared statement/; }; subtest '$st->val', sub { ok !eval { $conn->q('SELECT')->val; 1 }; like $@, qr/on query returning no data/; ok !eval { $conn->q('SELECT 1, 2')->val; 1 }; like $@, qr/on query returning more than one column/; ok !eval { $conn->q('SELECT 1 UNION SELECT 2')->val; 1 }; like $@, qr/on query returning more than one row/; ok !defined $conn->q('SELECT 1 WHERE false')->val; ok !defined $conn->q('SELECT null')->val; is $conn->q('SELECT $1::text', "\x{1F603}")->val, "\x{1F603}"; }; subtest '$st->rowl', sub { ok !eval { $conn->q('SELECT 1 WHERE false')->rowl; 1 }; like $@, qr/on query returning zero rows/; ok !eval { $conn->q('SELECT 1 UNION SELECT 2')->rowl; 1 }; like $@, qr/on query returning more than one row/; ok !eval { $conn->q('SELEXT')->rowl; 1; }; is scalar $conn->q('SELECT')->rowl, 0; is scalar $conn->q('SELECT 1, 2')->rowl, 2; is_deeply [$conn->q('SELECT')->rowl], []; is_deeply [$conn->q('SELECT 1, 2')->rowl], [1, 2]; }; subtest '$st->rowa', sub { ok !eval { $conn->q('SELECT 1 WHERE false')->rowa; 1 }; like $@, qr/on query returning zero rows/; ok !eval { $conn->q('SELECT 1 UNION SELECT 2')->rowa; 1 }; like $@, qr/on query returning more than one row/; ok !eval { $conn->q('SELEXT')->rowa; 1; }; is_deeply $conn->q('SELECT')->rowa, []; is_deeply $conn->q('SELECT 1, 2')->rowa, [1, 2]; }; subtest '$st->rowh', sub { ok !eval { $conn->q('SELECT 1 WHERE false')->rowh; 1 }; like $@, qr/on query returning zero rows/; ok !eval { $conn->q('SELECT 1 UNION SELECT 2')->rowh; 1 }; like $@, qr/on query returning more than one row/; ok !eval { $conn->q('SELECT 1 as a, 2 as a')->rowh; 1 }; like $@, qr/Query returns multiple columns with the same name/; ok !eval { $conn->q('SELEXT')->rowh; 1; }; is_deeply $conn->q('SELECT')->rowh, {}; is_deeply $conn->q('SELECT 1 as a, 2 as b')->rowh, {a => 1, b => 2}; }; { my $st = $conn->q("SELECT \$1::int AS a, \$2::char(5) AS \"\x{1F603}\""); undef $conn; # statement keeps the connection alive is_deeply $st->params, [ { oid => 23 }, { oid => 1042 } ]; is_deeply $st->columns, [ { oid => 23, name => 'a' }, { oid => 1042, name => "\x{1F603}", typemod => 9 }, ]; } done_testing;