From bb3a3bc9e271b6f85cf2b23f005ac30650ff8fb6 Mon Sep 17 00:00:00 2001 From: pyrrhlin <1197072+myrrhlin@users.noreply.github.com> Date: Thu, 31 Oct 2024 01:57:18 -0400 Subject: [PATCH 1/4] tests for $sth->{ParamValues} attrib (gh #447) confirming behavior of this attribute before and after execution on prepared statements, with and without bound values. --- t/gh447-paramvalues.t | 130 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 130 insertions(+) create mode 100644 t/gh447-paramvalues.t diff --git a/t/gh447-paramvalues.t b/t/gh447-paramvalues.t new file mode 100644 index 00000000..a1f3c182 --- /dev/null +++ b/t/gh447-paramvalues.t @@ -0,0 +1,130 @@ +#! /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(); + } +} + +# ------ 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 +my $ofs = 0; + +$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(); + From b3fc0865cb021ca1ce759fdb75372d2d5741ebf6 Mon Sep 17 00:00:00 2001 From: pyrrhlin <1197072+myrrhlin@users.noreply.github.com> Date: Fri, 1 Nov 2024 00:30:35 -0400 Subject: [PATCH 2/4] set index offset for when gh#447 is fixed ParamValues attribute is a hashref whose keys should be integers startign with 1, for the placeholders in the prepared statement. --- t/gh447-paramvalues.t | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/t/gh447-paramvalues.t b/t/gh447-paramvalues.t index a1f3c182..39f7eecd 100644 --- a/t/gh447-paramvalues.t +++ b/t/gh447-paramvalues.t @@ -31,6 +31,14 @@ END { } } +# 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"); @@ -50,8 +58,6 @@ undef $sth; # test prepare/execute statement with a placeholder -my $ofs = 0; - $sth = $dbh->prepare("INSERT INTO $table values (?, ?)"); is_deeply($sth->{ParamValues}, {0+$ofs => undef, 1+$ofs => undef}, "ParamValues is correct hashref before INSERT") From d654262b06f41100198bb93d8a24fc6a9f2207bc Mon Sep 17 00:00:00 2001 From: pyrrhlin <1197072+myrrhlin@users.noreply.github.com> Date: Fri, 1 Nov 2024 01:14:43 -0400 Subject: [PATCH 3/4] doc: POD for ParamValues sth attribute --- lib/DBD/mysql.pm | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/lib/DBD/mysql.pm b/lib/DBD/mysql.pm index b2ecef35..0bfecb91 100644 --- a/lib/DBD/mysql.pm +++ b/lib/DBD/mysql.pm @@ -1675,6 +1675,24 @@ to DBD::mysql. The attribute list includes: =over +=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 when +the C or C method is called. Supplying the +parameter values in the arguments to C will override any +previously bound values. + +After execution, it is possible to use C to change a single +value in the statement and C again, with other values +unchanged. The attribute remains properly populated after the C +method is called, with the values from the last execution. + =item ChopBlanks this attribute determines whether a I will chop preceding From e2e42a4e6b22be51cfcbdcb15e99b8415b6ea47d Mon Sep 17 00:00:00 2001 From: pyrrhlin <1197072+myrrhlin@users.noreply.github.com> Date: Fri, 1 Nov 2024 01:18:09 -0400 Subject: [PATCH 4/4] doc: reorder mentioned sth attributes ALL_CAP attributes before WordCase, and mysql_* private attributes last. alphabetic order within each group. --- lib/DBD/mysql.pm | 81 ++++++++++++++++++++++++------------------------ 1 file changed, 41 insertions(+), 40 deletions(-) diff --git a/lib/DBD/mysql.pm b/lib/DBD/mysql.pm index 0bfecb91..04c9fa94 100644 --- a/lib/DBD/mysql.pm +++ b/lib/DBD/mysql.pm @@ -1670,28 +1670,36 @@ 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, while lower case attribute +names are private to DBD::mysql. The attribute list includes: =over -=item ParamValues +=item NAME -This attribute is supported as described in the DBI documentation. +A reference to an array of column names, as per DBI docs. -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. +=item NULLABLE -The values for these keys are initially undef; they are populated when -the C or C method is called. Supplying the -parameter values in the arguments to C will override any -previously bound values. +A reference to an array of boolean values; TRUE indicates that this column +may contain NULL's. -After execution, it is possible to use C to change a single -value in the statement and C again, with other values -unchanged. The attribute remains properly populated after the C -method is called, with the values from the last execution. +=item NUM_OF_FIELDS + +Number of fields returned by a I or I statement. -You may use this for checking whether a statement returned a result: -A zero value indicates a non-SELECT statement like I, -I or I. - =item mysql_table A reference to an array of table names, useful in a I 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. See below. - =item mysql_type A reference to an array of MySQL's native column types, for example