The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
#!perl -w
$|=1;

use strict;

use Test::More tests => 55;

## ----------------------------------------------------------------------------
## 15array.t
## ----------------------------------------------------------------------------
# 
## ----------------------------------------------------------------------------

BEGIN {
	use_ok('DBI');
}

# create a database handle
my $dbh = DBI->connect("dbi:Sponge:dummy", '', '', { 
    RaiseError => 1, 
    ShowErrorStatement => 1,
    AutoCommit => 1 
});

# check that our db handle is good
isa_ok($dbh, "DBI::db");

my $rv;
my $rows         = [];
my $tuple_status = [];
my $dumped;

my $sth = $dbh->prepare("insert", {
		rows          => $rows,   # where to 'insert' (push) the rows
		NUM_OF_PARAMS => 4,
		execute_hook  => sub {    # DBD::Sponge hook to make certain data trigger an error for that row
			local $^W;
			return $_[0]->set_err(1,"errmsg") if grep { $_ and $_ eq "B" } @_;
			return 1;
		}
	});
	
isa_ok($sth, "DBI::st");

cmp_ok(scalar @{$rows}, '==', 0, '... we should have 0 rows');

# -----------------------------------------------

ok(! eval {
        local $sth->{PrintError} = 0;
        $sth->execute_array(
		{
			ArrayTupleStatus => $tuple_status
		},
		[ 1, 2, 3 ],	          # array of integers
		42,                       # scalar 42 treated as array of 42's
		undef,                    # scalar undef treated as array of undef's
		[ qw(A B C) ],	          # array of strings
    ) },
    '... execute_array should return false'
);
ok $@, 'execute_array failure with RaiseError should have died';
like $sth->errstr, '/executing 3 generated 1 errors/';

cmp_ok(scalar @{$rows}, '==', 2, '... we should have 2 rows');
cmp_ok(scalar @{$tuple_status}, '==', 3, '... we should have 3 tuple_status');

ok(eq_array(
		$rows, 
		[ [1, 42, undef, 'A'], [3, 42, undef, 'C'] ]
		),
	'... our rows are as expected');

ok(eq_array(
		$tuple_status,
		[1, [1, 'errmsg', 'S1000'], 1]
		),
	'... our tuple_status is as expected');

# -----------------------------------------------
# --- change one param and re-execute

@$rows = ();
ok( $sth->bind_param_array(4, [ qw(a b c) ]), '... bind_param_array should return true');
ok( $sth->execute_array({ ArrayTupleStatus => $tuple_status }), '... execute_array should return true');

cmp_ok(scalar @{$rows}, '==', 3, '... we should have 3 rows');
cmp_ok(scalar @{$tuple_status}, '==', 3, '... we should have 3 tuple_status');

ok(eq_array(
		$rows, 
		[ [1, 42, undef, 'a'], [2, 42, undef, 'b'], [3, 42, undef, 'c'] ]
		),
	'... our rows are as expected');
		
ok(eq_array(
		$tuple_status,
		[1, 1, 1]
		),
	'... our tuple_status is as expected');

# -----------------------------------------------
# --- call execute_array in array context to get executed AND affected
@$rows = ();
my ($executed, $affected) = $sth->execute_array({ ArrayTupleStatus => $tuple_status });
ok($executed, '... execute_array should return true');
cmp_ok($executed, '==', 3, '... we should have executed 3 rows');
cmp_ok($affected, '==', 3, '... we should have affected 3 rows');

# -----------------------------------------------
# --- with no values for bind params, should execute zero times

@$rows = ();
$rv = $sth->execute_array( { ArrayTupleStatus => $tuple_status }, [], [], [], []);
ok($rv,   '... execute_array should return true');
ok(!($rv+0), '... execute_array should return 0 (but true)');

cmp_ok(scalar @{$rows}, '==', 0, '... we should have 0 rows');
cmp_ok(scalar @{$tuple_status}, '==', 0,'... we should have 0 tuple_status');

# -----------------------------------------------
# --- with only scalar values for bind params, should execute just once

@$rows = ();
$rv = $sth->execute_array( { ArrayTupleStatus => $tuple_status }, 5, 6, 7, 8);
cmp_ok($rv, '==', 1,   '... execute_array should return 1');

cmp_ok(scalar @{$rows}, '==', 1, '... we should have 1 rows');
ok(eq_array( $rows, [ [5,6,7,8] ]), '... our rows are as expected');
cmp_ok(scalar @{$tuple_status}, '==', 1,'... we should have 1 tuple_status');
ok(eq_array( $tuple_status, [1]), '... our tuple_status is as expected');

# -----------------------------------------------
# --- with mix of scalar values and arrays only arrays control tuples

@$rows = ();
$rv = $sth->execute_array( { ArrayTupleStatus => $tuple_status }, 5, [], 7, 8);
cmp_ok($rv, '==', 0,   '... execute_array should return 0');

cmp_ok(scalar @{$rows}, '==', 0, '... we should have 0 rows');
cmp_ok(scalar @{$tuple_status}, '==', 0,'... we should have 0 tuple_status');

# -----------------------------------------------
# --- catch 'undefined value' bug with zero bind values

@$rows = ();
my $sth_other = $dbh->prepare("insert", {
	rows => $rows,		   # where to 'insert' (push) the rows
	NUM_OF_PARAMS => 1,
});

isa_ok($sth_other, "DBI::st");

$rv = $sth_other->execute_array( {}, [] );
ok($rv,   '... execute_array should return true');
ok(!($rv+0), '... execute_array should return 0 (but true)');
# no ArrayTupleStatus

cmp_ok(scalar @{$rows}, '==', 0, '... we should have 0 rows');

# -----------------------------------------------
# --- ArrayTupleFetch code-ref tests ---

my $index = 0;

my $fetchrow = sub {				# generate 5 rows of two integer values
    return if $index >= 2;
    $index +=1;
    # There doesn't seem any reliable way to force $index to be
    # treated as a string (and so dumped as such).  We just have to
    # make the test case allow either 1 or '1'.
    return [ $index, 'a','b','c' ];
};

@$rows = ();
ok( $sth->execute_array({
		ArrayTupleFetch  => $fetchrow,
		ArrayTupleStatus => $tuple_status
	}), '... execute_array should return true');
	
cmp_ok(scalar @{$rows}, '==', 2, '... we should have 2 rows');
cmp_ok(scalar @{$tuple_status}, '==', 2, '... we should have 2 tuple_status');

ok(eq_array(
	$rows, 
	[ [1, 'a', 'b', 'c'], [2, 'a', 'b', 'c'] ]
	),
	'... rows should match'
);

ok(eq_array(
	$tuple_status, 
	[1, 1]
	),
	'... tuple_status should match'
);

# -----------------------------------------------
# --- ArrayTupleFetch sth tests ---

my $fetch_sth = $dbh->prepare("foo", {
        rows          => [ map { [ $_,'x','y','z' ] } 7..9 ],
        NUM_OF_FIELDS => 4
	});
	
isa_ok($fetch_sth, "DBI::st");	

$fetch_sth->execute();

@$rows = ();

ok( $sth->execute_array({
		ArrayTupleFetch  => $fetch_sth,
		ArrayTupleStatus => $tuple_status,
	}), '... execute_array should return true');

cmp_ok(scalar @{$rows}, '==', 3, '... we should have 3 rows');
cmp_ok(scalar @{$tuple_status}, '==', 3, '... we should have 3 tuple_status');

ok(eq_array(
	$rows, 
	[ [7, 'x', 'y', 'z'], [8, 'x', 'y', 'z'], [9, 'x', 'y', 'z'] ]
	),
	'... rows should match'
);

ok(eq_array(
	$tuple_status, 
	[1, 1, 1]
	), 
	'... tuple status should match'
);

# -----------------------------------------------
# --- error detection tests ---

$sth->{RaiseError} = 0;
$sth->{PrintError} = 0;

ok(!defined $sth->execute_array( { ArrayTupleStatus => $tuple_status }, [1],[2]), '... execute_array should return undef');
is($sth->errstr, '2 bind values supplied but 4 expected', '... errstr is as expected');

ok(!defined $sth->execute_array( { ArrayTupleStatus => { } }, [ 1, 2, 3 ]), '... execute_array should return undef');
is( $sth->errstr, 'ArrayTupleStatus attribute must be an arrayref', '... errstr is as expected');

ok(!defined $sth->execute_array( { ArrayTupleStatus => $tuple_status }, 1,{},3,4), '... execute_array should return undef');
is( $sth->errstr, 'Value for parameter 2 must be a scalar or an arrayref, not a HASH', '... errstr is as expected');

ok(!defined $sth->bind_param_array(":foo", [ qw(a b c) ]), '... bind_param_array should return undef');
is( $sth->errstr, "Can't use named placeholder ':foo' for non-driver supported bind_param_array", '... errstr is as expected');

$dbh->disconnect;

1;