Committed by Greg Sabino Mullane <[email protected]>
Subject: [DBD::Pg 3/3] First pass at placeholder escpaing. In other words,
allowing literal question marks to be passed into queries by adding a
backslash to them. Affects other placeholders as well (dollarsign and colon)
Add tests for ? and ?? by creating custom operators Adjust cleanup routines
in the tests for the new operators Actual rewrite code in dbdimp.c modified
from a proof of concept by Tim Bunce.
---
dbdimp.c | 17 ++++++++++++++++-
t/12placeholders.t | 31 +++++++++++++++++++++++++++++--
t/dbdpg_test_setup.pl | 26 ++++++++++++++++++++++++++
3 files changed, 71 insertions(+), 3 deletions(-)
diff --git a/dbdimp.c b/dbdimp.c
index d437d4d..c90d561 100644
--- a/dbdimp.c
+++ b/dbdimp.c
@@ -1733,7 +1733,7 @@ static void pg_st_split_statement (pTHX_ imp_sth_t *
imp_sth, int version, char
if (TSTART_slow) TRC(DBILOGFP, "%sBegin pg_st_split_statement\n",
THEADER_slow);
if (TRACE6_slow) TRC(DBILOGFP, "%spg_st_split_statement: (%s)\n",
THEADER_slow, statement);
-
+
/*
If the pg_direct flag is set (or the string has no length), we do not
split at all,
but simply put everything verbatim into a single segment and return.
@@ -1979,6 +1979,21 @@ static void pg_st_split_statement (pTHX_ imp_sth_t *
imp_sth, int version, char
continue;
}
+ /* If this placeholder is escaped, we rewrite the string to
remove the
+ backslash, and move on as if there is no placeholder */
+ if ('\\' == oldch) {
+ /* copy the placeholder-like character but ignore the
backslash */
+ unsigned char *p = statement-2;
+ while(*p++) {
+ *(p-1) = *p;
+ }
+ /* We need to adjust these items because we just
rewrote statement! */
+ statement--;
+ currpos--;
+ ch = *statement;
+ continue;
+ }
+
/* We might slurp in a placeholder, so mark the character
before the current one */
/* In other words, inside of "ABC?", set sectionstop to point
to "C" */
sectionstop=currpos-1;
diff --git a/t/12placeholders.t b/t/12placeholders.t
index eb4f3d2..cfd7164 100644
--- a/t/12placeholders.t
+++ b/t/12placeholders.t
@@ -17,7 +17,7 @@ my $dbh = connect_database();
if (! $dbh) {
plan skip_all => 'Connection to database failed, cannot continue
testing';
}
-plan tests => 251;
+plan tests => 253;
my $t='Connect to database for placeholder testing';
isnt ($dbh, undef, $t);
@@ -841,10 +841,37 @@ while (my ($name,$res) = each %booltest) {
}
}
+## Test of placeholder escaping. Enabled by default, so let's jump right in
+$t = q{Basic placeholder escaping works via backslash-question mark for \?};
+
+## But first, we need some operators
+$dbh->do('create operator ? (leftarg=int,rightarg=int,procedure=int4eq)');
+$dbh->commit();
+$dbh->do('create operator ?? (leftarg=text,rightarg=text,procedure=texteq)');
+$dbh->commit();
+
+$SQL = qq{SELECT count(*) FROM dbd_pg_test WHERE id \\? ?};
+$sth = $dbh->prepare($SQL);
+eval {
+ $count = $sth->execute(123);
+};
+is($@, '', $t);
+$sth->finish();
+
+$t = q{Basic placeholder escaping works via backslash-question mark for \?\?};
+$SQL = qq{SELECT count(*) FROM dbd_pg_test WHERE pname \\?\\? ?};
+$sth = $dbh->prepare($SQL);
+eval {
+ $count = $sth->execute('foobar');
+};
+is($@, '', $t);
+$sth->finish();
+
+## pg_placeholder_escaping = 1;
+
## Begin custom type testing
$dbh->rollback();
cleanup_database($dbh,'test');
$dbh->disconnect();
-
diff --git a/t/dbdpg_test_setup.pl b/t/dbdpg_test_setup.pl
index 01fc98c..bebd833 100644
--- a/t/dbdpg_test_setup.pl
+++ b/t/dbdpg_test_setup.pl
@@ -22,6 +22,12 @@ my @matviews =
'dbd_pg_matview',
);
+my @operators =
+ (
+ '?.integer.integer',
+ '??.text.text',
+ );
+
my @schemas =
(
'dbd_pg_testschema',
@@ -792,6 +798,20 @@ sub relation_exists {
} ## end of relation_exists
+sub operator_exists {
+
+ my ($dbh,$opname,$leftarg,$rightarg) = @_;
+
+ my $SQL = 'SELECT 1 FROM pg_catalog.pg_operator '.
+ 'WHERE oprname=? AND oprleft::regtype::text = ? AND
oprright::regtype::text = ?';
+ my $sth = $dbh->prepare_cached($SQL);
+ my $count = $sth->execute($opname,$leftarg,$rightarg);
+ $sth->finish();
+ return $count < 1 ? 0 : 1;
+
+} ## end of operator_exists
+
+
sub cleanup_database {
## Clear out any testing objects in the current database
@@ -811,6 +831,12 @@ sub cleanup_database {
$dbh->do("DROP MATERIALIZED VIEW $schema.$name");
}
+ for my $name (@operators) {
+ my ($opname,$leftarg,$rightarg) = split /\./ => $name;
+ next if ! operator_exists($dbh,$opname,$leftarg,$rightarg);
+ $dbh->do("DROP OPERATOR $opname($leftarg,$rightarg)");
+ }
+
for my $name (@tables) {
my $schema = ($name =~ s/(.+)\.(.+)/$2/) ? $1 : $S;
next if ! relation_exists($dbh,$schema,$name);
--
1.8.4