Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Bug #62593 Updated pdo_pgsql driver to convert boolean values to pg nati... #198

Closed
wants to merge 7 commits into from

4 participants

Will Fitch Lars Strojny Wez Furlong David Soria Parra
Will Fitch

...ve format in emulation mode

Change-Id: Id81a7ae42e4108c126abb29f927950fea754144c

Will Fitch

From the internals list:

The following cases cause pgsql boolean types to be converted to an incompatible (long) format:

  1. PQprepare is not available (HAVE_PQPREPARE is undefined). This happens when the libpq version < 8.0
  2. PQprepare is available, but either PDO_PGSQL_ATTR_DISABLE_NATIVE_PREPARED_STATEMENT or PDO_ATTR_EMULATE_PREPARES are true (emulation handled by PDO, and the parameter hook pgsql_stmt_param_hook just skips parameter checks)

This results in PDO converting the parameter to a long (default behavior for boolean). Take the following example:

$pdo = new PDO($dsn);
$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, true);
$query = $pdo->prepare( 'SELECT :foo IS FALSE as val_is_false' );
$query->bindValue( ':foo', false, PDO::PARAM_BOOL );
$query->execute( );
print_r($query->errorInfo());

This results in the following:

Array
(
[0] => 42804
[1] => 7
[2] => ERROR: argument of IS FALSE must be type boolean, not type integer at character 8
)

This happens because true and false are converted to their long formats (1 and 0 respectively), which are invalid values for Postgres. However, in the sole event that PQprepare is available and emulation is disabled, boolean parameters are correctly converted to "t" and "f".

As noted in bug #62593, disabling emulation fixes the issue. There are a couple of issues with this approach, though. First, it forces you to make multiple calls to the server when you actually only need to escape input. Second, and most important in my case, when using middleware like pgbouncer, it's not possible to use true prepared statements. The calls from PQprepare and PQexec will have separate handles.

The attached patch updates the driver to behave like so:

  1. Do we have PQprepare and is emulation turned off? If so, let the driver handle via PQprepare and PQexec
  2. Is PQprepare unavailable? If so, modify the original param by replacing the long 1 or 0 format to "t" or "f"
  3. Is PQprepare available and emulation turned on? If so, modify the original param by replacing the long 1 or 0 format to "t" or "f"
Lars Strojny

Thanks! We need a test for that.

Will Fitch

There actually is one, and it currently fails: bug_33876.phpt. This actually makes that specific test pass.

willfitch added some commits
Will Fitch willfitch Bug #62593 Updated pdo_pgsql driver to convert boolean values to pg n…
…ative format in emulation mode

Change-Id: Id81a7ae42e4108c126abb29f927950fea754144c
0e7f739
Will Fitch willfitch Bug #62593 Added test for change
Change-Id: I21ffe7e8913b367e447afca2b1b9079b0dcbfb70
eb599b4
Will Fitch

I went ahead and added a new test for this bug. Some of the tests from #33876 aren't relevant to this and I'd like to confirm this specific test only.

Wez Furlong

for the sake of completeness, can you add something like this to the test case?

 $value = false;
 $query->bindParam(':foo', $value, PDO::PARAM_BOOL);
 print_r($value);

so that we can assert that we didn't change the value of $value?

Will Fitch

@wez I had to update SEPARATE_ZVAL_IF_NOT_REF to SEPARATE_ZVAL; otherwise, the original value gets modified on bindParam. I've updated the test to verify bindValue and bindParam behave as expected.

Wez Furlong

Hmmm, ok, next possible wrinkle then; can you try something like Example 3 from the manual crossed with the above?
http://php.net/manual/en/pdostatement.bindparam.php
but where the parameter is a bool?
You'll need to define a procedure with an OUT parameter; in this case we expect $value to get modified; want to make sure we didn't break that :-)

Will Fitch

Haha. Well, there are a couple of issues here. First, PDO_PARAM_INTPUT_OUTPUT doesn't work for most drivers (see https://bugs.php.net/bug.php?id=43887). I have updated to specifically skip conversion of the param_type is hashed with PDO_PARAM_INPUT_OUTPUT. This event could cause a memory leak otherwise, so this is good. This does not, however, address the other bug and probably shouldn't.

I did not add the INOUT test as it will fail regardless.

Will Fitch

@wez any other issues you can think of? I'm going to commit the test with the example 3 code but commented out. When we are able to address the other bug, the comments can be removed.

Will Fitch

@wez @lstrojny If there are no further issues with this, could we get this merged? It is a show-stopper for those of us attempting to upgrade from 5.2 to 5.3/5.4.

Wez Furlong
wez commented

The code looks good to me. I'm a little out of touch with getting things merged these days, so will need to appeal to @lstrojny and others to get it merged.

Thanks!

Will Fitch

Thanks, @wez. I appreciate your feedback

Will Fitch

@lstrojny Can we get this merged/queued for merging?

David Soria Parra
Owner
dsp commented

I rebased it and currently waiting for Johannes approval for 5.3.

David Soria Parra
Owner
dsp commented

Merged

David Soria Parra dsp closed this
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
This page is out of date. Refresh to see the latest.
14 ext/pdo_pgsql/pgsql_statement.c
@@ -362,8 +362,20 @@ static int pgsql_stmt_param_hook(pdo_stmt_t *stmt, struct pdo_bound_param_data *
362 362 }
363 363 break;
364 364 }
  365 + } else {
  366 +#endif
  367 + if (param->is_param) {
  368 + /* We need to manually convert to a pg native boolean value */
  369 + if (PDO_PARAM_TYPE(param->param_type) == PDO_PARAM_BOOL &&
  370 + ((param->param_type & PDO_PARAM_INPUT_OUTPUT) != PDO_PARAM_INPUT_OUTPUT)) {
  371 + SEPARATE_ZVAL(&param->parameter);
  372 + param->param_type = PDO_PARAM_STR;
  373 + ZVAL_STRINGL(param->parameter, Z_BVAL_P(param->parameter) ? "t" : "f", 1, 1);
  374 + }
  375 + }
  376 +#if HAVE_PQPREPARE
365 377 }
366   -#endif
  378 +#endif
367 379 return 1;
368 380 }
369 381
51 ext/pdo_pgsql/tests/bug62593.phpt
... ... @@ -0,0 +1,51 @@
  1 +--TEST--
  2 +PDO PgSQL Bug #62593 (Emulate prepares behave strangely with PARAM_BOOL)
  3 +--SKIPIF--
  4 +<?php
  5 +if (!extension_loaded('pdo') || !extension_loaded('pdo_pgsql')) die('skip not loaded');
  6 +require dirname(__FILE__) . '/config.inc';
  7 +require dirname(__FILE__) . '/../../../ext/pdo/tests/pdo_test.inc';
  8 +PDOTest::skip();
  9 +?>
  10 +--FILE--
  11 +<?php
  12 +require dirname(__FILE__) . '/../../../ext/pdo/tests/pdo_test.inc';
  13 +$db = PDOTest::test_factory(dirname(__FILE__) . '/common.phpt');
  14 +$db->setAttribute(PDO::ATTR_EMULATE_PREPARES, true);
  15 +$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_SILENT);
  16 +$errors = array();
  17 +
  18 +$value = true;
  19 +$query = $db->prepare('SELECT :foo IS FALSE as val_is_false');
  20 +$query->bindValue(':foo', $value, PDO::PARAM_BOOL);
  21 +$query->execute();
  22 +$errors[] = $query->errorInfo();
  23 +var_dump($value);
  24 +
  25 +$query->bindValue(':foo', 0, PDO::PARAM_BOOL);
  26 +$query->execute();
  27 +$errors[] = $query->errorInfo();
  28 +
  29 +// Verify bindParam maintains reference and only passes when execute is called
  30 +$value = true;
  31 +$query->bindParam(':foo', $value, PDO::PARAM_BOOL);
  32 +$value = false;
  33 +$query->execute();
  34 +$errors[] = $query->errorInfo();
  35 +var_dump($value);
  36 +
  37 +$expect = 'No errors found';
  38 +
  39 +foreach ($errors as $error)
  40 +{
  41 + if (strpos('Invalid text representation', $error[2]) !== false)
  42 + {
  43 + $expect = 'Invalid boolean found';
  44 + }
  45 +}
  46 +echo $expect;
  47 +?>
  48 +--EXPECTF--
  49 +bool(true)
  50 +bool(false)
  51 +No errors found

Tip: You can add notes to lines in a file. Hover to the left of a line to make a note

Something went wrong with that request. Please try again.