From: tim at moocowproductions dot org
Operating system: Gentoo Linux & Mac OS X Leopard
PHP version: 5.2.5
PHP Bug Type: MySQLi related
Bug description: prepared statements not sending correct data to MySQL when
using transactions
Description:
------------
When I am trying to run a transaction, with inserts that depend on
each other, I get the following:
Cannot add or update a child row: a foreign key constraint fails
(`q2test/UserPermissions`, CONSTRAINT `up_userid_fk` FOREIGN KEY
(`userID`) REFERENCES `Users` (`userID`) ON DELETE CASCADE)
I thought it might be MySQL at first, but when I tried to prepare the
statements by hand in the command-line interface, I did not have this
problem. The issue seems to be that it is trying to set the userID
variable to 0, when in fact it should be LAST_INSERT_ID.
Now if I grab LAST_INSERT_ID and put it into a variable, I get the
correct number. But if I try to execute the second query, it appears
to set it back to 0 for some reason. I turned on the MySQL general log
which indicates that a quit is being issued after the prepare (so PHP
isn't getting as far as to execute the statement):
071122 19:53:39 31 Connect [EMAIL PROTECTED] on q2test
31 Query set autocommit=0
31 Prepare [1] INSERT INTO Users (username,
firstName, lastName, password, phone, email, role) VALUES (?, ?, ?, ?,
?, ?,?)
31 Execute [1] INSERT INTO Users (username,
firstName, lastName, password, phone, email, role) VALUES ('test',
'test', 'test', 'NI!GpasswordNI!G', '555', 'email','estimator')
31 Prepare [2] INSERT INTO UserPermissions
VALUES( userID=?,
canAddBuilders=?,
canAddAddresses=?,
canAddPlans=?,
canAddUsers=?,
canModifyBuilders=?,
canModifyAddresses=?,
canModifyPlans=?,
canModifyUsers=?,
canDeleteBuilders=?,
canDeleteAddresses=?,
canDeletePlans=?,
canDeleteUsers=?,
canAssignPlans=?
)
31 Quit
Now I tried to set specific values in the code to insert another
userID and I also tried to remove the foreign key restriction. When
this happens, both INSERTS complete, but the insert in the
UserPermissions table has a userID of instead of the one from the
previous insert (or one that I hard-code in for testing).
Reproduce code:
---------------
The code that generates this is:
<?php
include '../htdocs/global.inc.php';
$dbHost = '127.0.0.1';
$dbDatabase = 'test';
$dbUsername = 'test';
$dbPassword = '?????';
$mysqli = new mysqli($dbHost, $dbUsername, $dbPassword, $dbDatabase)
or die("Cannot connect to database");
function generateSalt($length = 4)
{
$chars =
"[EMAIL PROTECTED]&*-+";
$code = "";
while (strlen($code) < $length) {
$code .= $chars[mt_rand(0,strlen($chars))];
}
return $code;
}
function addUser($username, $firstName, $lastName, $role, $phone, $email,
$password)
{
global $mysqli;
$salt =generateSalt();
$password = $salt.$password.$salt;
$stmt = $mysqli->stmt_init();
$stmt->prepare("INSERT INTO Users (username, firstName, lastName,
password, phone, email, role) VALUES (?, ?, ?, ?, ?, ?,?)") or
die($mysqli->error);
$stmt->bind_param('sssssss', $username, $firstName, $lastName,
$password,
$phone, $email, $role);
if(!$stmt->execute())
{
$stmt->close();
return 0;
}
$stmt->close();
echo $mysqli->thread_id."\n";
return $mysqli->insert_id;
}
function addUserPermissions($userID, $canAddBuilders, $canAddAddresses,
$canAddPlans, $canAddUsers, $canModifyBuilders, $canModifyAddresses,
$canModifyPlans, $canModifyUsers, $canDeleteBuilders, $canDeleteAddresses,
$canDeletePlans, $canDeleteUsers, $canAssignPlans)
{
global $mysqli;
$stmt = $mysqli->stmt_init();
$stmt->prepare("INSERT INTO UserPermissions
VALUES( userID=?,
canAddBuilders=?,
canAddAddresses=?,
canAddPlans=?,
canAddUsers=?,
canModifyBuilders=?,
canModifyAddresses=?,
canModifyPlans=?,
canModifyUsers=?,
canDeleteBuilders=?,
canDeleteAddresses=?,
canDeletePlans=?,
canDeleteUsers=?,
canAssignPlans=?
)");
$stmt->bind_param('isssssssssssss', $userID, $canAddBuilders,
$canAddAddresses, $canAddPlans, $canAddUsers, $canModifyBuilders,
$canModifyAddresses, $canModifyPlans, $canModifyUsers, $canDeleteBuilders,
$canDeleteAddresses, $canDeletePlans, $canDeleteUsers, $canAssignPlans);
if(!$stmt->execute())
{
echo $mysqli->thread_id."\n";
echo $stmt->error;
$stmt->close();
return false;
}
$stmt->close();
echo $mysqli->thread_id."\n";
return true;
}
echo $mysqli->thread_id."\n";
$mysqli->autocommit(FALSE);
$userID = addUser('test', 'test', 'test', 'estimator', '555', 'email',
'password');
if($userID == 0)
$mysqli->rollback();
addUserPermissions($userID, 'n', 'n', 'n', 'n', 'n', 'n', 'n', 'n', 'n',
'n', 'n', 'n', 'n');
$mysqli->close();
And the schema is:
mysql> show create table Users\G
*************************** 1. row ***************************
Table: Users
Create Table: CREATE TABLE `Users` (
`userID` int(10) unsigned NOT NULL auto_increment,
`username` varchar(32) NOT NULL,
`password` varchar(32) NOT NULL,
`salt` varchar(4) NOT NULL,
`firstName` varchar(32) NOT NULL,
`lastName` varchar(48) NOT NULL,
`role` enum('Admin','Webmaster','Estimator') NOT NULL default
'Estimator',
`phone` varchar(40) default NULL,
`email` varchar(255) NOT NULL,
PRIMARY KEY (`userID`),
UNIQUE KEY `username_idx` USING BTREE (`username`),
UNIQUE KEY `firstlastname_idx` (`firstName`,`lastName`)
) ENGINE=InnoDB AUTO_INCREMENT=51 DEFAULT CHARSET=utf8
1 row in set (0.08 sec)
mysql> show create table UserPermissions\G
*************************** 1. row ***************************
Table: UserPermissions
Create Table: CREATE TABLE `UserPermissions` (
`userID` int(10) unsigned NOT NULL,
`canAddBuilders` enum('Y','N') NOT NULL default 'N',
`canAddAddresses` enum('Y','N') NOT NULL default 'N',
`canAddPlans` enum('Y','N') NOT NULL default 'N',
`canAddUsers` enum('Y','N') NOT NULL default 'N',
`canModifyBuilders` enum('Y','N') NOT NULL default 'N',
`canModifyAddresses` enum('Y','N') NOT NULL default 'N',
`canModifyPlans` enum('Y','N') NOT NULL default 'N',
`canModifyUsers` enum('Y','N') NOT NULL default 'N',
`canDeleteBuilders` enum('Y','N') NOT NULL default 'N',
`canDeleteAddresses` enum('Y','N') NOT NULL default 'N',
`canDeletePlans` enum('Y','N') NOT NULL default 'N',
`canDeleteUsers` enum('Y','N') NOT NULL default 'N',
`canAssignPlans` enum('Y','N') NOT NULL default 'N',
PRIMARY KEY (`userID`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
1 row in set (0.01 sec)
Expected result:
----------------
Both inserts to complete and the transaction is committed.
Actual result:
--------------
First INSERT completes but the second fails with this:
Cannot add or update a child row: a foreign key constraint fails
(`q2test/UserPermissions`, CONSTRAINT `up_userid_fk` FOREIGN KEY
(`userID`) REFERENCES `Users` (`userID`) ON DELETE CASCADE)
--
Edit bug report at http://bugs.php.net/?id=43381&edit=1
--
Try a CVS snapshot (PHP 4.4):
http://bugs.php.net/fix.php?id=43381&r=trysnapshot44
Try a CVS snapshot (PHP 5.2):
http://bugs.php.net/fix.php?id=43381&r=trysnapshot52
Try a CVS snapshot (PHP 5.3):
http://bugs.php.net/fix.php?id=43381&r=trysnapshot53
Try a CVS snapshot (PHP 6.0):
http://bugs.php.net/fix.php?id=43381&r=trysnapshot60
Fixed in CVS: http://bugs.php.net/fix.php?id=43381&r=fixedcvs
Fixed in release:
http://bugs.php.net/fix.php?id=43381&r=alreadyfixed
Need backtrace: http://bugs.php.net/fix.php?id=43381&r=needtrace
Need Reproduce Script: http://bugs.php.net/fix.php?id=43381&r=needscript
Try newer version: http://bugs.php.net/fix.php?id=43381&r=oldversion
Not developer issue: http://bugs.php.net/fix.php?id=43381&r=support
Expected behavior: http://bugs.php.net/fix.php?id=43381&r=notwrong
Not enough info:
http://bugs.php.net/fix.php?id=43381&r=notenoughinfo
Submitted twice:
http://bugs.php.net/fix.php?id=43381&r=submittedtwice
register_globals: http://bugs.php.net/fix.php?id=43381&r=globals
PHP 3 support discontinued: http://bugs.php.net/fix.php?id=43381&r=php3
Daylight Savings: http://bugs.php.net/fix.php?id=43381&r=dst
IIS Stability: http://bugs.php.net/fix.php?id=43381&r=isapi
Install GNU Sed: http://bugs.php.net/fix.php?id=43381&r=gnused
Floating point limitations: http://bugs.php.net/fix.php?id=43381&r=float
No Zend Extensions: http://bugs.php.net/fix.php?id=43381&r=nozend
MySQL Configuration Error: http://bugs.php.net/fix.php?id=43381&r=mysqlcfg