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();
// ...
Related
I'm trying to execute a stored procedure on SQL Server using PDO. Everything runs fine, but when I try to read the output parameter (whose type is UNIQUEIDENTIFIER) the only thing I get is a NULL.
I've tried running my script on Debian 9 with PHP 7.0 and Ubuntu 18.10 with PHP 7.2 and changing the PDO type of my parameter, with no success.
$order_uid = null;
$sql = "EXEC spInsertOrder ?, ?, ?, ?...";
$stmt = $db->prepare($sql);
$stmt->bindParam(29, $order_uid, PDO::PARAM_INPUT_OUTPUT | PDO::PARAM_STR, 50);
if ($stmt->execute() === false) {
echo $stmt->errorCode();
print_r($stmt->errorInfo());
}
I expect to get the UUID that SQL Server emits, instead this error raises:
Fatal error: Uncaught PDOException: SQLSTATE[IMSSP]: An invalid type
for parameter 5 was specified. Only booleans, integers, floating point
numbers, strings, and streams may be used as parameters.
Before you read the value of your OUTPUT parameter, you need to consider the following:
If your stored procedure executes SELECT statements, you need to consume all results with PDOStatement::nextRowset, before accessing the value of your output parameter.
If your statement executes INSERT or UPDATE statements, put SET NOCOUNT ON as first line in your procedure to stop SQL Server to return the count of the affected rows as a resultset.
set your PHP variable to null.
Working example (tested with PHP 7.1.12 and PHP Driver for SQL Server (PDO) 4.3.0+9904):
T-SQL:
CREATE PROCEDURE [dbo].[sp_UID]
#id UNIQUEIDENTIFIER OUTPUT
AS BEGIN
SET NOCOUNT ON
SET #id = NEWID()
END
PHP:
<?php
# Connection info
$server = 'server\instance,port';
$database = 'database';
$uid = 'uid';
$pwd = 'pdw';
# Connection
try {
$dbh = new PDO("sqlsrv:server=$server;Database=$database", $uid, $pwd);
$dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
} catch( PDOException $e ) {
die("Error connecting to SQL Server. ".$e->getMessage());
}
# Stored procedure
try {
$sql = "{CALL sp_UID(?)}";
$uid = null;
$stmt = $dbh->prepare($sql);
$stmt->bindParam(1, $uid, PDO::PARAM_STR | PDO::PARAM_INPUT_OUTPUT, 36);
$stmt->execute();
// If your procedure returns result set, you need to fetch result and then get the value for your output parameter
/*
do {
while ($row = $stmt->fetch( PDO::FETCH_ASSOC )) {
}
} while ($stmt->nextRowset());
*/
} catch( PDOException $e ) {
die( "Error executing stored procedure: ".$e->getMessage());
}
$stmt = null;
# End
$dbh = null;
echo $uid;
?>
Output:
F689A035-C3DB-4D4E-88FB-52F5DA133FA8
Hi I have a stored procedure with a while inside which receives 2 parameters for the condition of the while. I make it to work like a for. If I executed it from SQL it works perfectly, but If I call it from PHP, the while doesn't respect the condition and suddenly ends.
The while insert an iterator number in each row like
1,a,b
2,a,b
3,a,b
etc. If I set the iterations from 1 to 1000 the loop stop without a reason in different interaction in every execution of the SP.
I have a try/catch and I can't detect the error.
I'm sure all the parameters are right.
Can someone help me to detect why the stored procedure can't end correctly?
I'm using PDO to access to SQL Server.
Store Procedure:
ALTER procedure [dbo].[sp_AddSealsFromPurchase]
#initialFolio int,
#finalFolio int,
#userDelivery_ID varchar(50),
#userReceives_ID varchar(50),
#color nvarchar(10),
#supplier_ID int,
#requisition_ID int,
#office_ID int,
#reference nvarchar(50)
as
begin
declare #now datetime;
set #now = getdate();
while #initialFolio<=#finalFolio Begin
begin try
INSERT into Seals (SealNo,SealType,Color,Supplier_ID,[Source],Office_ID,Requisition_ID,Reference)
Values (#initialFolio,1,#color,#supplier_ID,1,#office_ID,#requisition_ID,#reference)
INSERT INTO StatusSeal(Seal_ID,Status_ID,UserDelivery_ID,UserReceives_ID,StatusSeal_Date,UserDeliveryType,UserReceivesType)
Values(##Identity,1,#userDelivery_ID,#userReceives_ID,#now,1,1)
set #initialFolio = #initialFolio +1;
end try
begin catch
set #initialFolio = #initialFolio +1;
end catch
end
end
PHP:
function AddSealsFromPurchase($Data){
session_start();
$cnx=$this->connectSqlSrv();
$sth=$cnx->prepare("Exec sp_AddSealsFromPurchase ?,?,?,?,?,?,?,?,?");
$sth->bindParam(1, $Data["InitialFolio"]);
$sth->bindParam(2, $Data["FinalFolio"]);
$sth->bindParam(3, $Data["UserDelivery"]);
$sth->bindParam(4, $_SESSION['SESSINFOSEAL']['User_Usr']);
$sth->bindParam(5, $Data["Color"]);
$sth->bindParam(6, $Data["Supplier_ID"]);
$sth->bindParam(7, $Data["Requisition_ID"]);
$sth->bindParam(8, $_SESSION['SESSINFOSEAL']['Ofice_ID']);
$sth->bindParam(9, $Data["Reference"]);
session_write_close();
$retval=$this->ExecuteNoQuery($sth);
return json_encode($retval);
}
public function connectSqlSrv(){
try{
$dbCnx = new PDO("sqlsrv:Server=$this->server;Database=$this->db", $this->usr, $this->psw);
$dbCnx->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
return $dbCnx;
}
catch(PDOException $e){
echo $e;
die();
return null;
}
}
public function ExecuteNoQuery($sth)
{
$r=array('data'=>false,
'error'=>false,
'r'=>'');
try {
$sth->execute();
if ($r['r'] = $sth->rowCount()) {
$r['data'] = true;
}
} catch (PDOException $e) {
$r['error']=true;
$r['r'] = $e->getMessage();
}
return $r;
}
try {
$query = 'UPDATE keywords SET value = :keyvalue WHERE keyword = :keyname AND document_id = :docId';
$pdo = _openConnection();
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$pdo->beginTransaction();
$pdoStatement = $pdo->prepare($query);
foreach ($keywords as $keyname => $keyval) {
$pdoStatement->bindParam(':docId', $id, PDO::PARAM_STR);
$pdoStatement->bindParam(':keyname', $keyname, PDO::PARAM_STR);
$pdoStatement->bindParam(':keyvalue', $keyval, PDO::PARAM_STR);
$pdoStatement->execute();
}
$res = $pdo->commit();
var_dump('retornando true', $res);
return true;
} catch (PDOException $e) {
$pdo->rollBack();
echo $e->getMessage();
return false;
}
The sentence updates a given row identified by KEYWORDNAME and DOCUMENT_ID.
I am sending a wrong keyword name (non-existent) but existent document id.
Shouldn't it throw an exception for record not found and rollback the operation?
It is always succeeding and returning true (also I see the var_dump)
PS: this is the last portion of the code.
As far as the database (and thus PDO) is concerned - this is not an error. You performed an update statement, and it successfully updated 0 rows.
If you want to handle this as an error, you'd have to do it manually:
$res = $pdo->commit();
if ($pdo->rowCount() == 0) {
# some exception treatment
}
No, you are simply updating 0 rows when the WHERE condition is not matched. Updating, selecting, etc. 0 rows is not an error, these are normal database operations.
Check the rowCount() to see how many rows are updated and handle that accordingly.
A query which doesn't match any records is NOT an error. It's just an empty result set, which is a perfectly valid result.
The only time you'd get an exception from the query is if there was an actual problem with the query itself, the connection to the db, etc... e.g. A syntax error in the DB, connection failure, permission denied on whatever table(s) you're accessing, etc...
What is a sure way to tell if an update query succeeded when using php pdo and mysql?
In my app, I update totals of items that are submitted but a user, and the table looks like this:
items
userId | itemsAdded | itemsChecked | itemsUnChecked | itemsTotal
1 | 5 | 2 | 3 | 5
So when I do update items set itemTotals = itemsChecked+itemUnChecked the itemsTotal column remains the same unless the itemsAdded changes and the itemsUnChecked increments (2 + 3 equals 5, 1 + 4 is also 5).
I used to use rowCount() to check if a query succeeded, but in this case, since the itemsTotal column stays the same, there's no way of telling if the sql succeeded or not.
$query = $conn->prepare($sql);
$query->execute(array(
":itemCount" => $itemCount
":itemId" => $itemId
));
$queryCount = $query->rowCount();
if($queryCount == 1) {
echo 'Updated succeeded';
} else {
echo 'Updated failed!';
}
I could also use:
$query = $conn->prepare($sql);
$result = $query->execute(array(
":itemCount" => $itemCount
":itemId" => $itemId
));
if($result) {
echo 'Updated succeeded';
} else {
echo 'Updated failed!';
}
But does that return true or false based on if the query succeed or based on the number of rows it updated?
I only need to check if the query succeeded or not. No need to tell the number of rows that were updated.
The execute() method will either throw an exception or return FALSE (depending on the error mode you have set for the database connection) when the execution of a SQL statement fails.
If we set the error mode to throw an exception, prior to executing a statement, (usually immediately after establishing a database connection), like this
$conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
Then we can use a try/catch block to handle an exception thrown by a SQL statement. Something like this:
try {
$query->execute(...);
echo 'Update succeeded';
} catch(PDOException $e) {
echo 'Update failed!';
echo 'Error: ' . $e->getMessage();
}
Information about error mode settings and handling is available in the documentation here: http://php.net/manual/en/pdo.error-handling.php
Or, if PDO isn't set to throw an exception, we can use a simple if test. (The execute() method will return FALSE if the the statement fails.)
if ($query->execute(...)) {
echo 'Update succeeded';
} else {
echo 'Update failed!';
}
For more precise control with the different types of failure, we can use the errorCode() method to retrieve the SQLSTATE associated with the last operation on the statement handle, and we can perform conditional tests on the returned value. http://php.net/manual/en/pdostatement.errorcode.php
Even if no rows are affected that does not mean the update failed just, like you said, nothing was changed. It would only fail if there was an exception thrown. To handle this you would need to implement a try/catch block.
http://php.net/manual/en/language.exceptions.php
try{
$query = $conn->prepare($sql);
$result = $query->execute(array(
":itemCount" => $itemCount
":itemId" => $itemId
));
echo 'Updated succeeded';
} catch(Exception $e) {
echo 'Updated failed!';
}
Sorry for this beginners question and i'm not a PHP developer, but now i'm trying to learn it.
i want to add record in MySQL data base and i'm using transactions lock.
my code is as below.
$SqlQuery="INSERT INTO tab_photo VALUES('$PhotoID','$ProjectId','$Day','$barCode','$photoName','$PhotoXml')";
$waiting = true;
while($waiting) {
try {
// save border data
$stmt = $conn->prepare($SqlQuery);
$conn->beginTransaction();
$stmt->execute();
sleep(1);
$x=$conn->commit();
echo "x value-".$x;
echo "Success";
$waiting = false;
}
catch (PDOException $e){
echo "Failled :".$PhotoID."-".$PhotoID;
if(stripos($e->getMessage(), 'DATABASE IS LOCKED') !== false) {
// This should be specific to SQLite, sleep for 0.25 seconds
// and try again. We do have to commit the open transaction first though
$conn->commit();
usleep(250000);
} else {
$conn->rollBack();
throw $e;
}
}
}
in here as output it gives,
x value-1 Success
but actually this record doesn't add to the database.
My Questions:
Even the commit is successful(output 1) how does it not added to the database?
how can i check whether record is added to database? ( Is there any way to find it without write select statement?
As I understand, you expect that PDOException will be thrown when statement is failed to execute. But as I can see, exception is not thrown by default in such cases.
See how you can change that here
Suppose in your case you should have a code like this:
$conn = new PDO($connection_string);
$conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); // this will force PDO to throw exception when SQL statement fails instead of simply setting an error.
Suppose this will work fine for you.
Please note that you should not use
$SqlQuery="INSERT INTO tab_photo VALUES('$PhotoID','$ProjectId','$Day','$barCode','$photoName','$PhotoXml')";
Instead of that, you should use parameters binding:
$SqlQuery="INSERT INTO tab_photo VALUES(:PhotoID,:ProjectId,:Day,:barCode,:photoName,:PhotoXml)";
$stmt = $conn->prepare($SqlQuery);
$conn->beginTransaction();
$stmt->execute(array(':PhotoID' => $PhotoID, ':ProjectId' => $ProjectId, ....));
sleep(1);
See this for more details.