[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.
[email protected] Fri, 2 Jan 2015 04:27:55 +0000
| Newsgroups | perl.dbd.pg.changes |
|---|---|
| Message-ID | <[email protected]> |
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