sad-spirit / pg-builder

Query builder for Postgres backed by SQL parser

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Problem with JSON strings

mapcentia opened this issue · comments

If I use a JSON string where a value has an escaped \n the output will be $$ quoted and the following key/value will be transformed from "a": false to "a"$1 if the value is NOT a string:

update test set json = '{"b":"a\\n","a":false}' where id=1

set "json" = $${"b":"a\\n","a"$1}$$ DETAIL: Token "$" is invalid

I figured out where is an option that can prevent this behavior StatementFactory(PDOCompatible: true), but this will not be enforced unless, there are actual parameters in the statement https://github.com/sad-spirit/pg-builder/blob/2.x/src/sad_spirit/pg_builder/StatementFactory.php#L192

Please confirm whether I correctly understand what happens here: you use pg_builder to generate a query that does not contain placeholders and later feed it to PDO::prepare() and try to PDOStatement::execute() which fails due to the above broken query?

I figured out where is an option that can prevent this behavior StatementFactory(PDOCompatible: true), but this will not be enforced unless, there are actual parameters in the statement

You are correct. It isn't enforced because that flag controls two changes:

  • Generation of dollar-quoted strings
  • Doubling of ? characters in operator names (as you most probably know, lots of Postgres JSON operators have these)

While the first change is safe whether you use the built query with PDO::prepare() / PDOStatement::execute() or with PDO::query() / PDO::exec(), the second one is not. Thus the method tries to be smart and only adds these changes when the query has parameters and definitely will be fed to prepare().

That's correct.

I guess that :false in {"b":"a\\n","a":false} is converted by PDO to a native placeholder $1 ?

But that seems only to happen when using $$ quoted strings.

I guess that :false in {"b":"a\\n","a":false} is converted by PDO to a native placeholder $1 ?

Exactly.

But that seems only to happen when using $$ quoted strings.

PDO cannot parse these, but it properly parses strings in single quotes. That's the reason why dollar-quoted strings aren't generated in PDO::prepare() compatibility mode.

I thought a bit about possible fixes, the best idea so far is the following: add a $forcePDOPrepareCompatibility bool parameter to StatementFactory::createFromAST(). It will default to false, if set to true it will override both the constructor parameter and the check for placeholders, generating the queries with

  • Named parameters left intact,
  • No dollar-quoted strings,
  • ? character in operators replaced with ??.

So if you intend to use the query without placeholders in PDO::prepare() / PDOStatement::execute() you'll have to explicitly set this parameter to true.

How does that sound?

That's sound good. Thanks for taking the time to look into this issue

Released version 2.3.1 with this fix