From afdef8fe51cb2fa3f971f0cc38d116aac02ecba7 Mon Sep 17 00:00:00 2001 From: Oscar Reimer Date: Thu, 28 Nov 2024 15:16:05 +0100 Subject: [PATCH] Add support for Postgres placeholders to Parser --- src/SQL/Parser.php | 4 +- tests/Functional/SQL/ParserTest.php | 17 ++++++ tests/SQL/ParserTest.php | 85 +++++++++++++++++++++++++++++ 3 files changed, 104 insertions(+), 2 deletions(-) diff --git a/src/SQL/Parser.php b/src/SQL/Parser.php index ad30f099079..e4964484028 100644 --- a/src/SQL/Parser.php +++ b/src/SQL/Parser.php @@ -34,13 +34,13 @@ */ final class Parser { - private const SPECIAL_CHARS = ':\?\'"`\\[\\-\\/'; + private const SPECIAL_CHARS = ':\?\'"`\\[\\-\\/$'; private const BACKTICK_IDENTIFIER = '`[^`]*`'; private const BRACKET_IDENTIFIER = '(?connection->fetchOne('SELECT \'{"a":null}\'::jsonb ?? :key', ['key' => 'a'])); } + + public function testPostgresPlaceholders(): void + { + if (! TestUtil::isDriverOneOf('pgsql')) { + self::markTestSkipped('This test requires the pgsql driver.'); + } + + $this->connection->beginTransaction(); + $this->connection->executeStatement('CREATE TABLE dummy_table (a_number smallint, a_number_2 smallint)'); + $this->connection->executeStatement( + 'INSERT INTO dummy_table (a_number, a_number_2) VALUES ($1, $1)', + [1], + [ParameterType::INTEGER], + ); + $this->connection->rollBack(); + } } diff --git a/tests/SQL/ParserTest.php b/tests/SQL/ParserTest.php index e861dfeb74a..491338bc38a 100644 --- a/tests/SQL/ParserTest.php +++ b/tests/SQL/ParserTest.php @@ -45,61 +45,121 @@ private static function getStatementsWithParameters(): iterable 'SELECT {?}', ]; + yield [ + 'SELECT $1', + 'SELECT {$1}', + ]; + yield [ 'SELECT * FROM Foo WHERE bar IN (?, ?, ?)', 'SELECT * FROM Foo WHERE bar IN ({?}, {?}, {?})', ]; + yield [ + 'SELECT * FROM Foo WHERE bar IN ($1, $2, $1)', + 'SELECT * FROM Foo WHERE bar IN ({$1}, {$2}, {$1})', + ]; + yield [ 'SELECT ? FROM ?', 'SELECT {?} FROM {?}', ]; + yield [ + 'SELECT $1 FROM $2', + 'SELECT {$1} FROM {$2}', + ]; + yield [ 'SELECT "?" FROM foo WHERE bar = ?', 'SELECT "?" FROM foo WHERE bar = {?}', ]; + yield [ + 'SELECT "$1" FROM foo WHERE bar = $1', + 'SELECT "$1" FROM foo WHERE bar = {$1}', + ]; + yield [ "SELECT '?' FROM foo WHERE bar = ?", "SELECT '?' FROM foo WHERE bar = {?}", ]; + yield [ + "SELECT '$1' FROM foo WHERE bar = $1", + "SELECT '$1' FROM foo WHERE bar = {\$1}", + ]; + yield [ 'SELECT `?` FROM foo WHERE bar = ?', 'SELECT `?` FROM foo WHERE bar = {?}', ]; + yield [ + 'SELECT `$1` FROM foo WHERE bar = $1', + 'SELECT `$1` FROM foo WHERE bar = {$1}', + ]; + yield [ 'SELECT [?] FROM foo WHERE bar = ?', 'SELECT [?] FROM foo WHERE bar = {?}', ]; + yield [ + 'SELECT [$1] FROM foo WHERE bar = $1', + 'SELECT [$1] FROM foo WHERE bar = {$1}', + ]; + yield [ 'SELECT * FROM foo WHERE jsonb_exists_any(foo.bar, ARRAY[?])', 'SELECT * FROM foo WHERE jsonb_exists_any(foo.bar, ARRAY[{?}])', ]; + yield [ + 'SELECT * FROM foo WHERE jsonb_exists_any(foo.bar, ARRAY[$1])', + 'SELECT * FROM foo WHERE jsonb_exists_any(foo.bar, ARRAY[{$1}])', + ]; + yield [ "SELECT 'Doctrine\DBAL?' FROM foo WHERE bar = ?", "SELECT 'Doctrine\DBAL?' FROM foo WHERE bar = {?}", ]; + yield [ + "SELECT 'Doctrine\DBAL$1' FROM foo WHERE bar = $1", + "SELECT 'Doctrine\DBAL$1' FROM foo WHERE bar = {\$1}", + ]; + yield [ 'SELECT "Doctrine\DBAL?" FROM foo WHERE bar = ?', 'SELECT "Doctrine\DBAL?" FROM foo WHERE bar = {?}', ]; + yield [ + 'SELECT "Doctrine\DBAL$1" FROM foo WHERE bar = $1', + 'SELECT "Doctrine\DBAL$1" FROM foo WHERE bar = {$1}', + ]; + yield [ 'SELECT `Doctrine\DBAL?` FROM foo WHERE bar = ?', 'SELECT `Doctrine\DBAL?` FROM foo WHERE bar = {?}', ]; + yield [ + 'SELECT `Doctrine\DBAL$1` FROM foo WHERE bar = $1', + 'SELECT `Doctrine\DBAL$1` FROM foo WHERE bar = {$1}', + ]; + yield [ 'SELECT [Doctrine\DBAL?] FROM foo WHERE bar = ?', 'SELECT [Doctrine\DBAL?] FROM foo WHERE bar = {?}', ]; + yield [ + 'SELECT [Doctrine\DBAL?] FROM foo WHERE bar = $1', + 'SELECT [Doctrine\DBAL?] FROM foo WHERE bar = {$1}', + ]; + yield [ 'SELECT :foo FROM :bar', 'SELECT {:foo} FROM {:bar}', @@ -293,6 +353,31 @@ private static function getStatementsWithParameters(): iterable , ]; + yield 'Postgres placeholders inside comments' => [ + <<<'SQL' +/* + * test placeholder $1 + */ +SELECT dummy as "dummy$1" + FROM DUAL + WHERE '$1' = '$1' +-- AND dummy <> $1 + AND dummy = $1 +SQL +, + <<<'SQL' +/* + * test placeholder $1 + */ +SELECT dummy as "dummy$1" + FROM DUAL + WHERE '$1' = '$1' +-- AND dummy <> $1 + AND dummy = {$1} +SQL +, + ]; + yield 'Escaped question' => [ <<<'SQL' SELECT '{"a":null}'::jsonb ?? :key