I have a database table with an unsigned int that represents a wallet balance. this column is called wallet. It is in a table called users.
The following query fails via mysql cli:
UPDATE users set wallet = `wallet` - 550000000 WHERE username = 'user'
With error message:
ERROR 1690 (22003): BIGINT UNSIGNED value is out of range in
'(database.users.wallet - 550000000)'
The issue is, when executed via PDO with ERRMODE_EXCEPTION, it brings wallet balance to 0 and continues execution without raising the mentioned error in an exception
SELECT ##sql_mode returns nothing in both the code and mysql cli.
Here is the function that creates my database handle and returns it to the query object
//This function connects to a database
public function connect($dbname,DBConfig $config = NULL)
{
//If the connection is already set return it
if(isset($this->dbs[$dbname]))
return $this->dbs[$dbname];
//If the config object is not already set, and still null, throw exception
if(!isset($this->_config[$dbname]))
{
if($config === NULL)
throw new PDOdatabasesException('Database configuration object is not set',1);
$this->_config[$dbname] = $config;
}
$config = $this->_config[$dbname];
//Create a PDO object
$this->dbs[$dbname] = new PDO(
$config::type .
':host=' . $config::$host .
';port=' . $config::$port .
';dbname=' . $dbname,
$config::$user,
$config::$password
);
//Tell the handle to throw exceptions
$this->dbs[$dbname]->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION );
$this->dbs[$dbname]->setAttribute( PDO::ATTR_EMULATE_PREPARES, false );
//Return a reference to the newly created PDO object
return $this->dbs[$dbname];
} //End connect()
Trying to save the need to paste a bunch of unnecessary code.. my query object takes a database handle from above, and prepares and executes this statement:
'UPDATE users SET wallet = wallet - ? WHERE id = ?'
binding the amount (approximately 10 million) and the user id, then executing.
Wallet balance can be at 0 and the query will still execute successfully. Why does this happen when the cli cannot? It does not make sense!!
I believe i need to once again reiterate This query SHOULD fail if it drops wallet below 0!
It succeeds through pdo, but not through mysql cli, my question is WHY??
The result of wallet - 550000000 should be less than 0 and your column is UNSIGNED. Try to change your column type from BIGINT UNSIGNED to BIGINT
This is apparently an unsolved bug in PDO::bindValue() and PDO::bindParam()
EDIT:
I am apparently stupid and did not realize that data_type was REQUIRED to be defined when binding an integer to a query.
I have simplified the code to be the following:
$db = new PDO('mysql:dbname=test;host=127.0.0.1','omitted','omitted');
$db->setAttribute(PDO::ATTR_ERRMODE,PDO::ERRMODE_EXCEPTION);
try{
$statement = $db->prepare("UPDATE test set number = number - ? WHERE id = 1");
$statement->bindValue(1,1000000000);
$statement->execute();
}catch(PDOException $e)
{
die($e->getMessage());
}
changing the statement to (and not using bindValue or bindParam):
$statement = $db->prepare("UPDATE test set number = number - 100000000 WHERE id = 1");
then the code throws the expected exception
CHANGING AGAIN TO:
$statement>bindValue(1,1000000000,PDO::PARAM_INT);
throws as expected
Related
I've come across with a problem. My framework was working just fine with PHP 5.3.0. I upgraded my PHP version to PHP 5.4.x and I started to have few issues with some parts of my framework.
After PHP version upgrade, PDO lastInsterId() always returns 0.
I have auto-increment field called id.
It is adding the data to database without any problems.
For some reason I keep getting 0 as last insert id.
Here is my code;
databaseobjects.php
public static function create () {
global $db;
$attributes = self::sanitize(static::$fields);
$sql = "INSERT INTO ".PREFIX.static::$table_name." (";
$sql .= join(", ", array_keys($attributes));
$sql .= ") VALUE (:";
$sql .= join(", :", array_keys($attributes));
$sql .= ")";
return ($db->crudQuery($sql, $attributes)) ? true : false;
}
public static function lastInsertID () {
global $db;
return $db->handler->lastInsertId();
}
database.php
public function crudQuery($sql, $data) {
$sth = $this->handler->prepare($sql);
return $sth->execute($data);
}
First create() method is called, then crudQuery() method is called.
As I mentioned before, I can add the data successfully to MySQL database.
Unfortunately when I call lastInsterID() method, it always returns 0.
I will be really glad if you can help me out with this problem before I will get the last ID with SQL Query (:
Other than a bug in php/PDO or your framework, there are two possibilities. Either lastInsertId() is called on a different MySQL connection than the insert, or you are generating the id in your application/framework and inserting it, rather than letting auto_increment generate it for you. Which column in the table is the primary key/auto_increment? Is that column included in $attributes in your create() function?
You can test PDO to make sure that part is working correctly with this code (in a new file):
// Replace the database connection information, username and password with your own.
$conn = new PDO('mysql:dbname=test;host=127.0.0.1', 'user', 'password');
$conn->exec('CREATE TABLE testIncrement ' .
'(id INT NOT NULL AUTO_INCREMENT PRIMARY KEY, name VARCHAR(50))');
$sth = $conn->prepare('INSERT INTO testIncrement (name) VALUES (:name)');
$sth->execute([':name' => 'foo']);
var_dump($conn->lastInsertId());
$conn->exec('DROP TABLE testIncrement');
When I ran this script, the output was
string(1) "1"
After you commit a transaction PDO::lastInsertID() will return 0, so best to call this method before the transaction is committed.
The one other problem could be using $pdo->exec($sql) instead of $pdo->query($sql).
exec($sql) will return always 0 when you use $pdo->lastInsertId(). So use query() instead.
I got a 0 when the last insert statement failed due to a foreign key contraint. last_error was a string.
When no exception is thrown, lastInsertId returns 0. However, if lastInsertId is called before calling commit, the right id is returned.
http://php.net/manual/es/pdo.lastinsertid.php
I am having a problem where a prepared MySQL stored procedure call runs fine in a transaction, and I see the expected results from the stored procedure, but the changes do not appear to be saving to the actual database.
The PHP side of things looks like this:
$options = array();
$db = new PDO("mysql:host=localhost;dbname=mydb", "myuser", "mypass", $options);
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$db->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
// ..... .... ... .. .
$response["error"] = true;
if ($db->beginTransaction() == true)
{
try
{
$stmt = $db->prepare("call Layout_Row_Add(:pageid, :position);");
// $jason->page_id
$stmt->bindValue(':pageid', (int)$jason->page_id, PDO::PARAM_INT);
// $jason->position
$stmt->bindValue(':position', (int)$jason->position, PDO::PARAM_INT);
$stmt->execute();
$response["dbg1"] = $jason->page_id;
$response["dbg2"] = $jason->position;
$response["intrans1"] = $db->inTransaction();
$row = $stmt->fetch();
$db->commit();
$response["intrans2"] = $db->inTransaction();
$response["new_row_id"] = $row["NewRowId"];
$response["error"] = false;
}
catch (PDOException $e)
{
$db->rollBack();
$response["errortext"] = "PDO exception: " . $e->getMessage();
}
catch (Exception $exc)
{
$db->rollBack();
$response["errortext"] = "Exception: " . $e->getMessage();
}
}
else
{
$response["errortext"] = "Couldn't start transaction";
}
The $response variable gets encoded into JSON and sent back to the browser, which gets this:
error false
dbg1 1
dbg2 3
intrans1 true
intrans2 false
new_row_id 21
Everything looks exactly like it should, new_row_id is at its expected value meaning the autoincrement field ticked up, and the debug fields and transaction info is as expected.
However, doing a select * in MySQL Workbench doesn't return any of these rows that were supposedly added by the procedure. Running the procedure itself in MySQL Workbench works fine, as in, the commit actually sticks. Here's the procedure itself:
CREATE DEFINER=`myuser`#`myhost` PROCEDURE `Layout_Row_Add`(PageId int, Position int)
BEGIN
declare NewRowId int unsigned default 0;
update pages_layout_rows set ordinal = ordinal + 1 where page_id = PageId and ordinal >= Position;
insert into pages_layout_rows (page_id, ordinal) values (PageId, Position);
set NewRowId = last_insert_id();
select NewRowId;
END
The table is set to InnoDB, so transaction support should be available. I don't really know what to try next.
Found it - it looks like if you don't consume all the resultsets, the transaction appears to get rolled back in the end anyway. A stored procedure call adds an empty resultset as the last resultset, so that's what's happening.
// ...
$row = $stmt->fetch();
// let's consume all resultsets
while($stmt->nextRowset() && $stmt->columnCount());
$sol->db->commit();
// ...
I have been developing a wrapper class inside a small PHP framework and I am experiencing something weird with one of the method in my class.
First here is the class code :
<?php
namespace Framework;
header('Content-Type: text/html; charset=UTF-8');
class cConnexion
{
public $m_log;//This will be instantiate as a cLog object (another class in the namespace)
private $m_DB;//PDO instance
private $m_Host;//Host of the conneciton string
private $m_DBName;//Name of the DB to connecte to
private $m_Driver;//Driver name, mysql for MySQL, sqlsrv for MSSQL
private $m_Login;//Username for authentification
private $m_Password;//Password for authentification
public function __construct($p_Driver = "mysql", $p_host, $p_DBName, $p_login, $p_password)
{
$this->m_Host= $p_host;
$this->m_Driver = $p_Driver;
$this->m_DBName = $p_DBName;
$this->m_Login = $p_login;
$this->m_Password = $p_password;
$this->m_log = new cLog('', '', true, false, false);
}
public function SecureExecute($p_query, $p_param)
{
try
{
$stmt = $this->m_DB->prepare($p_query);
$status = $stmt->execute($p_param);
$this->m_log->setMessageFR("Aucune exception ne s'est levé. ");
$this->m_log->setMessageEN("No exceptions were raised. ");
$this->m_log->setSuccess($status);
return $status;
}
catch(\PDOException $e)
{
$this->m_log->setMessageFR("Une exception de type PDO est levé. ".$e->getMessage());
$this->m_log->setMessageEN("A PDO exception was raised. ".$e->getMessage());
$this->m_log->setSuccess(false);
return false;
}
}
}
?>
Note that I have remove every other method that are not relevant to the question but kept the constructor and all the properties. Also note that there is a method to connect the $m_DB property to the DB, you might want to assume it has already been called.
Here is the problem I need help solving:
Step by Step description of what is going on:
I create an instance of cConnexion and use the connecteToDB method that isn't described in here but it is working properly because I can use another method to do MySQL 'SELECT' statement.
I tried to UPDATE a row which doesn't exist in a MySQL table but the object still tell me that the UPDATE was successful.
Is that normal? I read the PDO::PDOStatement::execute Doc and is says that the return value of PDO::PDOStatement::execute is either true (on success) or false(if fails).
Here is an exemple of code using the method:
$sql = "UPDATE Employes SET CieNo = 3, Nom = :Name, Dept = :Department WHERE EmplCode = :Code";
$params = array
(
'Code'=>$EmplCode,// = 123
'Name'=>($lname." ".$fname),// = Lalonde Sebastien
'Department'=>$dep,// = INFO
);
$cn = new cConnexion(/*Connection string and params here*/);
$cn->connectToDB();
if($cn->SecureExecute($sql, $params))
{
echo "SQL1: true";
}
The following code always output "SQL1: true" even when the UPDATE shouldn't had work...(because the Employes table doesn't contains an EmplCode = 123).
Can someone suggest a way to change my class so that the method SecureExecute() return false when this happen?
The execute method returns true on success, which in your case is true: The mysql query was executed successfully, however no rows were effected.
If you need to check whether the update actually altered anything, you need to use "rowCount":
$stmt = $this->m_DB->prepare('Update ...');
$stmt->execute();
$effected = $stmt->rowCount();
You can then decide if you want to return a true/false value from your method.
http://www.php.net/manual/en/pdostatement.rowcount.php
You'll only receive an error if the SQL command itself is in error. So if Employes is a valid table, and CieNo, Nom, Dept, and EmplCode are all valid columns, then your SQL statement is valid, even though it doesn't always find a row to UPDATE. Technically, it was "successful", just not in the way you're wanting.
Take a look at rowCount()
for info on how to determine whether 0 or more rows were affected by your query.
In fact, this whole class appears to be utterly useless. Exactly the same outcome can be achieved with raw PDO, with very small addition.
$cn = new PDO(/*Connection string and params here*/);
$cn->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION );
$stmt = $cn->prepare($sql);
$stmt->execute($params);
I've come across with a problem. My framework was working just fine with PHP 5.3.0. I upgraded my PHP version to PHP 5.4.x and I started to have few issues with some parts of my framework.
After PHP version upgrade, PDO lastInsterId() always returns 0.
I have auto-increment field called id.
It is adding the data to database without any problems.
For some reason I keep getting 0 as last insert id.
Here is my code;
databaseobjects.php
public static function create () {
global $db;
$attributes = self::sanitize(static::$fields);
$sql = "INSERT INTO ".PREFIX.static::$table_name." (";
$sql .= join(", ", array_keys($attributes));
$sql .= ") VALUE (:";
$sql .= join(", :", array_keys($attributes));
$sql .= ")";
return ($db->crudQuery($sql, $attributes)) ? true : false;
}
public static function lastInsertID () {
global $db;
return $db->handler->lastInsertId();
}
database.php
public function crudQuery($sql, $data) {
$sth = $this->handler->prepare($sql);
return $sth->execute($data);
}
First create() method is called, then crudQuery() method is called.
As I mentioned before, I can add the data successfully to MySQL database.
Unfortunately when I call lastInsterID() method, it always returns 0.
I will be really glad if you can help me out with this problem before I will get the last ID with SQL Query (:
Other than a bug in php/PDO or your framework, there are two possibilities. Either lastInsertId() is called on a different MySQL connection than the insert, or you are generating the id in your application/framework and inserting it, rather than letting auto_increment generate it for you. Which column in the table is the primary key/auto_increment? Is that column included in $attributes in your create() function?
You can test PDO to make sure that part is working correctly with this code (in a new file):
// Replace the database connection information, username and password with your own.
$conn = new PDO('mysql:dbname=test;host=127.0.0.1', 'user', 'password');
$conn->exec('CREATE TABLE testIncrement ' .
'(id INT NOT NULL AUTO_INCREMENT PRIMARY KEY, name VARCHAR(50))');
$sth = $conn->prepare('INSERT INTO testIncrement (name) VALUES (:name)');
$sth->execute([':name' => 'foo']);
var_dump($conn->lastInsertId());
$conn->exec('DROP TABLE testIncrement');
When I ran this script, the output was
string(1) "1"
After you commit a transaction PDO::lastInsertID() will return 0, so best to call this method before the transaction is committed.
The one other problem could be using $pdo->exec($sql) instead of $pdo->query($sql).
exec($sql) will return always 0 when you use $pdo->lastInsertId(). So use query() instead.
I got a 0 when the last insert statement failed due to a foreign key contraint. last_error was a string.
When no exception is thrown, lastInsertId returns 0. However, if lastInsertId is called before calling commit, the right id is returned.
http://php.net/manual/es/pdo.lastinsertid.php
I managed to run the following code to insert into my table on first try. Then, I deleted that row in PHPMyAdmin to test my code further. I also noticed that it didn't get deleted on the 1st try. Only after few try. This might be due to I didn't set the $pdoHandle to NULL after I'm done with the query.
Then, unfortunately I couldn't insert new row on subsequent run. I even tried to change the input value and to avail I was unable to insert new row. The following are my PHP codes:
public function CreateNewCustomer($userId,$password,$name,$email)
{
$userId = filter_var($userId,FILTER_SANITIZE_STRING);
$password = filter_var($password,FILTER_SANITIZE_STRING);
$password = sha1($password);
$name = filter_var($name,FILTER_SANITIZE_STRING);
$email = filter_var($email,FILTER_SANITIZE_EMAIL);
do{
$customerId = hexdec(bin2hex(openssl_random_pseudo_bytes(4,$isStrong)));
echo $customerId;
$result = $this->connObject->exec("SELECT COUNT(id) FROM customer_tbl WHERE id=$customerId");
var_dump($result);
}while($result>0);
$statement = $this->connObject->prepare("INSERT INTO customer_tbl (id,name,email) VALUES ($customerId,:name,:email)");
$result = $statement->execute(array(':name'=>$name,':email'=>$email ));
var_dump($result);
$statement = $this->connObject->prepare("INSERT INTO login_tbl (username,password,customer_id) VALUES (:userName,PASSWORD(:password),$customerId)");
$result = $statement->execute(array(':userName'=>$userId,':password'=>$password ));
var_dump($result);
}
I used the following code to access the above method.
function Test($userName,$password,$name,$email)
{
try
{
$dbConnect = new DbConnect();
$pdoHandle = $dbConnect->Connect();
$userAccess = new UserAccess($pdoHandle);
$userAccess->CreateNewCustomer($userName,$password,$name,$email);
}
catch(PDOException $e)
{
$pdoHandle = null;
var_dump($e);
}
$pdoHandle = null;
}
Test('tester','password','TestX','test#example.com');
The var_dump of results is always false.
Is there any problem with my codes or is it something wrong with the database?
UPDATE/SOLUTION:
I just read through the PHP document on PDO::exec() and one of the user contributed notes mentioned that you can't use any SELECT statements (even thou the above only returns the count value) and any statements which might return a rows. The return value of PDO::exec() is the number of affected rows (integer), so the PDOStatement::closeCursor() can't be used to solve it. Even when I set the PDO::MYSQL_ATTR_USE_BUFFERED_QUERY=>true, it still doesn't work.
So, don't use PDO::exec() for any SELECT. I changed my code to PDO::query() instead as below,
do{
$customerId = hexdec(bin2hex(openssl_random_pseudo_bytes(4,$isStrong)));
$statement = $this->connObject->query("SELECT COUNT(id) FROM customer_tbl WHERE id=$customerId");
$statement->execute();
}while($statement->fetchColumn(0)>0);
Hope this would be helpful to anyone looking for a solution with similar problem and always remember to read the PHP document first including the user contributions.
Maybe not the answer but here are some things that you can do if you cannto see an obvious error:
If execute returns false, you can get more information about the error that happened by:
$arr = $statement->errorInfo();
print_r($arr);
or you can set different error reporting modes (e.g. throw an exception instead of the defaultsilent mode):
$dsn = 'mysql:dbname=testdb;host=127.0.0.1';
$user = 'dbuser';
$password = 'dbpass';
$dbh = new PDO($dsn, $user, $password);
$dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
This should help you to find the "real" error.
As it turned out (see comments below question), in this case the real error was:
"Cannot execute queries while other unbuffered queries are active.
Consider using PDOStatement::fetchAll(). Alternatively, if your code
is only ever going to run against mysql, you may enable query
buffering by setting the PDO::MYSQL_ATTR_USE_BUFFERED_QUERY"
In this case you have 2 options:
you can set the option to use buffered queries
$dbh = new PDO(’mysql:host=localhost;dbname=test’, ‘root’, ” ,array(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY => true))
or change your code and close an open cursor (may depend on the db driver you are using). You always should read the documentation which covers a lot of default problems.
Hope this helps.
I'm assuming the method is inside the UserAccess class and the connection you pass in is set to the local $this->connObject.
I suspect after you deleted the record, $customerId is being set to null in your interesting do-while loop with the select statement. If the id column in the DB is a non-null primary key field and you try to insert an explicit null it will fail.
Also, no need to keep setting your DB connection to null... this isn't C and connections aren't persistent (unless you explicitly declare them as such).