Procedure terminating silently when executing prepared statement - mysql

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.

Related

MySQL Cursors - having errors in MySQL Workbench

I am trying to write a mysql program, which has cursors in it. Due to an error in writing an DECLARE query, MySQL Workbench is always showing me the DECLARE is not valid at this position, expected EOF, ALTER, ANALYZE, BEGIN, BINLOG, CACHE, ...
Could you help me solve this problem?
Here is my code:
DELIMITER //
BEGIN
declare Naslov_knjige VARCHAR(24);
declare Cena_knjige DECIMAL(8,2);
DECLARE cursor_cene CURSOR
FOR SELECT
Naslov,
Cena
FROM
prvi_test_v2.knjige;
OPEN cursor_cene //
FETCH NEXT FROM cursor_cene INTO
#Naslov_knjige,
#Cena_knjige //
WHILE ##FETCH_STATUS = 0
BEGIN
PRINT #Naslov_knjige + CAST(#Cena_knjige AS VARCHAR) //
FETCH NEXT FROM cursor_cene INTO
#Naslov_knjige,
#Cena_knjige //
END //
CLOSE cursor_cene //
DEALLOCATE cursor_cene //
END //
DELIMITER ;
Thanks for your help!
I will assume you omitted a line for CREATE PROCEDURE, because in MySQL a BEGIN...END block must be part of a stored routine. See https://dev.mysql.com/doc/refman/8.0/en/begin-end.html
BEGIN ... END syntax is used for writing compound statements, which can appear within stored programs (stored procedures and functions, triggers, and events).
You changed the DELIMITER:
DELIMITER //
Using this delimiter terminates the whole CREATE PROCEDURE statement. You should not do this after the first statement in the body of the procedure. You need to use the normal ; terminators for each statement within the body of the procedure. The reason for changing the delimiter is so you can use ; for each statement in the procedure without ending the CREATE PROCEDURE.
See examples and documentation here: https://dev.mysql.com/doc/refman/8.0/en/stored-programs-defining.html
That's the reason for the error you got. You used // to terminate OPEN cursor_cene // which ended the CREATE PROCEDURE, but clearly there was more to that procedure.
There are other problems with your procedure. You seem to be using Microsoft SQL Server syntax, but MySQL is different.
Naslov_knjige is not the same variable as #Naslov_knjige in MySQL. Don't use the # sigil in front of local variables. If you use the # sigil, this refers to a user-defined variable.
The WHILE ##FETCH_STATUS = 0 syntax is specific to Microsoft SQL Server. MySQL has different syntax for running a cursor loop. See example in the documentation: https://dev.mysql.com/doc/refman/8.0/en/cursors.html
That's as far as I got. There may be more problems, but I am not going to look for them.

MySql: stop script if SELECT != (certain result)

I have a data cleanup script that I execute in Mysql Workbench in Windows. At the beginning of the script I have:
select ##hostname;
-- WARNING: it HAS to be `srv-datatest`
(+ the rest of the script)
I want the script to stop at that line if the condition (##hostname = 'srv-datatest') is not met.
Things that I've tried:
How to throw an exception:
DECLARE invalid_database CONDITION FOR 1051;
SIGNAL invalid_database;
-- DECLARE is not accepted by my workbench, and it lacks the "if" part anyway
How to do an IF on a select:
SELECT IF(##hostname='srv-datatest','yes','no');
-- It lacks the "stop here" part
create a stored procedure that will perform your process and test.
your script in mysql workbench should only call your stored procedure
It is possible to run it without stored procedures, using this ingenious solution:
SET #hostname := (SELECT ##hostname);
SET #condition = (#hostname='srv-datatest');
SET SESSION sql_mode = if(#condition, ##SESSION.sql_mode, 'Script stopped. Check condition.');
In this code:
If #condition=1, the script will run smoothly.
If #condition=0 it will throw an exception, showing the message error.

Executing all stored proceedures in one script

i am handing over a project to a another party which i have been doing for some time, in this project i do some modifications to some of the existing table by adding coloumns, renaming coloumns etc.
When i was handling the project what i did was, putting the changes or the modifications inside a stored proceedure once it was run calling the function from the query browser.
stored proceedure
CREATE DEFINER=`my_db`#`10.%` PROCEDURE `alter_test_1`()
BEGIN
DECLARE v_finished INTEGER DEFAULT 0;
DECLARE v_table VARCHAR(100) DEFAULT "";
DECLARE stmt VARCHAR(500) DEFAULT "";
DECLARE column_cursor CURSOR FOR
SELECT TABLE_NAME FROM `information_schema`.`tables` WHERE table_schema = 'my_db'
AND table_name LIKE 'tot_table_%';
DECLARE CONTINUE HANDLER
FOR NOT FOUND SET v_finished = 1;
OPEN column_cursor;
alter_tables: LOOP
FETCH column_cursor INTO v_table;
IF v_finished = 1 THEN
LEAVE alter_tables;
END IF;
SET #prepstmt = CONCAT('ALTER TABLE my_db','.',v_table,' CHANGE OS platform VARCHAR(25);');
PREPARE stmt FROM #prepstmt;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
END LOOP alter_tables;
CLOSE column_cursor;
END
afterwards i run
call alter_test_1();
in the query browser.
Now as im handing this over this two step excution is not professional therefore is there a way for me to do this by using another stored proceddure instead running a call alter_test_1() separately... what i mean is there a way to put this call or several call statements inside a stored proceedure and excecute all the call statements in one shot, once that particular stored proceedure is run.
Well... you call a stored procedure within another stored procedure... by calling it.
CREATE PROCEDURE `do_things`()
BEGIN
CALL `alter_test_1`();
CALL `do_more_stuff`();
END
Executing CALL do_things(); will first run your procedure, then one called "do_more_stuff" and then will return. It will terminate at the first unhandled error thrown unless you catch the error with a HANDLER.
That seems like the answer to the question you asked. I'm less clear about whether that was the question you intended to ask, because you talk about "2 step execution," and I don't see what the 2 steps are.
If you are asking how to run a stored procedure (step 2?) without declaring it first (step 1?), then, no, you can't... it's a "stored" procedure, and has to be stored before it can be executed.

Mysql very strange behavior

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

Exception while calling stored procedure from jdbc

Calling a stored procedure results in this exception:
SQLException1 java.sql.SQLException: User does not have access to
metadata required to determine stored procedure parameter types. If
rights can not be granted, configure connection with
"noAccessToProcedureBodies=true" to have driver generate parameters
that represent INOUT strings irregardless of actual parameter types.
To resolve this, I tried:
Connection c = DriverManager.getConnection("jdbc:mysql://localhost:3306
/database?useInformationSchema=true&noAccessToProcedureBodies=true",
"user_name", "pasword");
But it still does not work.
I am using shared hosting.
I am using
Software version: 5.0.91-community-log - MySQL Community Edition (GPL)
Protocol version: 10
Java 1.6
mysql-connector-java-5.1.14-bin.jar
One of my stored procedures is:
DROP PROCEDURE IF EXISTS `share_message`
DELIMITER //
CREATE PROCEDURE share_message(in messageid1 int(200),in received_by1 int(20),
in sent_by1 int(20),in shared_of1 int(20),author1 int(20), OUT query_status1 TINYINT)
BEGIN
DECLARE EXIT HANDLER FOR SQLEXCEPTION
BEGIN
-- ERROR
SET query_status1 = -1;
rollback;
END;
DECLARE EXIT HANDLER FOR SQLWARNING
BEGIN
-- WARNING
SET query_status1 = -1;
rollback;
END;
START TRANSACTION;
SET query_status1 = 0;
INSERT INTO post_message_users(messageid,received_by,sent_by,shared_of,author)
VALUES(messageid1,received_by1,sent_by1,shared_of1,author1);
UPDATE post_messages SET total_share=total_share+1 WHERE messageid=messageid1;
SET query_status1 =1;
COMMIT;
END//
DELIMITER ;
This is working properly with my local database.
It seems that the stored procedure you are attempting to use needs access to MySQL's INFORMATION_SCHEMA. That's a (fake) database built in every MySQL server; it's used to fetch descriptions of tables, columns, indexes, and the like.
It looks like the user id you're using doesn't have access to the INFORMATION_SCHEMA. That's understandable on a hosting service.
Go on MyPhpAdmin and try a query like this to be sure about that.
SELECT table_schema, table_name
FROM information_schema.columns
WHERE column_name = 'something'
AND table_schema = 'your database name'
If you get some kind of error saying you don't have permission, this is definitely your problem.
You could try rewriting your stored proc, or you could ask your hosting service to grant you the appropriate priv.
TLDR; Change your Java code, make the CallableStatement reference parameters by index instead of name.
After having a similar problem I updated my JDBC driver mysql-connector-java-5.1.26-bin.jar.
The error then changed from
User does not have access to metadata required to determine stored
procedure parameter types. If rights can not be granted, configure
connection with "noAccessToProcedureBodies=true" to have driver
generate parameters that represent INOUT strings irregardless of
actual parameter types.
to
No access to parameters by name when connection has been configured not to access procedure bodies
I changed my Callable Statement to reference parameters by index instead of name, and hey presto it works.
Updating the driver may not be necessary, just knowing to use indexes instead of names when you don't have metadata access or routine body access.
Good Luck