Skip to content

Commit

Permalink
Add support for Postgres placeholders to Parser
Browse files Browse the repository at this point in the history
  • Loading branch information
sweoggy committed Dec 2, 2024
1 parent e9fb8d7 commit afdef8f
Show file tree
Hide file tree
Showing 3 changed files with 104 additions and 2 deletions.
4 changes: 2 additions & 2 deletions src/SQL/Parser.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,13 @@
*/
final class Parser
{
private const SPECIAL_CHARS = ':\?\'"`\\[\\-\\/';
private const SPECIAL_CHARS = ':\?\'"`\\[\\-\\/$';

private const BACKTICK_IDENTIFIER = '`[^`]*`';
private const BRACKET_IDENTIFIER = '(?<!\b(?i:ARRAY))\[(?:[^\]])*\]';
private const MULTICHAR = ':{2,}';
private const NAMED_PARAMETER = ':[a-zA-Z0-9_]+';
private const POSITIONAL_PARAMETER = '(?<!\\?)\\?(?!\\?)';
private const POSITIONAL_PARAMETER = '((?<!\\?)\\?(?!\\?)|\\$\d+)';
private const ONE_LINE_COMMENT = '--[^\r\n]*';
private const MULTI_LINE_COMMENT = '/\*([^*]+|\*+[^/*])*\**\*/';
private const SPECIAL = '[' . self::SPECIAL_CHARS . ']';
Expand Down
17 changes: 17 additions & 0 deletions tests/Functional/SQL/ParserTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

namespace Doctrine\DBAL\Tests\Functional\SQL;

use Doctrine\DBAL\ParameterType;
use Doctrine\DBAL\Tests\FunctionalTestCase;
use Doctrine\DBAL\Tests\TestUtil;

Expand All @@ -17,4 +18,20 @@ public function testPostgreSQLJSONBQuestionOperator(): void

self::assertTrue($this->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();
}
}
85 changes: 85 additions & 0 deletions tests/SQL/ParserTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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}',
Expand Down Expand Up @@ -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
Expand Down

0 comments on commit afdef8f

Please sign in to comment.