The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
#!perl

## Various stuff that does not go elsewhere

use 5.006;
use strict;
use warnings;
use Test::More;
use Data::Dumper;
use DBI;
use DBD::Pg;
use lib 't','.';
require 'dbdpg_test_setup.pl';
select(($|=1,select(STDERR),$|=1)[1]);

my $dbh = connect_database();

if (! $dbh) {
	plan skip_all => 'Connection to database failed, cannot continue testing';
}
plan tests => 77;

isnt ($dbh, undef, 'Connect to database for miscellaneous tests');

my $t = q{Method 'server_trace_flag' is available without a database handle};
my $num;
eval {
	$num = DBD::Pg->parse_trace_flag('NONE');
};
is ($@, q{}, $t);

$t='Method "server_trace_flag" returns undef on bogus argument';
is ($num, undef, $t);

$t=q{Method "server_trace_flag" returns 0x00000100 for DBI value 'SQL'};
$num = DBD::Pg->parse_trace_flag('SQL');
is ($num, 0x00000100, $t);

$t=q{Method "server_trace_flag" returns 0x01000000 for DBD::Pg flag 'pglibpq'};
$num = DBD::Pg->parse_trace_flag('pglibpq');
is ($num, 0x01000000, $t);

$t=q{Database handle method "server_trace_flag" returns undef on bogus argument};
$num = $dbh->parse_trace_flag('NONE');
is ($num, undef, $t);

$t=q{Database handle method "server_trace_flag" returns 0x00000100 for DBI value 'SQL'};
$num = $dbh->parse_trace_flag('SQL');
is ($num, 0x00000100, $t);

$t=q{Database handle method 'server_trace_flags' returns 0x01000100 for 'SQL|pglibpq'};
$num = $dbh->parse_trace_flags('SQL|pglibpq');
is ($num, 0x01000100, $t);

$t=q{Database handle method 'server_trace_flags' returns 0x03000100 for 'SQL|pglibpq|pgstart'};
$num = $dbh->parse_trace_flags('SQL|pglibpq|pgstart');
is ($num, 0x03000100, $t);

my $flagexp = 24;
my $sth = $dbh->prepare('SELECT 1');
for my $flag (qw/pglibpq pgstart pgend pgprefix pglogin pgquote/) {

	my $hex = 2**$flagexp++;
	$t = qq{Database handle method "server_trace_flag" returns $hex for flag $flag};
	$num = $dbh->parse_trace_flag($flag);
	is ($num, $hex, $t);

	$t = qq{Database handle method 'server_trace_flags' returns $hex for flag $flag};
	$num = $dbh->parse_trace_flags($flag);
	is ($num, $hex, $t);

	$t = qq{Statement handle method "server_trace_flag" returns $hex for flag $flag};
	$num = $sth->parse_trace_flag($flag);
	is ($num, $hex, $t);

	$t = qq{Statement handle method 'server_trace_flags' returns $hex for flag $flag};
	$num = $sth->parse_trace_flag($flag);
	is ($num, $hex, $t);
}

SKIP: {

	my $SQL = q{
CREATE OR REPLACE FUNCTION dbdpg_test_error_handler(TEXT)
RETURNS boolean
LANGUAGE plpgsql
AS $BC$
 DECLARE
   level ALIAS FOR $1;
 BEGIN 
  IF level ~* 'notice' THEN
    RAISE NOTICE 'RAISE NOTICE FROM dbdpg_test_error_handler';
  ELSIF level ~* 'warning' THEN
    RAISE WARNING 'RAISE WARNING FROM dbdpg_test_error_handler';
  ELSIF level ~* 'exception' THEN
    RAISE EXCEPTION 'RAISE EXCEPTION FROM dbdpg_test_error_handler';
  END IF;
  RETURN TRUE;
 END;
$BC$
};

	eval {
		$dbh->do($SQL);
		$dbh->commit();
	};
	if ($@) {
		$dbh->rollback();
		$@ and skip ('Cannot load function  for testing', 6);
	}

	$sth = $dbh->prepare('SELECT * FROM dbdpg_test_error_handler( ? )');

	is( $sth->err, undef, q{Statement attribute 'err' is initially undef});

  TODO: {
		local $TODO = q{Known bug: notice and warnings should set err to 6};

		for my $level (qw/notice warning/) {
			$sth->execute($level);
			is( $sth->err, 6, qq{Statement attribute 'err' set to 6 for level $level});
		}
	}

	$dbh->do(q{SET client_min_messages = 'FATAL'});

	for my $level (qw/exception/) {
		eval { $sth->execute($level);};
		is( $sth->err, 7, qq{Statement attribute 'err' set to 7 for level $level});
		$dbh->rollback;
	}

	for my $level (qw/normal/) {
		$sth->execute($level);
		is( $sth->err, undef, q{Statement attribute 'err' set to undef when no notices raised});
	}

	$sth->finish;

	is( $sth->err, undef, q{Statement attribute 'err' set to undef after statement finishes});

	$dbh->do('DROP FUNCTION dbdpg_test_error_handler(TEXT)') or die $dbh->errstr;
	$dbh->do('SET client_min_messages = NOTICE');
	$dbh->commit();

}

SKIP: {

	eval {
		require File::Temp;
	};
	$@ and skip ('Must have File::Temp to complete trace flag testing', 9);

	my ($fh,$filename) = File::Temp::tempfile('dbdpg_test_XXXXXX', SUFFIX => 'tst', UNLINK => 1);
	my ($flag, $info, $expected, $SQL);

	$t=q{Trace flag 'SQL' works as expected};
	$flag = $dbh->parse_trace_flags('SQL');
	$dbh->trace($flag, $filename);
	$SQL = q{SELECT 'dbdpg_flag_testing'};
	$dbh->do($SQL);
	$dbh->commit();
	$dbh->trace(0);
	seek $fh,0,0;
	{ local $/; ($info = <$fh>) =~ s/\r//go; }
	$expected = qq{begin;\n\n$SQL;\n\ncommit;\n\n};
	is ($info, $expected, $t);

	$t=q{Trace flag 'pglibpq' works as expected};
	seek $fh, 0, 0;
	truncate $fh, tell($fh);
	$dbh->trace($dbh->parse_trace_flag('pglibpq'), $filename);
	$dbh->do($SQL);
	$dbh->commit();
	$dbh->trace(0);
	seek $fh,0,0;
	{ local $/; ($info = <$fh>) =~ s/\r//go; }
	$expected = q{PQexec
PQresultStatus
PQresultErrorField
PQclear
PQexec
PQresultStatus
PQresultErrorField
PQntuples
PQclear
PQtransactionStatus
PQtransactionStatus
PQexec
PQresultStatus
PQresultErrorField
PQclear
};
	is ($info, $expected, $t);

	$t=q{Trace flag 'pgstart' works as expected};
	seek $fh, 0, 0;
	truncate $fh, tell($fh);
	$dbh->trace($dbh->parse_trace_flags('pgstart'), $filename);
	$dbh->do($SQL);
	$dbh->commit();
	$dbh->trace(0);
	seek $fh,0,0;
	{ local $/; ($info = <$fh>) =~ s/\r//go; }
	$expected = q{Begin pg_quickexec (query: SELECT 'dbdpg_flag_testing' async: 0 async_status: 0)
Begin _result (sql: begin)
Begin _sqlstate
Begin _sqlstate
Begin dbd_db_commit
Begin pg_db_rollback_commit (action: commit AutoCommit: 0 BegunWork: 0)
Begin PGTransactionStatusType
Begin _result (sql: commit)
Begin _sqlstate
};
	is ($info, $expected, $t);

	$t=q{Trace flag 'pgprefix' works as expected};
	seek $fh, 0, 0;
	truncate $fh, tell($fh);
	$dbh->trace($dbh->parse_trace_flags('pgstart|pgprefix'), $filename);
	$dbh->do($SQL);
	$dbh->commit();
	$dbh->trace(0);
	seek $fh,0,0;
	{ local $/; ($info = <$fh>) =~ s/\r//go; }
	$expected = q{dbdpg: Begin pg_quickexec (query: SELECT 'dbdpg_flag_testing' async: 0 async_status: 0)
dbdpg: Begin _result (sql: begin)
dbdpg: Begin _sqlstate
dbdpg: Begin _sqlstate
dbdpg: Begin dbd_db_commit
dbdpg: Begin pg_db_rollback_commit (action: commit AutoCommit: 0 BegunWork: 0)
dbdpg: Begin PGTransactionStatusType
dbdpg: Begin _result (sql: commit)
dbdpg: Begin _sqlstate
};
	is ($info, $expected, $t);

	$t=q{Trace flag 'pgend' works as expected};
	seek $fh, 0, 0;
	truncate $fh, tell($fh);
	$dbh->trace($dbh->parse_trace_flags('pgend'), $filename);
	$dbh->do($SQL);
	$dbh->commit();
	$dbh->trace(0);
	seek $fh,0,0;
	{ local $/; ($info = <$fh>) =~ s/\r//go; }
	$expected = q{End _sqlstate (imp_dbh->sqlstate: 00000)
End _sqlstate (status: 1)
End _result
End _sqlstate (imp_dbh->sqlstate: 00000)
End _sqlstate (status: 2)
End pg_quickexec (rows: 1, txn_status: 2)
End _sqlstate (imp_dbh->sqlstate: 00000)
End _sqlstate (status: 1)
End _result
End pg_db_rollback_commit (result: 1)
};
	is ($info, $expected, $t);

	$t=q{Trace flag 'pglogin' returns undef if no activity};
	seek $fh, 0, 0;
	truncate $fh, tell($fh);
	$dbh->trace($dbh->parse_trace_flags('pglogin'), $filename);
	$dbh->do($SQL);
	$dbh->commit();
	$dbh->trace(0);
	seek $fh,0,0;
	{ local $/; $info = <$fh>; }
	$expected = undef;
	is ($info, $expected, $t);

	$t=q{Trace flag 'pglogin' works as expected with DBD::Pg->parse_trace_flag()};
	$dbh->disconnect();
	my $flagval = DBD::Pg->parse_trace_flag('pglogin');
	seek $fh, 0, 0;
	truncate $fh, tell($fh);
	DBI->trace($flagval, $filename);
	$dbh = connect_database({nosetup => 1});
	$dbh->do($SQL);
	$dbh->disconnect();
	$dbh = connect_database({nosetup => 1});
	$dbh->disconnect();
	DBI->trace(0);
	seek $fh,0,0;
	{ local $/; ($info = <$fh>) =~ s/\r//go; }
	$expected = q{Login connection string: 
Connection complete
Disconnection complete
};
	$info =~ s/(Login connection string: ).+/$1/g;
	is ($info, "$expected$expected", $t);

	$t=q{Trace flag 'pglogin' works as expected with DBD::Pg->parse_trace_flag()};
	seek $fh, 0, 0;
	truncate $fh, tell($fh);
	DBI->trace($flagval, $filename);
	$dbh = connect_database({nosetup => 1});
	$dbh->disconnect();
	DBI->trace(0);
	seek $fh,0,0;
	{ local $/; ($info = <$fh>) =~ s/\r//go; }
	$expected = q{Login connection string: 
Connection complete
Disconnection complete
};
	$info =~ s/(Login connection string: ).+/$1/g;
	is ($info, "$expected", $t);

	$t=q{Trace flag 'pgprefix' and 'pgstart' appended to 'pglogin' work as expected};
	seek $fh, 0, 0;
	truncate $fh, tell($fh);
	DBI->trace($flagval, $filename);
	$dbh = connect_database({nosetup => 1});
	$dbh->do($SQL);
	$flagval += $dbh->parse_trace_flags('pgprefix|pgstart');
	$dbh->trace($flagval);
	$dbh->do($SQL);
	$dbh->trace(0);
	$dbh->rollback();
	seek $fh,0,0;
	{ local $/; ($info = <$fh>) =~ s/\r//go; }
	$expected = q{Login connection string: 
Connection complete
dbdpg: Begin pg_quickexec (query: SELECT 'dbdpg_flag_testing' async: 0 async_status: 0)
dbdpg: Begin _sqlstate
};
	$info =~ s/(Login connection string: ).+/$1/g;
	is ($info, "$expected", $t);

} ## end trace flag testing using File::Temp

#
# Test of the "data_sources" method
#

$t='The "data_sources" method did not throw an exception';
my @result;
eval {
	@result = DBI->data_sources('Pg');
};
is ($@, q{}, $t);

$t='The "data_sources" method returns a template1 listing';
if (! defined $result[0]) {
	fail ('The data_sources() method returned an empty list');
}
else {
	is (grep (/^dbi:Pg:dbname=template1$/, @result), '1', $t);
}

$t='The "data_sources" method returns undef when fed a bogus second argument';
@result = DBI->data_sources('Pg','foobar');
is (scalar @result, 0, $t);

$t='The "data_sources" method returns information when fed a valid port as the second arg';
my $port = $dbh->{pg_port};
@result = DBI->data_sources('Pg',"port=$port");
isnt ($result[0], undef, $t);

SKIP: {

	$t=q{The "data_sources" method returns information when 'dbi:Pg' is uppercased};

	if (! exists $ENV{DBI_DSN} or $ENV{DBI_DSN} !~ /pg/i) {
		skip 'Cannot test data_sources() DBI_DSN munging unless DBI_DSN is set', 2;
	}

	my $orig = $ENV{DBI_DSN};
	$ENV{DBI_DSN} =~ s/DBI:PG/DBI:PG/i;
	@result = DBI->data_sources('Pg');
	like ((join '' => @result), qr{template0}, $t);

	$t=q{The "data_sources" method returns information when 'DBI:' is mixed case};

	$ENV{DBI_DSN} =~ s/DBI:PG/dBi:pg/i;
	@result = DBI->data_sources('Pg');
	like ((join '' => @result), qr{template0}, $t);

	$ENV{DBI_DSN} = $orig;

}

#
# Test the use of $DBDPG_DEFAULT
#

$t=qq{Using \$DBDPG_DEFAULT ($DBDPG_DEFAULT) works};
$sth = $dbh->prepare(q{INSERT INTO dbd_pg_test (id, pname) VALUES (?,?)});
eval {
$sth->execute(600,$DBDPG_DEFAULT);
};
$sth->execute(602,123);
is ($@, q{}, $t);

#
# Test transaction status changes
#

$t='Raw ROLLBACK via do() resets the transaction status correctly';
$dbh->{AutoCommit} = 1;
$dbh->begin_work();
$dbh->do('SELECT 123');
eval { $dbh->do('ROLLBACK'); };
is ($@, q{}, $t);
eval { $dbh->begin_work(); };
is ($@, q{}, $t);

$t='Using dbh->commit() resets the transaction status correctly';
eval { $dbh->commit(); };
is ($@, q{}, $t);
eval { $dbh->begin_work(); };
is ($@, q{}, $t);

$t='Raw COMMIT via do() resets the transaction status correctly';
eval { $dbh->do('COMMIT'); };
is ($@, q{}, $t);
eval { $dbh->begin_work(); };
is ($@, q{}, $t);

$t='Calling COMMIT via prepare/execute resets the transaction status correctly';
$sth = $dbh->prepare('COMMIT');
$sth->execute();
eval { $dbh->begin_work(); };
is ($@, q{}, $t);

## Check for problems in pg_st_split_statement by having it parse long strings
my $problem;
diag 'Checking pg_st_split_statement. This may take a while...';
for my $length (0..16384) {
    my $sql = sprintf 'SELECT %*d', $length + 3, $length;
    my $cur_len = $dbh->selectrow_array($sql);
	next if $cur_len == $length;
	$problem = "length $length gave us a select of $cur_len";
	last;
}

if (defined $problem) {
	fail ("pg_st_split_statment failed: $problem");
}
else {
	pass ('pg_st_split_statement gave no problems with various lengths');
}

## Check for problems with insane number of placeholders
for my $ph (1..13) {
	my $total = 2**$ph;
	$t = "prepare/execute works with $total placeholders";
	my $sql = 'SELECT count(*) FROM pg_class WHERE relpages IN (' . ('?,' x $total);
	$sql =~ s/.$/\)/;
	$sth = $dbh->prepare($sql);
	my @arr = (1..$total);
	my $count = $sth->execute(@arr);
	is $count, 1, $t;
	$sth->finish();
}

## Make sure our mapping of char/SQL_CHAR/bpchar is working as expected
$dbh->do('CREATE TEMP TABLE tt (c_test int, char4 char(4))');

$sth = $dbh->prepare ('SELECT * FROM tt');
$sth->execute;
my @stt = @{$sth->{TYPE}};

$sth = $dbh->prepare('INSERT INTO tt VALUES (?,?)');

$sth->bind_param(1, undef, $stt[0]); ## 4
$sth->bind_param(2, undef, $stt[1]); ## 1 aka SQL_CHAR
$sth->execute(2, '0301');

my $SQL = 'SELECT char4 FROM tt';
my $result = $dbh->selectall_arrayref($SQL)->[0][0];

$t = q{Using bind_param with type 1 yields a correct bpchar value};
is( $result, '0301', $t);

cleanup_database($dbh,'test');
$dbh->disconnect();