php's mysqli::commit has a $name param - what does it do - php

https://www.php.net/manual/en/mysqli.commit.php
public mysqli::commit(int $flags = 0, ?string $name = null): bool
Parameters
flags: A bitmask of MYSQLI_TRANS_COR_* constants.
name: If provided then COMMIT/*name*/ is executed.
My question: what is COMMIT/*name*/ ??
I can't find any Mysql documentation for this
https://dev.mysql.com/doc/refman/8.0/en/commit.html
https://dev.mysql.com/doc/refman/8.0/en/innodb-autocommit-commit-rollback.html
nor any usage in the wild

So I turned on query logging
and this is what was logged
passing name to commit():
Query START TRANSACTION
Query INSERT INTO `bob` (`t`) VALUES ("test 1")
Query SAVEPOINT `foo`
Query INSERT INTO `bob` (`t`) VALUES ("test 2")
Query COMMIT /*foo*/
both queries were logged
passing name to rollback:
Query START TRANSACTION
Query INSERT INTO `bob` (`t`) VALUES ("test 1")
Query SAVEPOINT `foo`
Query INSERT INTO `bob` (`t`) VALUES ("test 2")
Query ROLLBACK /*foo*/
Query COMMIT /*comment*/
neither insert happened... entire transaction was rolled back
Nutshell:
mysqli::commit and mysqli::rollback have $name parameters that don't do anything beside add a comment to the query.
To actually rollback to a savepoint, you'll have to execute a query (ROLLBACK TO `name`).
mysqli extension provides a savepoint($name) method, but no rollback_to_savepoint!

When you start a transaction, you have the option of creating a savepoint name for the transaction. You can then use that name to commit. From the MySQL docs:
The SAVEPOINT statement sets a named transaction savepoint with a name of identifier. If the current transaction has a savepoint with the same name, the old savepoint is deleted and a new one is set.

Related

PHP mysqli is NOT trapping some errors when calling stored procedure

We're building a production PHP-MySQL application, and want MySQL stored procedures to be the central bullet-proof gateway to the database. Duplicate keys, table not found, server instance going down, etc all and any kind of error needs to be trapped and conveyed to the calling PHP web-based UI, and transaction rolled back in the stored proc upon such errors.
I am using PHP mysqli and calling a stored procedure as follows:
$stmt = mysqli_prepare($db, "call my_stored_proc(?, ?, ?, #ptid)");
if ($stmt && mysqli_stmt_bind_param($stmt, "sss", 'p1', 'p2', 'p3') &&
mysqli_stmt_execute($stmt) && mysqli_stmt_close($stmt)) {
echo "All fine!"
} else {
echo mysqli_error($db);
db_disconnect($db);
exit;
}
The stored procedure does some basic validation and signals a user-defined condition if the validation fails. And sure enough, my PHP code is able to catch and see those non-database (eg. formatting) validation errors arising from the stored procedure. After the non-database validations pass, the stored procedure goes on to do a database-related validation and if those pass it inserts a row in a table, and passes the ID in the last OUT parameter.
My problem is that if this insert fails (say, bcoz duplicate key error, or table not found error), my PHP code is simply not catching the error! It prints "All fine"!
Why is that? What am I missing?
I want my invocation of the stored proc to be bullet-proof, and all errors raised by the stored proc should be trappable in PHP.
FYI: If I call the stored proc from a mysql client (like MySQL Workbench or the mysql client on Linux), the errors are correctly reported.
LATER EDITS:
FYI, the stored procedure code is simply:
delimiter $$
drop procedure if exists my_stored_proc $$
create procedure my_stored_proc
(
in p_name VARCHAR(31),
in p_notes VARCHAR(510),
in p_created_by VARCHAR(31),
out p_pt_id INT
)
begin
declare custom_exception condition for sqlstate '45000';
declare l_retval boolean;
declare l_right_now datetime default now();
select p_name regexp '^[[:space:]]*$' into l_retval;
if l_retval then
signal custom_exception set message_text = 'NAME cannot be blank.';
end if;
select p_name regexp '[^0-9_]' into l_retval;
if l_retval then
signal custom_exception set message_text = 'Invalid NAME.';
end if;
call validate_user_in_db(p_created_by, true, l_retval);
if not l_retval then
signal custom_exception set message_text = 'Invalid CREATED_BY user.';
end if;
insert into some_table
(
NAME, NOTES,
CREATED_BY, CREATED_ON
) values
(
p_name, p_notes,
p_created_by, l_right_now
);
set p_pt_id = last_insert_id();
end $$
delimiter ;
EVEN LATER UPDATE:
The weird thing is, if I comment out the call to validate_user_in_db in the above stored proc, things work fine and errors are correctly trapped (eg. duplicate key, etc) in PHP.
FYI: validate_user_in_db does the following:
create procedure validate_user_in_db (in p_user VARCHAR(127),
in p_active_only boolean, out p_retval boolean)
begin
set p_retval = false;
if p_active_only then
select sql_calc_found_rows 'x'
from SOME_USERS_TABLE
where username = p_user
and active = true
limit 1;
else
select sql_calc_found_rows 'x'
from SOME_USERS_TABLE
where username = p_user
limit 1;
end if;
set #l_num_rows = found_rows() ;
if #l_num_rows = 1 then
set p_retval = true;
end if;
end $$
Sorry for the long post. But I thought I'd give the full picture.
What am I missing? Why is my PHP code not getting back errors if the call to validate_user_in_db is enabled? Is validate_user_in_db changing some state permanently? Is the sql_calc_found_rows keyword messing things up?
FYI: This is PHP 7.3 and MySQL 5.6
Aah, after breaking my head against it for long and a lot googling, I found the problem! It is closely related to How to call a stored procedure within another stored procedure (PHP and mysqli)
Basically I had a case of PHP calling SP1, which in turn called SP2, and everything working fine in a mysql client but breaking when called by PHP!
It turns out the problem is that SP2 was SELECTing a result set (ie. SELECT without an INTO clause).
I re-wrote SP2 to necessarily do a SELECT INTO and that fixed the problem.
I think the ability to SELECT a result set without doing a SELECT INTO is a crappy feature in MySQL. Come to think of it, quite a few things crappy about MySQL (stored functions, exception propagation up the stack, poor compilation and syntax error pinpointing in stored procedures, bad concept of transaction boundaries, etc).
I think SELECTing a result set in a stored routine should be avoided at all costs.
PHP reports errors from Stored Procedures. The problem here is that calling Stored Procedures through mysqli is not an easy task. It's best to avoid Stored Procedures and mysqli if you can.
All errors can be reported by mysqli if you enable mysqli error reporting. Simply add this line before opening a new connection:
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
Note: until recently mysqli had plenty of bugs that either crashed PHP or didn't report errors properly. Keep your PHP up-to-date to avoid such problems.
Your main problem is that your stored procedure is producing results, which you are not reading in PHP. Results from MySQL are fetched sequentially, even if the queries are executed asynchronously. When an error happens after SELECT in your stored procedure then PHP will not throw the error immediately. You must fetch each result, even if you don't need them in PHP.
You can utilize a simple do-while loop to fetch all results.
$stmt = $mysqli->prepare('CALL SP1()');
echo $stmt->execute();
do {
$stmt->get_result();
} while ($y = $stmt->next_result());

Transaction: commit() vs rollBack()

I have some queries at one script and I want to execute either all of them or none of them ..! I've searched about that and I figured out I have to use transaction.
Actually I want to use PDO::beginTransaction. Now there is two approaches.
rollback() function
commit() function
So what's the difference between them? Both of them seems identical to me, So when should I use which one?
<?php
$dbh->beginTransaction();
$sth1 = $dbh->exec("DROP TABLE fruit");
$sth2 = $dbh->exec("UPDATE dessert SET name = 'hamburger'");
$sth3 = $dbh->exec("INSERT INTO names(id, name) VALUES (NULL, 'peter')");
// which one?
$dbh->commit();
// or
$dbh->rollBack();
// ??
/* Database connection is now back in autocommit mode */
?>
Both of them seems identical to me
That's wrong. Transaction by definition is Atomic in nature means either it will happen and succeed executing all commands in the group or none at all. If it's successful and you want to persist the change then COMMIT else if any of the statement in the group fails then ROLLBACK to get back to pristine state.
So in your case, you would want to have all the below statement execute successfully and if that then COMMIT to persist the change but if any of the statement fails for any so called reason then it may end up giving a undesired result which you don't want to persist and so ROLLBACK and get back to previous consistent state.
$sth1 = $dbh->exec("DROP TABLE fruit");
$sth2 = $dbh->exec("UPDATE dessert SET name = 'hamburger'");
$sth3 = $dbh->exec("INSERT INTO names(id, name) VALUES (NULL, 'peter')");
Read about Transaction and also see this another post PHP + MySQL transactions examples
You use commit to perform the transaction, and rollback is the opposite, you use rollback when you want to keep all unchanged (for example if you have detected some error during some step of the transaction).

return true if mysql update statement excute and false when no row was updated

i have a mysql update statement but it return true when rows are updated and when no row is updated.
i want to knw if row was altered or not.
here is my query:
$q=mysql_query("UPDATE results set marks='$mark' and total='$total'
where result_id='$rid'");
if($q){
echo 'rows updated';
}
else{
echo 'No row was updated';
}
it always print the "row updated " message, whether no record updated or not.
any help plz?
You probably want mysql_affected_rows() instead.
From the documentation:
Get the number of affected rows by the last INSERT, UPDATE, REPLACE or DELETE query associated with link_identifier.
Note thatmysql_* functions are deprecated -- I recommend you switch to MySQLi or PDO instead (and start using parameterized queries, to be safe from SQL injection)
First, the proper syntax is:
UPDATE results
set marks='$mark', total='$total'
where result_id='$rid';
You might want to phrase it as:
UPDATE results
set marks='$mark', total='$total'
where result_id='$rid' and marks <> '$mark' and total <> '$total';
I think different versions of MySQL differ on whether or not they report a selected row changing, even when the new versions of the values are the same as the old values.

PHP: SQL Prepared statement transaction not working correctly. It's inserting 1 SQL statement, not both

I'm finding that the PDO Transaction is only commiting 1 of my 2 SQL statement. For some reason, my PHP script is not inserting into my MySQL database 'homes' table BUT it does insert into the 'invoices' table - even though I'm using a PHP PDO database transaction.
Code below:
$conn_str = DB . ':host=' . DB_HOST . ';dbname=' . DB_NAME;
$dbh = new PDO($conn_str, DB_USERNAME, DB_PASSWORD);
/* Begin a transaction, turning off autocommit */
$dbh->beginTransaction();
$sql_create_home_listing = 'INSERT INTO homes ( customer_id,
account_type_id,
address,
city,
state,
zip,
display_status
) VALUES (?,?,?,?,?,?,true)';
$stmt = $dbh->prepare($sql_create_home_listing);
$stmt->bindParam(1, $customer_id);
$stmt->bindParam(2, $account_type_id);
$stmt->bindParam(3, $_SESSION['street']);
$stmt->bindParam(4, $_SESSION['city']);
$stmt->bindParam(5, $_SESSION['state']);
$stmt->bindParam(6, $_SESSION['zip']);
$stmt->execute();
$home_id = $dbh->lastInsertId();
// another SQL statement
$sql_create_invoice = "INSERT INTO invoices (customer_id, account_type_id, price, cc_authorized, home_id) VALUES (?,?,?,?,?)";
$cc_authorized = false;
$anotherStmt = $dbh->prepare($sql_create_invoice);
$anotherStmt->bindParam(1, $customer_id);
$anotherStmt->bindParam(2, $account_type_id);
$anotherStmt->bindParam(3, $account_plan_price);
$anotherStmt->bindParam(4, $cc_authorized);
$anotherStmt->bindParam(5, $home_id);
$anotherStmt->execute();
/* Commit the changes */
$dbh->commit();
How is it possible that only the 'invoices' table is getting the insert and not both the 'invoices' table AND the 'homes' table?
Note: no errors are reported by PHP.
Firstly, check whether you really have any errors in PHP - it's notoriously crap at telling you. There is an option you can set on PDO objects to throw an exception on database error - I recommend you set it.
It sounds to me like it is inserting the row, but you're in a transaction which is never committed so it gets rolled back, and the row is never visible (Your isolation mode is READ_COMMITTED or higher).
In that case you need to re-examine how your application uses transactions and try to see if you can get it properly consistent. Using transactions is nontrivial; it needs either a lot of code to get things right, or some well thought out wrapper code or something. If you don't understand any of that, leave autocommit on.
Check that your tables are transactional (InnoDB vs MyISAM as an example..).
Might want to do a try catch, so that if there is an error you can rollback. This may give you some insight.
Is true a valid value for display_status ?
MySQL does not have a bool type, nor does it have true as a predefined function or constant. So my guess is that it's a syntax error.
Get some decent error handling. Set the option which throws when there's a SQL error (See PDO docs!)
I found the problem. On my homes table I had a field deemed as 'unique' but it was not and preventing the insert from happening

Cant the prepared statement be used throught the transaction, from php?

I work on a LAPP environment (linux apache postgresql php), and i'm just triyn to find out how to use the prepared statement within the transaction (if is possible).
I hope code will explain better then words:
Example 1, simple transaction:
BEGIN;
INSERT INTO requests (user_id, description, date) VALUES ('4', 'This dont worth anything', NOW());
UPDATE users SET num_requests = (num_requests + 1) WHERE id = '4';
--something gone wrong, cancel the transaction
ROLLBACK;
UPDATE users SET last_activity = NOW() WHERE id = '4'
COMMIT;
In the example above, if i undestood right the transaction, the only effect in the database will be the last_activity's update... ye?
If i try to use that transaction in php (both with PDO or pg_ methods) the code should look like that (example 2):
/* skip the connection */
pg_query($pgConnection, "BEGIN");
pg_query($pgConnection, "INSERT INTO requests (user_id, description, date) VALUES ('$id_user', 'This dont worth anything', NOW())");
pg_query($pgConnection, "UPDATE users SET num_requests = (num_requests + 1) WHERE id = '$id_user'");
//something gone wrong, cancel the transaction
pg_query($pgConnection, "ROLLBACK");
pg_query($pgConnection, "UPDATE users SET last_activity = NOW() WHERE id = '$id_user'");
pg_query($pgConnection, "COMMIT");
And that works fine. Maybe ugly to see, but seem to work (suggestion are always welcome)
Anyway, my problem come when i try to envolve the example 2 with the prepared statements (i know that in the example 2 the use of prepared statements is not very usefull)
Example 3:
/* skip the connection */
pg_prepare($pgConnection, 'insert_try', "INSERT INTO requests (user_id, description, date) VALUES ('$1', '$2', $3)");
pg_query($pgConnection, "BEGIN");
pg_execute($pgConnection, 'insert_try', array($user_id, 'This dont worth anything', date("Y-m-d")));
/* and so on ...*/
Well, the example 3 simply dont work, the prepared statement will be effective if the transaction due in rollback.
So, the prepared statements can't be used in the transaction, or am i taking the wrong way?
EDIT:
After some try with PDO, i'm arrived at this point:
<?php
$dbh = new PDO('pgsql:host=127.0.0.1;dbname=test', 'myuser', 'xxxxxx');
$rollback = false;
$dbh->beginTransaction();
//create the prepared statements
$insert_order = $dbh->prepare('INSERT INTO h_orders (id, id_customer, date, code) VALUES (?, ?, ?, ?)');
$insert_items = $dbh->prepare('INSERT INTO h_items (id, id_order, descr, price) VALUES (?, ?, ?, ?)');
$delete_order = $dbh->prepare('DELETE FROM p_orders WHERE id = ?');
//move the orders from p_orders to h_orders (history)
$qeOrders = $dbh->query("SELECT id, id_customer, date, code FROM p_orders LIMIT 1");
while($rayOrder = $qeOrders->fetch(PDO::FETCH_ASSOC)){
//h_orders already contain a row with id 293
//lets make the query fail
$insert_order->execute(array('293', $rayOrder['id_customer'], $rayOrder['date'], $rayOrder['code'])) OR var_dump($dbh->errorInfo());
//this is the real execute
//$insert_order->execute(array($rayOrder['id'], $rayOrder['id_customer'], $rayOrder['date'], $rayOrder['code'])) OR die(damnIt('insert_order'));
//for each order, i move the items too
$qeItems = $dbh->query("SELECT id, id_order, descr, price FROM p_items WHERE id_order = '" . $rayOrder['id'] . "'") OR var_dump($dbh->errorInfo());
while($rayItem = $qeItems->fetch(PDO::FETCH_ASSOC)){
$insert_items->execute(array($rayItem['id'], $rayItem['id_order'], $rayItem['descr'], $rayItem['price'])) OR var_dump($dbh->errorInfo());
}
//if everything is ok, delete the order from p_orders
$delete_order->execute(array($rayOrder['id'])) OR var_dump($dbh->errorInfo());
}
//in here i'll use a bool var to see if anythings gone wrong and i need to rollback,
//or all good and commit
$dbh->rollBack();
//$dbh->commit();
?>
The code above fails with this output:
array(3) { [0]=> string(5) "00000" [1]=> int(7) [2]=> string(62) "ERROR: duplicate key violates unique constraint "id_h_orders"" }
array(3) { [0]=> string(5) "25P02" [1]=> int(7) [2]=> string(87) "ERROR: current transaction is aborted, commands ignored until end of transaction block" }
Fatal error: Call to a member function fetch() on a non-object in /srv/www/test-db/test-db-pgsql-08.php on line 23
So, seem like when the first execute fail (the one with id 293) the transaction is automatically aborted... does the PDO auto-rollback, or something else?
My goal is to complete the first big while loop, and at the end, using a bool var as flag, decide if to rollback or commit the transaction.
With PostgreSQL, if any statement produces a server error during a transaction, that transaction is marked as aborted. That doesn't mean it's actually rolled back yet- just that you can hardly do anything except roll it back. I assume PDO doesn't automatically issue a rollback, it waits for you to call the "rollback" method.
To achieve what I think you want, you can use a savepoint. Rather than rolling back the entire transaction, you can just rollback to the savepoint, and continue the transaction. I'll give an example of using this from psql:
srh#srh#[local] =# begin;
BEGIN
srh#srh#[local] *=# insert into t values(9,6,1,true);
INSERT 0 1
srh#srh#[local] *=# savepoint xyzzy;
SAVEPOINT
srh#srh#[local] *=# insert into t values(9,6,2,true);
ERROR: duplicate key value violates unique constraint "t_pkey"
srh#srh#[local] !=# insert into t values(10,6,2,true);
ERROR: current transaction is aborted, commands ignored until end of transaction block
srh#srh#[local] !=# rollback to savepoint xyzzy;
ROLLBACK
srh#srh#[local] *=# insert into t values(10,6,2,true);
INSERT 0 1
srh#srh#[local] *=# commit;
COMMIT
srh#srh#[local] =#
So in this example, the first column of t is the primary key. I tried to insert two rows into t with an id of 9, and the got a uniqueness constraint. I can't just redo the insert with the right values because now any statement will get the "current transaction is aborted..." error. But I can do "rollback to savepoint", which brings me back to the state I was at when I did "savepoint" ("xyzzy" is the name of the savepoint). Then I can issue the correct insert command and finally commit the transaction (which commits both inserts).
So in your case, I suspect what you need to do is create a savepoint before your UPDATE statement: if it gives an error, do a "rollback to savepoint" and set your flag. You'll need to generate unique names for the savepoints: using a counter, for example.
I'm not entirely sure I understand why you're doing all this. Surely you want to stop processing as soon as you know you're going to roll back the transaction? Or is there some other processing going on in the loop that has to happen as well?
You should be using
pdo_obj->beginTransaction()
pdo_obj->commit()
pdo_obj->prepare()
Also you have a random commit at the end of your first example.
begin
// do all your stuff
// check for errors through interface
commit OR not
pg_query($pgConnection, "ROLLBACK"); // end of tx(1)
// start new transaction after last rollback = tx(2)
pg_query($pgConnection, "UPDATE users SET last_activity = NOW() WHERE id = '$id_user'");
// commit tx(2) or don't here
// this isn't needed pg_query($pgConnection, "COMMIT");
If you didn't commit, and need to manually adjust stuff, use another transaction. Preparing your query (if I recall) is part of a transaction, because it can fail. You can't really just manually take an SQL statement and turn it into queries. The PDO interface has abstractions for a reason. :)
http://uk3.php.net/pdo <-- Solid examples of PHP/Postgre using PDO
good luck

Categories