Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for Postgres positional parameters to Parser #6634

Open
wants to merge 1 commit into
base: 4.3.x
Choose a base branch
from

Conversation

sweoggy
Copy link

@sweoggy sweoggy commented Nov 28, 2024

Q A
Type improvement
Fixed issues

Summary

This PR enables using Postgres placeholders $# with doctrine/dbal. For example the following query work after this patch:

$connection->executeStatement(
    'INSERT INTO dummy_table (some_id) VALUES ($1)',
    [1],
    [],
);

Without this patch a driver exception is thrown: Could not find parameter 1 in the SQL statement.

Copy link
Member

@morozov morozov left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@sweoggy, thanks for the patch, it looks good.

Besides the unit tests, could you add an integration test that demonstrate the usage of these parameters with PostgreSQL? It doesn't look like DBAL has ever supported them.

Also, the parser is a back port of the corresponding parser from PDO. It looks like at the time of the back port, it didn't support such parameters or I missed something. In any event, I want to keep its implementation close to PDO. If the support for these parameters was introduced in an newer PHP version, please update the link in the PHPDoc.


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+)';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it make sense to extract the new pattern into a separate constant?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think so. Two arguments:

  • This is Postgres' way of specifying positional parameters. So the naming makes a lot of sense.
  • Adding an additional pattern would incur a performance penalty and complicate the code (having to add one more handler, which in turn just calls $visitor->acceptPositionalParameter($sql);)

@sweoggy sweoggy changed the title Add support for Postgres placeholders to Parser Add support for Postgres positional parameters to Parser Dec 2, 2024
@sweoggy sweoggy force-pushed the 4.3.x branch 3 times, most recently from 9280a57 to af9bf85 Compare December 2, 2024 15:05
@sweoggy
Copy link
Author

sweoggy commented Dec 2, 2024

@sweoggy, thanks for the patch, it looks good.

Besides the unit tests, could you add an integration test that demonstrate the usage of these parameters with PostgreSQL? It doesn't look like DBAL has ever supported them.

Also, the parser is a back port of the corresponding parser from PDO. It looks like at the time of the back port, it didn't support such parameters or I missed something. In any event, I want to keep its implementation close to PDO. If the support for these parameters was introduced in an newer PHP version, please update the link in the PHPDoc.

I believe this is a native pgsql driver feature, https://www.postgresql.org/docs/current/sql-expressions.html#SQL-EXPRESSIONS-PARAMETERS-POSITIONAL

I just pushed a fix for re-occurring positional parameters and added an integration test

Copy link
Member

@morozov morozov left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just realized it now that the SQL parser is used elsewhere than the wrapper statement and the oci8 driver – specifically, the pgsql driver. I think it's fine for now but its purpose becomes less and less clear, so we may want to break it down into a few smaller ones to address #3744.

Since @derrabus is the original author of the pgsql driver, I'd like him to review this patch as well.

As a second thought: if those are the placeholders natively recognized by the underlying driver, then why does it require a SQL parser in the DBAL implementation of the driver for them to work? So far, the parser has been used only in order to support the placeholders that the given component doesn't support natively.


public function testPostgresPlaceholders(): void
{
if (! TestUtil::isDriverOneOf('pgsql')) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like this test passes on pdo_pgsql as well (not sure how it's implemented in PDO). Please move it into a dedicated test class and enable for pdo_pgsql.

Since it's a functional test, it would be better to name it in a way that it reflects the functionality being tested (something about PostgreSQL native positional parameters). The parser is an implementation detail, which isn't always relevant (specifically, in the case of pdo_pgsql).

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I moved it to a separate class. Tried to enable it for pdo_pgsql as well, but it actually doesn't work, it doesn't seem to pass the values to the query and instead tries to insert null values.

Not sure if this is a limitation of pdo_pgsql or further changes would be required to Doctrine to support positional parameters for this driver. Error can be seen here https://github.com/debricked/dbal/actions/runs/12137000213/job/33839318280

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems to be a limitation of PDO https://www.php.net/pdo.prepare

@morozov morozov requested a review from derrabus December 3, 2024 01:43
@sweoggy sweoggy force-pushed the 4.3.x branch 2 times, most recently from 58de3d5 to 3332d96 Compare December 3, 2024 09:51
@derrabus
Copy link
Member

derrabus commented Dec 3, 2024

As a second thought: if those are the placeholders natively recognized by the underlying driver, then why does it require a SQL parser in the DBAL implementation of the driver for them to work? So far, the parser has been used only in order to support the placeholders that the given component doesn't support natively.

The parser is currently used to translate the PDO-style positional parameters (?) to the positional parameters that Postgres understands ( $1, $2, …). I assume that our "feature" gets in the way if someone actually wants to use the Postgres-style positional parameters.

@sweoggy
Copy link
Author

sweoggy commented Dec 3, 2024

I just realized it now that the SQL parser is used elsewhere than the wrapper statement and the oci8 driver – specifically, the pgsql driver. I think it's fine for now but its purpose becomes less and less clear, so we may want to break it down into a few smaller ones to address #3744.

Since @derrabus is the original author of the pgsql driver, I'd like him to review this patch as well.

As a second thought: if those are the placeholders natively recognized by the underlying driver, then why does it require a SQL parser in the DBAL implementation of the driver for them to work? So far, the parser has been used only in order to support the placeholders that the given component doesn't support natively.

Regarding the second thought. This change is needed because Doctrine\DBAL\Driver\PgSQL\ConvertParameters::getParameterMap() is called on line 58 for Doctrine\DBAL\Driver\PgSQL\Connection whenever you are using a prepared statement.

Without this change, the Parser does not call Doctrine\DBAL\Driver\PgSQL\ConvertParameters::acceptPositionalParameter(), which means that the parameter map will be empty and there will be nothing to map the passed parameters to, which results in the could not find parameter 1 in the SQL statement exception.

@morozov
Copy link
Member

morozov commented Dec 5, 2024

The parser is currently used to translate the PDO-style positional parameters (?) to the positional parameters that Postgres understands ( $1, $2, …). I assume that our "feature" gets in the way if someone actually wants to use the Postgres-style positional parameters.

That's my understanding as well. I believe that instead of making the parser support what the driver supports natively, a better approach would be to make the parser not get in the way of someone using the native parameters. The driver should not consider the parameter map derived from parsing the query the source of truth. It should only augment the native placeholders with the PDO-style ones.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants