#!/usr/bin/perl -w -I./t
# $Id: 70execute_array.t 15016 2011-11-24 09:26:38Z mjevans $
# loads of execute_array and execute_for_fetch tests
use Test::More;
use strict;
use Data::Dumper;
$| = 1;
my $has_test_nowarnings = 1;
eval "require Test::NoWarnings";
$has_test_nowarnings = undef if $@;
my $table = 'PERL_DBD_execute_array';
my $table2 = 'PERL_DBD_execute_array2';
my @captured_error; # values captured in error handler
my $dbh;
my @p1 = (1,2,3,4,5);
my @p2 = qw(one two three four five);
my $fetch_row = 0;
use DBI qw(:sql_types);
#use_ok('ODBCTEST');
use_ok('Data::Dumper');
BEGIN {
plan skip_all => "DBI_DSN is undefined"
if (!defined $ENV{DBI_DSN});
}
END {
if ($dbh) {
drop_table($dbh);
$dbh->disconnect();
}
Test::NoWarnings::had_no_warnings()
if ($has_test_nowarnings);
done_testing();
}
sub error_handler
{
@captured_error = @_;
note("***** error handler called *****");
0; # pass errors on
}
sub create_table
{
my $dbh = shift;
eval {
$dbh->do(qq/create table $table (a integer primary key, b char(20))/);
};
if ($@) {
diag("Failed to create test table $table - $@");
return 0;
}
eval {
$dbh->do(qq/create table $table2 (a integer primary key, b char(20))/);
};
if ($@) {
diag("Failed to create test table $table2 - $@");
return 0;
}
my $sth = $dbh->prepare(qq/insert into $table2 values(?,?)/);
for (my $row = 0; $row < @p1; $row++) {
$sth->execute($p1[$row], $p2[$row]);
}
1;
}
sub drop_table
{
my $dbh = shift;
eval {
local $dbh->{PrintError} = 0;
local $dbh->{PrintWarn} = 0;
$dbh->do(qq/drop table $table/);
$dbh->do(qq/drop table $table2/);
};
note("Table dropped");
}
# clear the named table of rows
sub clear_table
{
$_[0]->do(qq/delete from $_[1]/);
}
# check $table contains the data in $c1, $c2 which are arrayrefs of values
sub check_data
{
my ($dbh, $c1, $c2) = @_;
my $data = $dbh->selectall_arrayref(qq/select * from $table order by a/);
my $row = 0;
foreach (@$data) {
is($_->[0], $c1->[$row], "row $row p1 data");
is($_->[1], $c2->[$row], "row $row p2 data");
$row++;
}
}
sub check_tuple_status
{
my ($tsts, $expected) = @_;
note(Data::Dumper->Dump([$tsts], [qw(ArrayTupleStatus)]));
my $row = 0;
foreach my $s (@$tsts) {
if (ref($expected->[$row])) {
is(ref($s), 'ARRAY', 'array in array tuple status');
is(scalar(@$s), 3, '3 elements in array tuple status error');
} else {
if ($s == -1) {
pass("row $row tuple status unknown");
} else {
is($s, $expected->[$row], "row $row tuple status");
}
}
$row++
}
}
# insert might return 'mas' which means the caller said the test
# required Multiple Active Statements and the driver appeared to not
# support MAS.
sub insert
{
my ($dbh, $sth, $ref) = @_;
die "need hashref arg" if (!$ref || (ref($ref) ne 'HASH'));
note("insert " . join(", ", map {"$_ = ". DBI::neat($ref->{$_})} keys %$ref ));
# DBD::Oracle supports MAS don't compensate for it not
if ($ref->{requires_mas} && $dbh->{Driver}->{Name} eq 'Oracle') {
delete $ref->{requires_mas};
}
@captured_error = ();
if ($ref->{raise}) {
$sth->{RaiseError} = 1;
} else {
$sth->{RaiseError} = 0;
}
my (@tuple_status, $sts, $total_affected);
$sts = 999999; # to ensure it is overwritten
$total_affected = 999998;
if ($ref->{array_context}) {
eval {
if ($ref->{params}) {
($sts, $total_affected) =
$sth->execute_array({ArrayTupleStatus => \@tuple_status},
@{$ref->{params}});
} elsif ($ref->{fetch}) {
($sts, $total_affected) =
$sth->execute_array(
{ArrayTupleStatus => \@tuple_status,
ArrayTupleFetch => $ref->{fetch}});
} else {
($sts, $total_affected) =
$sth->execute_array({ArrayTupleStatus => \@tuple_status});
}
};
} else {
eval {
if ($ref->{params}) {
$sts =
$sth->execute_array({ArrayTupleStatus => \@tuple_status},
@{$ref->{params}});
} else {
$sts =
$sth->execute_array({ArrayTupleStatus => \@tuple_status});
}
};
}
if ($ref->{error} && $ref->{raise}) {
ok($@, 'error in execute_array eval');
} else {
if ($ref->{requires_mas} && $@) {
diag("\nThis test died with $@");
diag("It requires multiple active statement support in the driver and I cannot easily determine if your driver supports MAS. Ignoring the rest of this test.");
foreach (@tuple_status) {
if (ref($_)) {
diag(join(",", @$_));
}
}
return 'mas';
}
ok(!$@, 'no error in execute_array eval') or note($@);
}
$dbh->commit if $ref->{commit};
if (!$ref->{raise} || ($ref->{error} == 0)) {
if (exists($ref->{sts})) {
is($sts, $ref->{sts},
"execute_array returned " . DBI::neat($sts) . " rows executed");
}
if (exists($ref->{affected}) && $ref->{array_context}) {
is($total_affected, $ref->{affected},
"total affected " . DBI::neat($total_affected))
}
}
if ($ref->{raise}) {
if ($ref->{error}) {
ok(scalar(@captured_error) > 0, "error captured");
} else {
is(scalar(@captured_error), 0, "no error captured");
}
}
if ($ref->{sts}) {
is(scalar(@tuple_status), (($ref->{sts} eq '0E0') ? 0 : $ref->{sts}),
"$ref->{sts} rows in tuple_status");
}
if ($ref->{tuple}) {
check_tuple_status(\@tuple_status, $ref->{tuple});
}
return;
}
# simple test on ensure execute_array with no errors:
# o checks returned status and affected is correct
# o checks ArrayTupleStatus is correct
# o checks no error is raised
# o checks rows are inserted
# o run twice with AutoCommit on/off
# o checks if less values are specified for one parameter the right number
# of rows are still inserted and NULLs are placed in the missing rows
# checks binding via bind_param_array and adding params to execute_array
# checks binding no parameters at all
sub simple
{
my ($dbh, $ref) = @_;
note('simple tests ' . join(", ", map {"$_ = $ref->{$_}"} keys %$ref ));
note(" all param arrays the same size");
foreach my $commit (1,0) {
note(" Autocommit: $commit");
clear_table($dbh, $table);
$dbh->begin_work if !$commit;
my $sth = $dbh->prepare(qq/insert into $table values(?,?)/);
$sth->bind_param_array(1, \@p1);
$sth->bind_param_array(2, \@p2);
insert($dbh, $sth,
{ commit => !$commit, error => 0, sts => 5, affected => 5,
tuple => [1, 1, 1, 1, 1], %$ref});
check_data($dbh, \@p1, \@p2);
}
note " Not all param arrays the same size";
clear_table($dbh, $table);
my $sth = $dbh->prepare(qq/insert into $table values(?,?)/);
$sth->bind_param_array(1, \@p1);
$sth->bind_param_array(2, [qw(one)]);
insert($dbh, $sth, {commit => 0, error => 0,
raise => 1, sts => 5, affected => 5,
tuple => [1, 1, 1, 1, 1], %$ref});
check_data($dbh, \@p1, ['one', undef, undef, undef, undef]);
note " Not all param arrays the same size with bind on execute_array";
clear_table($dbh, $table);
$sth = $dbh->prepare(qq/insert into $table values(?,?)/);
insert($dbh, $sth, {commit => 0, error => 0,
raise => 1, sts => 5, affected => 5,
tuple => [1, 1, 1, 1, 1], %$ref,
params => [\@p1, [qw(one)]]});
check_data($dbh, \@p1, ['one', undef, undef, undef, undef]);
note " no parameters";
clear_table($dbh, $table);
$sth = $dbh->prepare(qq/insert into $table values(?,?)/);
insert($dbh, $sth, {commit => 0, error => 0,
raise => 1, sts => '0E0', affected => 0,
tuple => [], %$ref,
params => [[], []]});
check_data($dbh, \@p1, ['one', undef, undef, undef, undef]);
}
# error test to ensure correct behavior for execute_array when it errors:
# o execute_array of 5 inserts with last one failing
# o check it raises an error
# o check caught error is passed on from handler for eval
# o check returned status and affected rows
# o check ArrayTupleStatus
# o check valid inserts are inserted
# o execute_array of 5 inserts with 2nd last one failing
# o check it raises an error
# o check caught error is passed on from handler for eval
# o check returned status and affected rows
# o check ArrayTupleStatus
# o check valid inserts are inserted
sub error
{
my ($dbh, $ref) = @_;
die "need hashref arg" if (!$ref || (ref($ref) ne 'HASH'));
note('error tests ' . join(", ", map {"$_ = $ref->{$_}"} keys %$ref ));
{
note("Last row in error");
clear_table($dbh, $table);
my $sth = $dbh->prepare(qq/insert into $table values(?,?)/);
my @pe1 = @p1;
$pe1[-1] = 1;
$sth->bind_param_array(1, \@pe1);
$sth->bind_param_array(2, \@p2);
insert($dbh, $sth, {commit => 0, error => 1, sts => undef,
affected => undef, tuple => [1, 1, 1, 1, []],
%$ref});
check_data($dbh, [@pe1[0..4]], [@p2[0..4]]);
}
{
note("2nd last row in error");
clear_table($dbh, $table);
my $sth = $dbh->prepare(qq/insert into $table values(?,?)/);
my @pe1 = @p1;
$pe1[-2] = 1;
$sth->bind_param_array(1, \@pe1);
$sth->bind_param_array(2, \@p2);
insert($dbh, $sth, {commit => 0, error => 1, sts => undef,
affected => undef, tuple => [1, 1, 1, [], 1], %$ref});
check_data($dbh, [@pe1[0..2],$pe1[4]], [@p2[0..2], $p2[4]]);
}
}
sub fetch_sub
{
note("fetch_sub $fetch_row");
if ($fetch_row == @p1) {
note('returning undef');
$fetch_row = 0;
return;
}
return [$p1[$fetch_row], $p2[$fetch_row++]];
}
# test insertion via execute_array and ArrayTupleFetch
sub row_wise
{
my ($dbh, $ref) = @_;
note("row_size via execute_for_fetch");
# Populate the first table via a ArrayTupleFetch which points to a sub
# returning rows
$fetch_row = 0; # reset fetch_sub to start with first row
clear_table($dbh, $table);
my $sth = $dbh->prepare(qq/insert into $table values(?,?)/);
insert($dbh, $sth,
{commit => 0, error => 0, sts => 5, affected => 5,
tuple => [1, 1, 1, 1, 1], %$ref,
fetch => \&fetch_sub});
# NOTE: The following test requires Multiple Active Statements. Although
# I can find ODBC drivers which do this it is not easy (if at all possible)
# to know if an ODBC driver can handle MAS or not. If it errors the
# driver probably does not have MAS so the error is ignored and a
# diagnostic is output. Exceptions are DBD::Oracle which definitely does
# support MAS.
# The data pushed into the first table is retrieved via ArrayTupleFetch
# from the second table by passing an executed select statement handle into
# execute_array.
note("row_size via select");
clear_table($dbh, $table);
$sth = $dbh->prepare(qq/insert into $table values(?,?)/);
my $sth2 = $dbh->prepare(qq/select * from $table2/);
# some drivers issue warnings when mas fails and this causes
# Test::NoWarnings to output something when we already found
# the test failed and captured it.
# e.g., some ODBC drivers cannot do MAS and this test is then expected to
# fail but we ignore the failure. Unfortunately in failing DBD::ODBC will
# issue a warning in addition to the fail
$sth->{Warn} = 0;
$sth->{Warn} = 0;
ok($sth2->execute, 'execute on second table') or diag($sth2->errstr);
ok($sth2->{Executed}, 'second statement is in executed state');
my $res = insert($dbh, $sth,
{commit => 0, error => 0, sts => 5, affected => 5,
tuple => [1, 1, 1, 1, 1], %$ref,
fetch => $sth2, requires_mas => 1});
return if $res && $res eq 'mas'; # aborted , does not seem to support MAS
check_data($dbh, \@p1, \@p2);
}
# test updates
# updates are special as you can update more rows than there are parameter rows
sub update
{
my ($dbh, $ref) = @_;
note("update test");
# populate the first table with the default 5 rows using a ArrayTupleFetch
$fetch_row = 0;
clear_table($dbh, $table);
my $sth = $dbh->prepare(qq/insert into $table values(?,?)/);
insert($dbh, $sth,
{commit => 0, error => 0, sts => 5, affected => 5,
tuple => [1, 1, 1, 1, 1], %$ref,
fetch => \&fetch_sub});
check_data($dbh, \@p1, \@p2);
# update all rows b column to 'fred' checking rows affected is 5
$sth = $dbh->prepare(qq/update $table set b = ? where a = ?/);
# NOTE, this also checks you can pass a scalar to bind_param_array
$sth->bind_param_array(1, 'fred');
$sth->bind_param_array(2, \@p1);
insert($dbh, $sth,
{commit => 0, error => 0, sts => 5, affected => 5,
tuple => [1, 1, 1, 1, 1], %$ref});
check_data($dbh, \@p1, [qw(fred fred fred fred fred)]);
# update 4 rows column b to 'dave' checking rows affected is 4
$sth = $dbh->prepare(qq/update $table set b = ? where a = ?/);
# NOTE, this also checks you can pass a scalar to bind_param_array
$sth->bind_param_array(1, 'dave');
my @pe1 = @p1;
$pe1[-1] = 10; # non-existant row
$sth->bind_param_array(2, \@pe1);
insert($dbh, $sth,
{commit => 0, error => 0, sts => 5, affected => 4,
tuple => [1, 1, 1, 1, '0E0'], %$ref});
check_data($dbh, \@p1, [qw(dave dave dave dave fred)]);
# now change all rows b column to 'pete' - this will change all 5
# rows even though we have 2 rows of parameters so we can see if
# the rows affected is > parameter rows
$sth = $dbh->prepare(qq/update $table set b = ? where b like ?/);
# NOTE, this also checks you can pass a scalar to bind_param_array
$sth->bind_param_array(1, 'pete');
$sth->bind_param_array(2, ['dave%', 'fred%']);
insert($dbh, $sth,
{commit => 0, error => 0, sts => 2, affected => 5,
tuple => [4, 1], %$ref});
check_data($dbh, \@p1, [qw(pete pete pete pete pete)]);
}
diag("\n\nNOTE: This is an experimental test. It does not test anything in DBD::ODBC specifically but it does test execute_array and execute_for_fetch which are implemented in DBI. If it fails it should not stop you installing DBD::ODBC but if it fails with an error other than something indicating 'connection busy' it would be worth rerunning it with TEST_VERBOSE set or using prove and sending the results to the dbi-users mailing list.\n\n");
$dbh = DBI->connect();
unless($dbh) {
BAIL_OUT("Unable to connect to the database $DBI::errstr\nTests skipped.\n");
exit 0;
}
note("Using driver $dbh->{Driver}->{Name}");
# this test uses multiple active statements
# if we recognise the driver and it supports MAS enable it
my $driver_name = $dbh->get_info(6) || '';
if (($driver_name eq 'libessqlsrv.so') ||
($driver_name =~ /libsqlncli/)) {
my $dsn = $ENV{DBI_DSN};
if ($dsn !~ /^dbi:ODBC:DSN=/ && $dsn !~ /DRIVER=/i) {
my @a = split(q/:/, $ENV{DBI_DSN});
$dsn = join(q/:/, @a[0..($#a - 1)]) . ":DSN=" . $a[-1];
}
$dsn .= ";MARS_Connection=yes";
$dbh->disconnect;
$dbh = DBI->connect($dsn, $ENV{DBI_USER}, $ENV{DBI_PASS});
}
#$dbh->{ora_verbose} = 5;
$dbh->{RaiseError} = 1;
$dbh->{PrintError} = 0;
$dbh->{ChopBlanks} = 1;
$dbh->{HandleError} = \&error_handler;
$dbh->{AutoCommit} = 1;
drop_table($dbh);
ok(create_table($dbh), "create test table") or exit 1;
simple($dbh, {array_context => 1, raise => 1});
simple($dbh, {array_context => 0, raise => 1});
error($dbh, {array_context => 1, raise => 1});
error($dbh, {array_context => 0, raise => 1});
error($dbh, {array_context => 1, raise => 0});
error($dbh, {array_context => 0, raise => 0});
row_wise($dbh, {array_context => 1, raise => 1});
update($dbh, {array_context => 1, raise => 1});