Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

issue #447 tests for $sth->{ParamValues} attribute #450

Merged
merged 4 commits into from
Nov 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 46 additions & 27 deletions lib/DBD/mysql.pm
Original file line number Diff line number Diff line change
Expand Up @@ -1670,17 +1670,61 @@ header of table names together with all rows:
}

For portable applications you should restrict yourself to attributes with
capitalized or mixed case names. Lower case attribute names are private
to DBD::mysql. The attribute list includes:
capitalized or mixed case names. Uppercase attribute names are in the
statement handle interface described by L<DBI>, while lower case attribute
names are private to DBD::mysql. The attribute list includes:

=over

=item NAME

A reference to an array of column names, as per DBI docs.

=item NULLABLE

A reference to an array of boolean values; TRUE indicates that this column
may contain NULL's.

=item NUM_OF_FIELDS

Number of fields returned by a I<SELECT> or I<LISTFIELDS> statement.
You may use this for checking whether a statement returned a result:
A zero value indicates a non-SELECT statement like I<INSERT>,
I<DELETE> or I<UPDATE>.

=item TYPE

A reference to an array of column types. The engine's native column
types are mapped to portable types like DBI::SQL_INTEGER() or
DBI::SQL_VARCHAR(), as good as possible. Not all native types have
a meaningful equivalent, for example DBD::mysql::FIELD_TYPE_INTERVAL
is mapped to DBI::SQL_VARCHAR().
If you need the native column types, use I<mysql_type>. See below.

=item ChopBlanks

this attribute determines whether a I<fetchrow> will chop preceding
and trailing blanks off the column values. Chopping blanks does not
have impact on the I<max_length> attribute.

=item ParamValues

This attribute is supported as described in the DBI documentation.

It returns a hashref, the keys of which are the 'names' of the
placeholders: integers starting at 1. It returns an empty hashref if
the statement has no placeholders.

The values for these keys are initially undef; they are populated with
C<bind_param>, or when C<execute> method is called with parameters.
(Supplying the parameter values in the arguments to C<execute> will
override any previously bound values.)

After execution, it is possible to use C<bind_param> to change a single
parameter value and C<execute> the statement again, with other values
unchanged. The attribute remains properly populated after the C<finish>
method is called, with the values from the last execution.

=item mysql_gtids

Returns GTID(s) if GTID session tracking is ensabled in the server via
Expand Down Expand Up @@ -1732,35 +1776,10 @@ the maximum physically present in the result table, I<length> gives
the theoretically possible maximum. I<max_length> is valid for MySQL
only.

=item NAME

A reference to an array of column names.

=item NULLABLE

A reference to an array of boolean values; TRUE indicates that this column
may contain NULL's.

=item NUM_OF_FIELDS

Number of fields returned by a I<SELECT> or I<LISTFIELDS> statement.
You may use this for checking whether a statement returned a result:
A zero value indicates a non-SELECT statement like I<INSERT>,
I<DELETE> or I<UPDATE>.

=item mysql_table

A reference to an array of table names, useful in a I<JOIN> result.

=item TYPE

A reference to an array of column types. The engine's native column
types are mapped to portable types like DBI::SQL_INTEGER() or
DBI::SQL_VARCHAR(), as good as possible. Not all native types have
a meaningful equivalent, for example DBD::mysql::FIELD_TYPE_INTERVAL
is mapped to DBI::SQL_VARCHAR().
If you need the native column types, use I<mysql_type>. See below.

=item mysql_type

A reference to an array of MySQL's native column types, for example
Expand Down
136 changes: 136 additions & 0 deletions t/gh447-paramvalues.t
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
#! /bin/env perl

use strict;
use warnings;

#"set tabstop=4 softtabstop=4 shiftwidth=4 expandtab

use Data::Dumper;
use Test::More;
use DBI;
use lib 't', '.';
require 'lib.pl';

my ($row, $sth, $dbh);
my ($def, $rows, $errstr, $ret_ref);
use vars qw($test_dsn $test_user $test_password);
my $table = 'dbd_mysql_gh447';

eval {$dbh = DBI->connect($test_dsn, $test_user, $test_password,
{ RaiseError => 1, AutoCommit => 1});};

if ($@) {
plan skip_all => "no database connection";
}

# in case of exit early, ensure we clean up
END {
if ($dbh) {
$dbh->do("DROP TABLE IF EXISTS $table");
$dbh->disconnect();
}
}

# this is the starting index for the placeholder keys
# in the ParamValues attribute hashref. gh#447 showed
# the keys begin counting with 0, but DBI requires they
# start counting at 1.
# so, if this value is 0, tests pass under DBD::mysql 4.050.
# but the value should be 1, when the issue is fixed.
my $ofs = 1;

# ------ set up
ok(defined $dbh, "Connected to database");
$dbh->do("DROP TABLE IF EXISTS $table");
$dbh->do("CREATE TABLE $table (id INT(4), name VARCHAR(64))");

# test prepare/execute statement without a placeholder

$sth = $dbh->prepare("SHOW TABLES LIKE '$table'");
is_deeply($sth->{ParamValues}, {}, "ParamValues is empty hashref before SHOW");
$sth->execute();

is_deeply($sth->{ParamValues}, {}, "ParamValues is still empty after execution");

$sth->finish;
is_deeply($sth->{ParamValues}, {}, "ParamValues empty after finish");
undef $sth;


# test prepare/execute statement with a placeholder
$sth = $dbh->prepare("INSERT INTO $table values (?, ?)");
is_deeply($sth->{ParamValues}, {0+$ofs => undef, 1+$ofs => undef},
"ParamValues is correct hashref before INSERT")
|| print Dumper($sth->{ParamValues});

# insert rows with placeholder
my %rowdata;
my @chars = grep !/[0O1Iil]/, 0..9, 'A'..'Z', 'a'..'z';

for (my $i = 1 ; $i < 4; $i++) {
my $word = join '', $i, '-', map { $chars[rand @chars] } 0 .. 16;
$rowdata{$i} = $word; # save for later
$rows = $sth->execute($i, $word);
is($rows, 1, "Should have inserted one row");
is_deeply($sth->{ParamValues}, {0+$ofs => $i, 1+$ofs => $word},
"row $i ParamValues hashref as expected");
}

$sth->finish;
is_deeply($sth->{ParamValues}, {0+$ofs => 3, 1+$ofs => $rowdata{3}},
"ParamValues still hold last values after finish");
undef $sth;


# test prepare/execute with bind_param

$sth = $dbh->prepare("SELECT * FROM $table WHERE id = ? OR name = ?");
is_deeply($sth->{ParamValues}, {0+$ofs => undef, 1+$ofs => undef},
"ParamValues is hashref with keys before bind_param");
$sth->bind_param(1, 1, DBI::SQL_INTEGER);
$sth->bind_param(2, $rowdata{1});
is_deeply($sth->{ParamValues}, {0+$ofs => 1, 1+$ofs => $rowdata{1}},
"ParamValues contains bound values after bind_param");

$rows = $sth->execute;
is($rows, 1, 'execute selected 1 row');
is_deeply($sth->{ParamValues}, {0+$ofs => 1, 1+$ofs => $rowdata{1}},
"ParamValues still contains values after execute");

# try changing one parameter only (so still param 1 => 1)
$sth->bind_param(2, $rowdata{2});
is_deeply($sth->{ParamValues}, {0+$ofs => 1, 1+$ofs => $rowdata{2}},
"ParamValues updated with another bind_param");
$rows = $sth->execute;
is($rows, 2, 'execute selected 2 rows because changed param value');

# try execute with args (the previously bound values are overridden)
$rows = $sth->execute(3, $rowdata{3});
is($rows, 1, 'execute used exec args, overrode bound params');
is_deeply($sth->{ParamValues}, {0+$ofs => 3, 1+$ofs => $rowdata{3}},
"ParamValues reflect execute args -- bound params overwritten");

$sth->bind_param(1, undef, DBI::SQL_INTEGER);
is_deeply($sth->{ParamValues}, {0+$ofs => undef, 1+$ofs => $rowdata{3}},
"ParamValues includes undef param after binding");

$rows = $sth->execute(1, $rowdata{2});
is($rows, 2, 'execute used exec args, not bound values');
is_deeply($sth->{ParamValues}, {0+$ofs => 1, 1+$ofs => $rowdata{2}},
"ParamValues changed by execution");

undef $sth;


# clean up
$dbh->do("DROP TABLE IF EXISTS $table");

# Install a handler so that a warning about unfreed resources gets caught
$SIG{__WARN__} = sub { die @_ };

$dbh->disconnect();

undef $dbh;

done_testing();

Loading