PHP mysqli quickguide for stored procedures, http://php.net/manual/en/mysqli.quickstart.stored-procedures.php :
if (!$mysqli->query("DROP PROCEDURE IF EXISTS p") ||
!$mysqli->query("CREATE PROCEDURE p(IN id_val INT) BEGIN INSERT INTO test(id) VALUES(id_val); END;")) {
echo "Stored procedure creation failed: (" . $mysqli->errno . ") " . $mysqli->error;
}
does this not delete the procedure then replace it? is this the correct way to do it? what is the point in deleting it? does deleting it not negate the whole point of having a procedure which you can call for the duration of a MySQL connection?
You have to drop the 'old' procedure first, otherwise the CREATE will fail with an "already exists" errors. It's the same for pretty much EVERY object in a database, e.g.
CREATE TABLE foo ... // creates the table
CREATE TABLE foo ... // will **NOT** replace the one just created
you cannot 'overwrite' a table/proc/db just by redefining it. you have to drop the original one first.
Consider the chaos that'd occur if some poor DBA at a major bank accidentally ran
CREATE DATABASE clients;
and trashed their entire client db because the DB engine replaced the original DB with this new empty one.
Related
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());
I need to check if a column exists before trying to add it if not present.
Following information found in this post: mysql ALTER TABLE if column not exists
I added the following to my zen cart php file
$db->Execute("DROP PROCEDURE IF EXISTS `gdpr_accept`;
DELIMITER //
CREATE PROCEDURE `gdpr_accept`()
BEGIN
DECLARE CONTINUE HANDLER FOR SQLEXCEPTION BEGIN END;
ALTER TABLE " . TABLE_CUSTOMERS . " ADD `gdpr_accept` TINYINT(1) NOT NULL DEFAULT '0' AFTER `COWOA_account`;
END //
DELIMITER ;
CALL `gdpr_accept`();
DROP PROCEDURE `gdpr_accept`;");
However, I get the following error logged
[05-May-2018 19:37:02 Europe/Paris] PHP Fatal error: 1064:You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'DELIMITER //
CREATE PROCEDURE gdpr_accept()
BEGIN
' at line 2 :: DROP PROCEDURE IF EXISTS gdpr_accept;
DELIMITER //
CREATE PROCEDURE gdpr_accept()
BEGIN
DECLARE CONTINUE HANDLER FOR SQLEXCEPTION BEGIN END;
ALTER TABLE customers ADD gdpr_accept TINYINT(1) NOT NULL DEFAULT '0' AFTER COWOA_account;
END //
DELIMITER ;
CALL gdpr_accept();
DROP PROCEDURE gdpr_accept; ==> (as called by) /Applications/MAMP/htdocs/gdpr/stOrm-PTO-fluSh/includes/init_includes/init_reconsent_popup_setup.php on line 72
However, when I run the same command in phpMyAdmin, after confirming that i want to "DROP PROCEDURE IF EXISTS gdpr_accept" it runs perfectly.
Note: If i attempt to split up the query, it will fail at
$db->Execute("DELIMITER //");
with this error: 1064 You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'DELIMITER //' at line 1
Is there a reason why this SQL command can't be done via php, and is there a way round it?
DELIMITER is a builtin command in the mysql client, it is not a statement that the MySQL server recognizes. You can't run a DELIMITER statement using PHP.
But you don't need to use DELIMITER. That's only to help the mysql client tell where your CREATE PROCEDURE statement ends, because a procedure usually contains semicolon characters, and otherwise it would be ambiguous which semicolon was part of the procedure body versus the end of the procedure definition.
You should run one statement at a time from PHP. Then it's not ambiguous.
$db->Execute("DROP PROCEDURE IF EXISTS `gdpr_accept`");
$db->Execute("CREATE PROCEDURE `gdpr_accept`()
BEGIN
DECLARE CONTINUE HANDLER FOR SQLEXCEPTION BEGIN END;
ALTER TABLE " . TABLE_CUSTOMERS . " ADD `gdpr_accept` TINYINT(1) NOT NULL DEFAULT '0' AFTER `COWOA_account`;
END");
$db->Execute("CALL `gdpr_accept`()");
$db->Execute("DROP PROCEDURE `gdpr_accept`;");
By the way, there's no reason you need a procedure for this task, since you just drop the procedure when you're done anyway. It would be much simpler to just run the ALTER TABLE directly:
$db->Execute("ALTER TABLE " . TABLE_CUSTOMERS .
" ADD `gdpr_accept` TINYINT(1) NOT NULL DEFAULT '0' AFTER `COWOA_account`");
I see a lot of questions on Stack Overflow from people who seem to think it's a good idea to use stored procedures in MySQL. While stored procedures are common in Oracle and Microsoft SQL Server, they're more trouble than they're worth in MySQL. I avoid using stored procedures in MySQL.
I want to run the following mysql create function statement from PHP:
DELIMITER $$
CREATE FUNCTION `myFunc`(`instring` varchar(4000)) RETURNS int(11)
NO SQL
DETERMINISTIC
SQL SECURITY INVOKER
BEGIN
DECLARE position int;
....here comes function logic
RETURN position;
END$$
DELIMITER ;
But I get this mysql error:
check the manual that corresponds to your MySQL server version for the
right syntax to use near 'DELIMITER'
What can I do? Can I execute create statement without DELIMITER keyword?
You most likely do not need the DELIMTER command. That belongs to MySQL-centric client programs.
Please try with plain old semicolons:
if (!$mysqli->query("DROP PROCEDURE IF EXISTS p") ||
!$mysqli->query("CREATE PROCEDURE p(IN id_val INT) BEGIN INSERT INTO test(id) VALUES(id_val); END;")) {
echo "Stored procedure creation failed: (" . $mysqli->errno . ") " . $mysqli->error;
}
and let PHP worry about delimiting
CAVEAT
I got the above code from http://php.net/manual/en/mysqli.quickstart.stored-procedures.php
I'm writing a function which will drop a table if it already exists. It will ask the user what they'd like to call the table, take that response and put it into a php variable. I want to make a customized drop statement then for sql so that there are no errors with sql. Here's what I have.
$table = $_POST["tablename"]; //gets table name from html page
drop_table($db, $table); //call function
function drop_table($db,$table){
$drop = "DROP TABLE IF EXISTS .$table. "; //this is the part I can't figure out. How do I add in the table name to the statement,
$q = mysqli_query($db, $drop); //since the sql statement has to be in quotes?
}
Thanks!
P.Ss This is an internal system for analyses only. No worries with dropping tables if just my colleagues and I are using it
Your problem here is a syntax error by attempting to concatenate in $table with dots. Remove those.
$drop = "DROP TABLE IF EXISTS $table ";
But the much much larger problem is that you are permitting end users to drop any table in your database, since you have not filtered the input in any way.
You need to be sure that your users are only dropping tables in the currently selected database, which means at the very least, not permitting . inside $table to prevent things like $table = 'information_schema.user'
if (strpos($table, '.') !== FALSE) {
// don't allow the action!
}
Another step to take would be to verify that the value of $table exists in information_schema.TABLES and belongs to the correct current database before executing the DROP statement.
// If this returns 1, the table exists in the correct database and can be dropped.
// note that $table is escaped here. I didn't fill in the mysqli_query() but obviously
// this is to be executed. It would be even better with a MySQLi prepared statement
"SELECT 1
FROM information_schema.TABLES
WHERE
TABLE_SCHEMA='the_allowed_database'
AND TABLE_NAME='" . mysqli_real_escape_string($db, $table) . "'"`
After passing this check, you would do well to specify a prefix to tables which are flexible in the environment and are therefore permissible to delete, so that a user could not delete every table in the active database. For example, only permit deletion of tables with the prefix usertable_.
if (strpos($table, 'usertable_') !== 0) {
// don't permit deletion
}
This is a very difficult design to secure, and I would recommend you step back and rethink the strategy here. You need to be extremely careful when allowing users to drop tables based on form input.
Do you mean:
$drop = "DROP TABLE IF EXISTS " . $table;
I really, really hope you've thought through the consequences of someone being able to drop tables from your database by entering the right name in the URL.
I'm trying to create four tables within one PDO transaction.
When the first "CREATE TABLE ..." part of the statement contains errors I successfully get an exception, an error message, and rollback. But when the first "CREATE TABLE ..." part are written correctly (as in the example below) I get no exception, commit, and only the first table created.
Here's the code:
$conn = Connection::getInstance();
$conn->dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$conn->dbh->beginTransaction();
$stmt = $conn->dbh->prepare("
CREATE TABLE IF NOT EXISTS `1st table` (valid SQL-code)
ENGINE = InnoDB
DEFAULT CHARACTER SET = utf8;
CREATE TABLE IF NOT EXISTS `2nd table` (SQL-code with an error)
ENGINE = InnoDB
DEFAULT CHARACTER SET = utf8;
CREATE TABLE IF NOT EXISTS `3rd table`...
CREATE TABLE IF NOT EXISTS `4th table`...
");
try
{
$stmt->execute();
$conn->dbh->commit();
}
catch (Exception $e)
{
$conn->dbh->rollBack();
echo $e->getMessage();
}
unset($stmt);
After some research I found the following note at php.net:
Some databases, including MySQL, automatically issue an implicit COMMIT when a database definition language (DDL) statement such as DROP TABLE or CREATE TABLE is issued within a transaction.
Is it that what causes the problem and how to solve it?
Like several commenters suggested, transactional semantics are not available for data definition language statements (DDL) in mySQL.
The PDO implementers obviously did not attempt to add some kind of client-side transactional semantics where there is no support in the DBMS.