11

I'm having a strange problem with php PDO and mysql.

I have the following table:

create table test_table ( id integer, value text );

with a single row:

insert into test_table values (1, "asdf");

when I try to update this single row with a prepared statement, I got different behaviours depending on the syntax I use:

// connection to db (common code)
$dbh = new PDO("mysql:host=localhost;dbname=test", "myuser", "mypass");

=========================================================

// WORKING
$q = 'update test_table set id=1, value='.rand(0,99999).' where id=1';
$dbh->exec($q);

=========================================================

// WORKING
$q = 'update test_table set value=:value where id=:id';
$par = array(
    "id" => 1,
    "value" => rand(0,99999)
  );
$sth = $dbh->prepare($q, array(PDO::ATTR_CURSOR => PDO::CURSOR_FWDONLY));
$sth->execute($par);

=========================================================

// NOT WORKING
$q = 'update test_table set id=:id, value=:value where id=:id';
$par = array(
    "id" => 1,
    "value" => rand(0,99999)
  );
$sth = $dbh->prepare($q, array(PDO::ATTR_CURSOR => PDO::CURSOR_FWDONLY));
$sth->execute($par);

In the third case, on my server, the update is not performed on the row, without any reason nor exception/error. On another server it works. I' not looking for answers like: "and so? use the first or second implementation" :)

I'm asking why the third implementation doesn't work because I'm migrating a lot of code from a server to another one (it's not my code) and it contains a lot of queries like this one and I have no time to fix them one by one. On the current server it works and on the new one it doesn't.

Why the third implementation doesn't work? Is there any kind of configuration for php/pdo/mysql which could affect this behaviour?

Thanks.

Update: Tried to sqeeze out error messages:

$dbh->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION );

try {
// NOT WORKING
  $q = 'update test_table set id=:id, value=:value where id=:id';
  $par = array(
    "id" => 1,
    "value" => rand(0,99999)
  );
  $sth = $dbh->prepare($q, array(PDO::ATTR_CURSOR => PDO::CURSOR_FWDONLY));
  print_r($sth);
  print_r($dbh->errorInfo());
} catch(PDOException $e) {
  echo $e->getMessage();
}

$sth->execute($par);

Executing this code on both servers (working and not working):

PDOStatement Object
(
    [queryString] => update test_table set id=:id, value=:value where id=:id
)
Array
(
    [0] => 00000
    [1] => 
    [2] => 
)

Update 2

Look at this further test:

create table test_table ( value0 text, value text );
insert into test_table values ("1", "pippo");

// NOT WORKING

$q = 'update test_table set value0=:value0, value=:value where value0=:value0';
$par = array(
    "value0" => "1",
    "value" => rand(0, 839273)
);

create table test_table ( value0 text, value text );
insert into test_table values ("pippo", "1");

// WORKING

$q = 'update test_table set value=:value, value0=:value0 where value=:value';
$par = array(
    "value" => "1",
    "value0" => rand(0, 839273)
);

Incredible, isn't it? My suspect now is that exists some special update beahaviour specifically made for the first column of every table on PDO+placeholder handling.

Lorenzo Marcon
  • 8,029
  • 5
  • 38
  • 63
  • 1
    You are using :id both to change the value of 'id' as well as to identify the row. Are you sure that's what you want to do? – middus Feb 09 '12 at 11:05
  • 1
    @middus: as he said:"I'm migrating a lot of code from a server to another one (not my code) and it contains a lot of queries like this one and I have no time to fix them one by one" – Gianpaolo Di Nino Feb 09 '12 at 11:07
  • I know, it's not my code. I'd never do that. Still, I'd like to know why it doesn't work on a server and it does on another. – Lorenzo Marcon Feb 09 '12 at 11:08
  • And the other examples work on both servers? – middus Feb 09 '12 at 11:09
  • Yes, on the current server the three examples work. – Lorenzo Marcon Feb 09 '12 at 11:12
  • 2
    [How to squeeze error message out of PDO?](http://stackoverflow.com/q/3726505) – Pekka Feb 09 '12 at 11:12
  • good point @Pekka, I got a PDOException on my server. Still I don't get what's the point and if does exist a way to avoid it without rewriting the code – Lorenzo Marcon Feb 09 '12 at 11:21
  • Is the version of MySQL identical on the two servers? – Hecksa Feb 09 '12 at 11:29
  • @Hecksa, no. But I tried on three servers, actually. Not working: mysql 5.1.53 and 5.5.20, working: 5.1.56. Given this, I don't think mysql version could be responsible – Lorenzo Marcon Feb 09 '12 at 11:33
  • 1
    Any other points of difference between the servers, then? Maybe one database has an index on id and one doesn't, or anything else you can think of? As you say, doesn't seem feasible that mysql version would cause it given those versions. – Hecksa Feb 09 '12 at 11:42
  • I replied on the answer comment thread about this.. anyway, thanks for your time. – Lorenzo Marcon Feb 09 '12 at 14:19

4 Answers4

8

http://php.net/manual/en/pdo.prepare.php states:

You must include a unique parameter marker for each value you wish to pass in to the statement when you call PDOStatement::execute(). You cannot use a named parameter marker of the same name more than once in a prepared statement, unless emulation mode is on.

As this indicates, the likely reason behind your code working on one server and not another is that PDO::ATTR_EMULATE_PREPARES is disabled on the server which the code fails on. As the documentation says, this attribute effectively removes the restriction preventing you from using a parameter marker of the same name twice (along with some other restrictions).

Hecksa
  • 2,762
  • 22
  • 34
  • It's a partial answer :P I'll update it if I figure anything more out, but I'm getting nowhere at the moment. Can't see why it should ever work, to be honest. – Hecksa Feb 09 '12 at 11:32
  • well.. why not? If you try a raw update on mysql console on the test table it works (example 1). The problem happens only if you replace the values with placeholders. – Lorenzo Marcon Feb 09 '12 at 11:37
  • As explained in the answer - it's the use of PDO that causes the issue. Putting `:id` in a query twice is not permitted, it's expected that you use another parameter (albeit containing the same value) with a different name if you want to put the same value in the query twice. – Hecksa Feb 09 '12 at 11:44
  • 1
    for example, `update test_table set id=:id, value=:value where id=:idCopy` would be fine. – Hecksa Feb 09 '12 at 11:46
  • Yes, this works, forget my deleted comment. It's a placeholder issue. But still The original question aims to understand how could it work.. bug of that PDO version? – Lorenzo Marcon Feb 09 '12 at 11:56
  • well, incredible but this works: $q = 'update test_table set id=:id, value=:value where value=:value'; $par = array( "id" => rand(0, 839273), "value" => "pippo" ); – Lorenzo Marcon Feb 09 '12 at 11:59
  • I repeated the placeholder, but working on "value" instead of "id". Could it be a sort of 'reserved' word? id IS NOT primary key or anything strange, only a integer column. I tried surrounding it with backticks. – Lorenzo Marcon Feb 09 '12 at 12:01
  • Something else I've found - bugs.php.net/bug.php?id=34625 - "MySQL does not support cursors and the driver cannot emulate them for you." Maybe try running the queries without setting the cursor attribute in PDO? – Hecksa Feb 09 '12 at 12:03
  • and with all the new tests you've done - not aware of id being special at all, but that's certainly strange behaviour. Do value and id have the same data type? – Hecksa Feb 09 '12 at 12:05
  • id integer, value text. now I try to invert them.. and another test renaming the id column. – Lorenzo Marcon Feb 09 '12 at 12:07
  • Well, I'm going crazy or something very strange is happening here. I updated the problem description, please take a look. Thanks – Lorenzo Marcon Feb 09 '12 at 12:26
  • Really strange - I imagine there's something we're both missing here but I really can't put my finger on it. If I have any ideas I'll let you know but I think it's gonna take someone more knowledgable than me if I'm honest. – Hecksa Feb 09 '12 at 13:08
  • Same test (Update2), postgresql db, pgsql pdo driver. It works like a charm. I can only suppose that it could be a problem on pdo_mysql. I know, they say to avoid that syntax on the manual, but still I think it should produce an exception or something if used, and not a unpredictable behaviour. I'll perform further tests, but since you gave me the right direction to investigate on, I'll eventually mark yours as the right answer. – Lorenzo Marcon Feb 09 '12 at 13:29
  • Did you try running the queries without the cursor attribute? That's MySQL specific, I believe - could be relevant since it's working on Postgresql. – Hecksa Feb 09 '12 at 13:32
  • Just tried replacing $sth = $dbh->prepare($q, array(PDO::ATTR_CURSOR => PDO::CURSOR_FWDONLY)); with $sth = $dbh->prepare($q, array()); and got no difference. Still not working.. are there any other options I may try to pass to prepare()? – Lorenzo Marcon Feb 09 '12 at 13:49
  • There's plenty of others, but I can't imagine they're relevant. They're mainly just changing the default behaviour of the database query slightly, and if the query doesn't work with none (ie default behaviour) I'd be surprised if it works in a strange non-default setup. – Hecksa Feb 09 '12 at 14:03
  • I noticed that I use 20090626 version of pdo_mysql.so, while in the current server (where that syntax works) pdo_mysql.so version is 20060613. I can only assume that something changed in there three years :) Well, I think it'll end up with a code refactoring, even if that's not my code.. and many thanks for your time. – Lorenzo Marcon Feb 09 '12 at 14:18
  • No problem - thanks for an interesting problem, sorry I couldn't give you a complete answer though! – Hecksa Feb 09 '12 at 14:38
  • "You cannot use a named parameter marker of the same name more than once in a prepared statement". Could you please clarify? You mean the problem is that column name in the statement matches name of the named paramater marker? – JustLearn Oct 21 '20 at 09:52
  • Take this example from the question (first labelled as "Not Working"): `$q = 'update test_table set id=:id, value=:value where id=:id';` Notice that `:id` is used twice - once in `set id=:id` and again in `where id=:id`. The sentence you asked about basically means you can't do that. A named parameter marker like :id can only be used *once* in a prepared statement. Nothing to do with the column name, it's simply because `:id` has been used twice in the query (notice the : at the start - you could use `id` as much as you wanted without the colon). – Hecksa Oct 21 '20 at 14:48
0

Facing the same issue

with update statement (insert and select working well with array set)

i found out execute by binding Params :

$qry = $bd->prepare("UPDATE users SET name = :name WHERE id = :id");
$qry->bindParam(':name','user1');          
$qry->bindParam(':id','1');   
$qry->execute();
Romylussone
  • 773
  • 1
  • 8
  • 19
-1
try {
     $db = new PDO('mysql:host=localhost;dbname=vendor_management_system', 'root', '');
     $db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
     $db->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
}catch(PDOException $e) {
    echo 'ERROR: ' . $e->getMessage();
}




$fields[] = 'car_name';
$fields[] = 'model_no';
$fields[] = 'maker_id';
$fields[] = 'dealer_id';

$values[] = "testcar";
$values[] = "no#1";
$values[] = 2;
$values[] = 4;



echo SQLUpdate('car_details', $fields, $values,'car_id = 32 and car_name = "testname"',$db);




//START: SQLUpdate
//$fields = array of fields in DB
//$values = array of values respective to the $fields
 function SQLUpdate($table,$fields,$values,$where,$db) {



  //build the field to value correlation
  $buildSQL = '';
  if (is_array($fields)) {

        //loop through all the fields and assign them to the correlating $values
        foreach($fields as $key => $field) :
      if ($key == 0) {
            //first item
            $buildSQL .= $field.' = ?';
          } else {
            //every other item follows with a ","
            $buildSQL .= ', '.$field.' = ?';
          }
    endforeach;

  } else {
    //we are only updating one field
        $buildSQL .= $fields.' = :value';
  }

  $prepareUpdate = $db->prepare('UPDATE '.$table.' SET '.$buildSQL.'
WHERE '.$where);

  //execute the update for one or many values
  if (is_array($values)) {
    $affected_rows=$prepareUpdate->execute($values);
    return $affected_rows;
  } else {
    $affected_rows=$prepareUpdate->execute(array(':value' => $values));
    return $affected_rows;
  }


  //record and print any DB error that may be given
  $error = $prepareUpdate->errorInfo();
  if ($error[1]) print_r($error);

} 
//END: SQLUpdate
-2
$maker_id=1;
$stmt = $db->prepare("UPDATE car_details SET maker_id=?");
$affected_rows=$stmt->execute(array($maker_id));
echo $affected_rows.' were affected';