In my MySQL database on my PC, I have a table that is on another MySQL server in reality, seen from my local MySQL as a federated table. I access that table in a stored procedure.
Since "another MySQL server" can always just be unreachable, I want to put a continue handler into my stored procedures to detect it when the connection is gone, but I cannot figure out, how to do that, or at least not all the time.
This is an example procedure:
DROP PROCEDURE IF EXISTS `ABCDE_SELECT`;
DELIMITER $$
CREATE PROCEDURE `ABCDE_SELECT` ()
BEGIN
DECLARE CONTINUE HANDLER FOR SQLEXCEPTION, 10000, SQLSTATE 'HY000', SQLSTATE '10000', 1296, 2003, 10060, SQLSTATE '10060' SELECT "Error!" AS RESULT;
SELECT ID, Message_Timestamp, Message FROM test_table_federated;
END$$
DELIMITER ;
When I call it with the other server present, it gives out the table content as expected.
When I call it with the other server gone, it gives out "Error!" as expected.
When I now replace the SELECT statement with an INSERT[1] statement...
...and call the procedure with the other server present, it inserts and doesn't give out a thing as expected.
...and call the procedure with the other server gone, it raises an error:
Error Code: 1296. Got error 10000 'Error on remote system: 2003: Can't connect to MySQL server on '...' (10060)' from FEDERATED
Why is the CONTINUE HANDLER not working in that case?
[1]: e.g. INSERT INTO test_table_federated (ID, Message_Timestamp, Message) VALUES (NULL, NOW(6), "Hello!");
Related
I have a stored procedure that executes stored SQL.
However, the error-handler kicks-in and exits if the user attempts to execute
drop temporary table if exists t_person;
and 't_person' doesn't exist. I'm perfectly happy to generate an error when 'if exists' is not given, but how do I avoid an error for the earlier case (the error-code is unchanged)?
Here's my error handler:
DECLARE EXIT HANDLER FOR SQLEXCEPTION
BEGIN
set #sql = 'select \'Invalid SQL or bad parameter resulting in invalid SQL\' as `Error`';
prepare stmt from #sql;
execute stmt;
END;
You could use a CONTINUE handler rather an an EXIT handler that catches MySQL error 1051 "Unknown table"...
DECLARE CONTINUE HANDLER FOR 1051 BEGIN END;
-or-
DECLARE CONTINUE HANDLER FOR SQLSTATE '42S02' BEGIN END;
EDIT
To catch a MySQL error in an exception handler, you need to specify the MySQL error number or the corresponding SQLSTATE to be caught. (You could specify a named condition, but that named condition has to resolve to a MySQL error number or SQLSTATE).
A syntax error would throw MySQL error 1064.
If a table foo exists, and you issue a
CREATE TEMPORARY TABLE IF NOT EXISTS `foo` (id INT);
That would throw MySQL error 1050.
To catch that error, declare another handler for that. Assuming you want to "swallow" the exception and continue processing...
DECLARE CONTINUE HANDLER FOR 1050 BEGIN END;
Reference: https://dev.mysql.com/doc/refman/5.5/en/error-messages-server.html
The like p_person in the drop temporary table statement looks wrong to me; at least, I'm not familiar with using the LIKE keyword in a DROP TABLE statement.
I found two very strange problems in MySQL database.
My MySQL database version is 5.6.
Problem 1:
I have simple store procedure for update column value:
Store procedure is as below:
drop PROCEDURE if exists mysql_TestProc;
CREATE PROCEDURE mysql_TestProc(Finaltable VARCHAR(1024),ColTOProcess VARCHAR(1024)
,strFind TEXT,strReplace TEXT)
Label1:BEGIN
DECLARE code VARCHAR(1024) DEFAULT '00000' ;
-- Exception Handler
DECLARE CONTINUE HANDLER FOR SQLEXCEPTION
BEGIN
GET DIAGNOSTICS CONDITION 1
code = RETURNED_SQLSTATE;
END;
-- generate dynamic Query
SET #s:=CONCAT('UPDATE ',FinalTable,' SET
',ColTOProcess,'=REPLACE(',ColTOProcess,',\'',strFind,'\',\'',strReplace ,'\');');
PREPARE stmt from #s;
EXECUTE stmt;
-- If any exeption during query execution then Exception Handler will
-- assign error code to "code" variable
-- else "code" variable will have default value.
IF code != '00000' THEN
-- Error found..
select code ;
LEAVE Label1;
END IF;
END;
call mysql_TestProc("AnyTableName","ColumnName","Find Value","Replace Value").
If you call above store procedure with appropriate parameters it will
successfully update value.
But in my database It successfully update value with error code "42S22".
I changed machine then everything works fine.
So This strange behavior is only with my machine and my database("_temp").
Problem 2:
I have simple procedure as below:
DROP PROCEDURE IF EXISTS mysql_PrepareLogTable;
CREATE PROCEDURE mysql_PrepareLogTable(LogTable VARCHAR(1024),code TEXT,comment TEXT,category VARCHAR(1024),timestamp DATETIME,duration VARCHAR(100),rows INT,msg TEXT)
BEGIN
SET code=CONCAT(comment,' \n ',code);
SET #tempprepare=CONCAT('INSERT INTO ',LogTable,' VALUES ("',code,'","',category,'","',timestamp,'","',duration,'",',rows,',','"',msg,'")');
PREPARE stmt from #tempprepare;
EXECUTE stmt;
END;
I can compile above store procedure script all my mysql databases except one database("test2").
Only in database "test2", I am not able to compile above store procedure script.
Even I drop the store procedure and then try to execute the script but still It failed to compile script in database "test2".
I am using Toad 6 and mysql workbench 6.0.
So, anyone has any idea about these two problems.
Thank You,
Ronak
Problem
I have a stored procedure:
CREATE PROCEDURE `ProblematicProcedure` (IN dbName varchar(50), IN tableId INT)
MODIFIES SQL DATA
BEGIN
DROP VIEW IF EXISTS v1;
DROP VIEW IF EXISTS v2;
CALL ExecuteSql(CONCAT("CREATE VIEW v1 AS SELECT * FROM ",dbName,".my_table;"));
CALL ExecuteSql(CONCAT("CREATE VIEW v2 AS SELECT * FROM ",dbName,".table_",tableId,";"));
...
When called directly from command line or a client like Navicat or HeidiSql, it works well:
CALL ProblematicProcedure("my_schema",1);
But if called from a custom Apache module using the exactly same line above, it crashes on first ExecuteSql call. I have to make it work when called from the Apache module and couldn't find a reason to crash.
ExecuteSql definition
CREATE PROCEDURE ExecuteSql (IN sql_str TEXT)
BEGIN
SET #query = sql_str;
PREPARE stm FROM #query;
EXECUTE stm;
DEALLOCATE PREPARE stm;
END
What I tried?
Swapped two ExecuteSql calls.
Inlined ExecuteSql calls.
Removed ExecuteSql's and used direct SQL statements with hardcoded dbName and tableId values.
Created procedure without MODIFIES SQL DATA.
Granted CREATE VIEW privilege: GRANT ALL ON *.* TO 'myuser'#'%';
Note: I added simple insert statements between the lines to find where it is crashing. So, I am sure it crashes always on first ExecuteSql call.
Question
What can be reason to this crash?
Update: Finally, I managed to find error code:
ERROR 1312: Procedure can't return a result set in the given context
Solution
Use CLIENT_MULTI_STATEMENTS flag when connecting:
mysql_real_connect(conn, host, user, pass, db, 0, NULL, CLIENT_MULTI_STATEMENTS);
Why this is so?
Calling a stored procedure means executing multiple statements. So, I need to specify that I can execute multiple statements at once. Hence I am using MySql C API functions at client-side (in my Apache module), I need to specify CLIENT_MULTI_STATEMENTS flag when connecting:
mysql_real_connect(conn, host, user, pass, db, 0, NULL, CLIENT_MULTI_STATEMENTS);
Or set it later:
mysql_set_server_option(MYSQL_OPTION_MULTI_STATEMENTS_ON);
I learnt those from the C API Handling of Multiple Statement Execution page.
How I Debugged?
Debugging a stored procedure is not so easy. I used traditional log-table method, but performed a bit aggresively about finding the error code.
Firstly, defined two variables to keep the code and message about the error occurred:
DECLARE E INT DEFAULT 0; -- error code
DECLARE M TEXT DEFAULT NULL; -- error message
Then, defined possible error codes and messages both for client and server errors (full list here):
DECLARE CONTINUE HANDLER FOR 1000 SET E='1000', M="hashchk";
DECLARE CONTINUE HANDLER FOR 1001 SET E='1001', M="isamchk";
...
...
DECLARE CONTINUE HANDLER FOR 1312 SET E='1312', M="PROCEDURE %s can't return a result set in the given context";
...
...
DECLARE CONTINUE HANDLER FOR 1638 SET E='1638', M="Non-ASCII separator arguments are not fully supported";
DECLARE CONTINUE HANDLER FOR 1639 SET E='1639', M="debug sync point wait timed out";
DECLARE CONTINUE HANDLER FOR 1640 SET E='1640', M="debug sync point hit limit reached";
...
...
DECLARE CONTINUE HANDLER FOR 2057 SET E='2057', M="The number of columns in the result set differs from the number of bound buffers. You must reset the statement, rebind the result set columns, and execute the statement again";
And finally, put logs in critical points:
IF E> 0 THEN
CALL WriteLog(CONCAT("Error ", E, ": ", M));
END IF;
WriteLog is another procedure that only inserts into a log table. This method gave me the error code (1312) and then some Googling worked.
I am having a strange problem with MySQL Stored Procedure.
I have written a simple stored procedure as follows:
{
DELIMITER $$
CREATE DEFINER=`username`#`%` PROCEDURE `sp_create_my_log`(IN source TEXT,
OUT my_id INT)
BEGIN
--
-- insert record and return primary key
INSERT INTO my_log (source) VALUES (source);
SET my_id = LAST_INSERT_ID();
COMMIT;
END
}
This stored procedure is running absolutely fine on my local machine (MySQL Server 5.1, Windows XP). But when I try to run it on the server, I get the following error:
java.sql.SQLException: Parameter index of 2 is out of range (1, 0)
at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:1075)
at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:989)
at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:984)
at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:929)
at com.mysql.jdbc.CallableStatement$CallableStatementParamInfo.checkBounds(CallableStatement.java:274)
at com.mysql.jdbc.CallableStatement.checkParameterIndexBounds(CallableStatement.java:710)
at com.mysql.jdbc.CallableStatement.checkIsOutputParam(CallableStatement.java:672)
at com.mysql.jdbc.CallableStatement.registerOutParameter(CallableStatement.java:1846)
at org.apache.commons.dbcp.DelegatingCallableStatement.registerOutParameter(DelegatingCallableStatement.java:95)
at org.apache.commons.dbcp.DelegatingCallableStatement.registerOutParameter(DelegatingCallableStatement.java:95)
at com.mycomp.myprj.importer.ImporterImpl.onPreLoad(ImporterImpl.java:160)
at com.mycomp.myprj.importer.csv.FileImporter.load(FileImporter.java:43)
at com.mycomp.myprj.importer.csv.MyImporter.main(MyImporter.java:82)
0.843 seconds
Any idea why this is happening?
Just a wild guess (I'm more an Oracle than a MySQL kind of guy):
- Did you perhaps declare your parameter in Java wrong? (my_id is declared as an out parameter in the stored procedure, do the signatures in Java and MySQL match?)
- What happens if you call your procedure from the MySQL command line interface?
Kind regards, Frank
I need to perform several inserts in a single atomic transaction. For example:
start transaction;
insert ...
insert ...
commit;
However when MySQL encounters an error it aborts only the particular statement that caused the error. For example, if there is an error in the second insert statement the commit will still take place and the first insert statement will be recorded. Thus, when errors occur a MySQL transaction is not really a transaction. To overcome this problem I have used an error exit handler where I rollback the transaction. Now the transaction is silently aborted but I don't know what was the problem.
So here is the conundrum for you:
How can I both make MySQL abort a transaction when it encounters an error, and pass the error code on to the caller?
How can I both make MySQL abort a transaction when it encounters an error, and pass the error code on to the caller?
MySQL does pass error code to the caller and based on this error code the caller is free to decide whether it wants to commit work done up to the moment (ignoring the error with this particular INSERT statement) or to rollback the transaction.
This is unlike PostgreSQL which always aborts the transaction on error and this behavior is a source of many problems.
Update:
It's a bad practice to use an unconditional ROLLBACK inside the stored procedures.
Stored procedures are stackable and transactions are not, so a ROLLBACK within a nested stored procedure will roll back to the very beginning of the transaction, not to the state of the stored procedure execution.
If you want to use transactions to restore the database state on errors, use SAVEPOINT constructs and DECLARE HANDLER to rollback to the savepoints:
CREATE PROCEDURE prc_work()
BEGIN
DECLARE EXIT HANDLER FOR SQLEXCEPTION ROLLBACK TO sp_prc_work;
SAVEPOINT sp_prc_work;
INSERT …;
INSERT …;
…
END;
Failure in either insert will roll back all changes made by the procedure and exit it.
Using Mr. Quassnoi's example, here's my best approach to catching specific errors:
The idea is to use a tvariable to catch a simple error message, then you can catch sql states you think may happen to save custom messages to your variable:
DELIMITER $$
DROP PROCEDURE IF EXISTS prc_work $$
CREATE PROCEDURE prc_work ()
BEGIN
DECLARE EXIT HANDLER FOR SQLSTATE '23000'
BEGIN
SET #prc_work_error = 'Repeated key';
ROLLBACK TO sp_prc_work;
END;
DECLARE EXIT HANDLER FOR SQLEXCEPTION
BEGIN
SET #prc_work_error = 'Unknown error';
ROLLBACK TO sp_prc_work;
END;
START TRANSACTION;
SAVEPOINT sp_prc_work;
INSERT into test (id, name) VALUES (1, 'SomeText');
COMMIT;
END $$
DELIMITER ;
Then you just do your usual call, and do a select statement for the variable like:
call prc_work(); select #prc_work_error;
This will return either NULL if no error, or the message error in case of an error. If you need persistent error message you can optionally create a table to store it.
It's tedious and not very flexible because requires a DECLARE EXIT HANDLER segment for each status code you want to catch, it won't also show detailed error messages but hey, it works.