I am trying to write a store procedure in mysql which takes the input variables as criteria to choose which table be counted. And then will react according to the value.
e.g.
PROCEDURE `Function`(IN table_name varchar(10))
BEGIN
SET #c2 = CONCAT ('Select count(*) into #count From ',table_name);
PREPARE stmt from #c2;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
IF #count > 0 Then
doing something
Else
do something else
End If
END
As far as I understand, #count value is stored in the session rather than local. Prepare stmt needs to use #var in order to access the value elsewhere within the store procedure.
Now I have an issue, When I have a number of executions calling this store procedure at the same time would cause concurrency issue.
Is there any solution to resolve the concurrency issue? or alternative to run a dynamic query without needing #var?
Thanks you
There's a big difference between a stored procedure and a function. So please don't name your procedure function.
That aside, you have no problem. There's no concurrency issue. Like you said, user defined variables (the one with the #) have session scope. To run it somewhat "simultaneous", you'd have to do this in another session and there the variable would have another scope.
Related
Lets say that I want to write a procedure allowing me to call certain function on certain column, for example:
call foo('min','age') -> SELECT min(age) FROM table;
I want my procedure to be safe from sql injection, therefore, I'm willing to use prepared statements and parametrize the input
SET #var = "SELECT ?(?) FROM table;"
PREPARE x FROM #var;
EXECUTE x USING a, b;
Where a and b are input parameters, function and column, respectively.
However, it doesnt seem to be possible - InnoDB keeps throwing an error whenever I want to execute this statement.
Is it possible to solve this this way, or I need to resort to whitelisting?
EDIT:
Full code:
create procedure test(in func varchar(20), in col varchar(20))
begin
set #f = func;
set #c = col;
set #sql = "select ?(?) from table;";
prepare x from #sql;
execute x using #f, #c;
end;
calling:
call test('min','age');
Full error:
[42000][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 '(?) from table' at line 1
You cannot parametrize column/table/function name/alias. As, PREPARE statement only allow "values" part of the SQL query to be used as parameters. Function/Table/Column name/alias are used to determine the validity of the SQL statement; and thus cannot be changed during run-time execution. Changing it at execution time would potentially alter whether the SQL statement was valid.
You can think of it as compiling a code; hence the compiler must know all the function/class name(s) etc for creating a valid executable (yes, we can do dynamic classes, but that is rare). On the other hand, we can change input "values" to the program, but generally cannot change the operations to be done on the input data.
Also, MySQL server would consider the parameters as literals, and apply quotes around them, before using them in query execution.
Now, in your case, you can still use the function name as parameter for Stored procedure, and generate the query string using that. But you cannot use it as a parameter for the query itself.
delimiter $$
create procedure test(in func varchar(20), in col varchar(20))
begin
set #c = col;
-- use concat function to generate the query string using func parameter
set #sql = concat('select ', func, '(?) from table');
-- prepare the statement
prepare stmt from #sql;
-- execute
execute x using #c;
-- don't forget to deallocate the prepared statement
deallocate prepare stmt;
end$$
delimiter ;
I came across this while writing a mySQL query builder plugin. My solution was to prefix column and function names with a "?" character (the user can change the character in the plugin preferences).
The code that builds the prepared statement looks for values that begin with "?" and inserts the subsequent column/function name into the query inline instead of as prepared statement values.
I have a similar stored proc (but longer). It is called from PHP (GET request on Apache)
delimiter //
CREATE PROCEDURE dynamic(IN tbl CHAR(64), IN col CHAR(64))
BEGIN
SET #full_statement = CONCAT('SELECT ',col,' FROM ',tbl );
PREPARE stmt FROM #full_statement;
EXECUTE stmt;
END
//
delimiter ;
From what I read, #s is a mysql session variable living as long as my session is alive.
The #s presence annoys me since I fear that 2 concurrent request on that stored
proc might play with this "global variable". So I tried to remove the '#' like this
delimiter //
CREATE PROCEDURE dynamic(IN tbl CHAR(64), IN col CHAR(64))
BEGIN
DECLARE full_statement VARCHAR(300);
SET full_statement = CONCAT('SELECT ',col,' FROM ',tbl );
PREPARE stmt FROM full_statement;
EXECUTE stmt;
END
//
delimiter ;
to build the prepared statement without success.
I seem to have constantly
ERROR 1064 (42000): 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 'full_statement; EXECUTE stmt;
Does my fear of 2 calls within my php session is really a problem ?
(If not, what about having 200 stored procedures using that same
global variable) ?
Can I really achieve my goal and remove the '#' and let the prepared
statement being handle by a simple stored proc variable or is it a constraint of prepared statement ?
Regards.
Yes, the #var is required.
MySQL's PREPARE statement only accepts "user variables" (those prefixed with #), not local variables declared in a stored routine.
This has long been recognized as a WTF in MySQL:
Bug #17409 PREPARE doesn't support queries in local variables
Does my fear of 2 calls within my php session is really a problem ?
No. It's not a global variable, it's a session variable.
Two concurrent sessions have their own value for #var.
I have noticed in mysql that why preparing a dynamic query it requires what I think is a global variable. Is there a way to limit the scope of the variable to only betwenn the begin and end statements? Below is my test script when returns a value of 10 for the limitCnt variable.
delimiter //
drop procedure if exists testProc//
create procedure testProc ()
begin
-- DECLARE limitCnt INT default 10;
SET #limitCnt = 10;
PREPARE stmt FROM 'SELECT * FROM `participants` LIMIT ?';
EXECUTE stmt USING #limitCnt; -- the using part of the execute does not like the local variable
DEALLOCATE PREPARE stmt;
end//
call testProc()//
select #limitCnt//
drop procedure testProc//
delimiter ;
If you want to use local variables then they have to be scoped within begin...end block, however if you have nested begin and end blocks within some begin and end block then the variables declared in the "so called" parent begin and end block are accessible from the "so called" children begin and end blocks. Please read through http://dev.mysql.com/doc/refman/5.0/en/local-variable-scope.html.
What you are doing in your code is setting a session variable called limitCnt which is accessible and available anywhere in your procedure hence you are getting 10 as result when you do select #limitCnt.
To use a local variable use declare var_name var_type within your begin...end block.
Hope this answers your question.
According to the MySQL 5.0 Documentation, you cannot use local variables in EXECUTE ... USING statements. See an excerpt from the documentation below on how to use the statement:
EXECUTE stmt_name
[USING #var_name [, #var_name] ...]
You have to use the # prefix.
There is no way around it. It's by design in MySql.
Local/stored procedure/stored function level variables
can't be used to PREPARE a statement from
can't be referred in you prepared statement
can't be used to pass a parameter value to EXECUTE with USING
PREPARE Syntax
... Statement names are not case sensitive. preparable_stmt is either a
string literal or a user variable that contains the text of the SQL
statement. ...
... A statement prepared in stored program context cannot refer to stored
procedure or function parameters or local variables because they go
out of scope when the program ends and would be unavailable were the
statement to be executed later outside the program. ...
EXECUTE Syntax
...
Parameter values can be supplied only by user variables, and the USING
clause must name exactly as many variables as the number of parameter
markers in the statement. ...
User variables have a scope of a session.
Now, to address your concern just always explicitly set a value to a user defined variable before you use it. Just as you did assigning a default value to #limitCnt.
SET #limitCnt = 10;
Also you can set a value of NULL to such variable at the end of your stored procedure/stored function/script.
SET #limitCnt = NULL;
Here is SQLFiddle demo
I learned today through this section of the MySQL documentation that prepared statements cannot be performed in stored functions, but, as of MySQL version 5.0.13, they can be performed in stored procedures.
Today I was putting together a stored procedure and thought initially it might be interesting to try doing an INSERT statement in it as a prepared statement. However, despite this supposedly being possible (I'm using MySQL 5.5.14), the ? parameter marker in the statement string caused MySQL to throw a syntax error.
I threw a couple of simplified examples together using the same exact sort of syntax I used for the prepared INSERT statement. I'm hoping I just have a syntax error somewhere I just haven't caught. The first block, below, is the procedure that works, i.e. it uses the standard CONCAT(your query string) syntax.
DROP PROCEDURE IF EXISTS TestConc;
DELIMITER $$
CREATE Procedure TestConc()
BEGIN
SET #sql := CONCAT('CREATE TABLE Foo (FooID INT) ENGINE = InnoDB');
PREPARE stmt FROM #sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
SET #tn := 'Foo';
SET #sql := CONCAT('INSERT INTO ', #tn, ' VALUES (5)');
PREPARE stmt FROM #sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
END;
$$
DELIMITER ;
Upon calling the procedure with this code, the expected happens; 5 is stored in the FooID field of the newly-generated Foo table. However, if we change the lines between the two DEALLOCATE PREPARE directives to this:
SET #tn := 'Foo';
SET #sql := 'INSERT INTO ? VALUES (5)';
PREPARE stmt FROM #sql;
EXECUTE stmt USING #tn;
We get an error that tells us to check the syntax of the statement near '? VALUES (5)'.
Is it just not possible to substitute a parameter marker for a table name? I haven't tried doing something along the lines of 'SELECT ? FROM Foo' to see if this will work yet. Also, and I don't know if it's important, I've been trying this using MySQL Workbench 5.2.35 CE, rather than a command line.
I don't have any specific need to run queries as prepared statements within procedures ATM, I just want to make sure I have the syntax correct for doing so if I ever should need to.
The parameter '?' cannot be used for identifiers. Use first variant. From the reference - Parameter markers can be used only where data values should appear, not for SQL keywords, identifiers, and so forth.
Is it just not possible to substitute a parameter marker for a table name?
No, it's not possible. If you ever think you need this feature, it could be a sign that you have a bad table design.
If you really need to specify the table at runtime, you can use dynamic SQL but be careful not to introduce SQL injection vulnerabilities.
How can I use dynamic SQL statements in MySQL database and without using session variables?
Right now I have such a code (in MySQL stored procedure):
(...)
DECLARE TableName VARCHAR(32);
SET #SelectedId = NULL;
SET #s := CONCAT("SELECT Id INTO #SelectedId FROM ", TableName, " WHERE param=val LIMIT 1");
PREPARE stmt FROM #s;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
IF ISNULL(#SelectedId) THEN
(...)
But I'd like to use only local variables, that means I'd like to start this procedure with:
DECLARE TableName VARCHAR(32);
DECLARE s VARCHAR(1024);
DECLARE SelectedId INTEGER UNSIGNED;
(...)
and do not use # char anywhere. Is there any way to do this?
Sorry, prepared statements in MySQL are session-global. According to http://dev.mysql.com/doc/refman/5.1/en/sql-syntax-prepared-statements.html, "A prepared statement is also global to the session."
And there's no other way (besides prepared statements) to execute dynamic SQL in MySQL 5.x.
So you can of course replace "#s" above, but AFAIK you're stuck with #SelectedId.
In MySQL 6.x, there is a feature planned which will add an "EXECUTE IMMEDIATE" statement which will execute dynamic SQL. See http://forge.mysql.com/worklog/task.php?id=2793.
The link above gives a page not found. See here instead :
https://dev.mysql.com/doc/refman/5.7/en/prepare.html
The end para clearly states :
"
A statement prepared in stored program context cannot refer to stored procedure or function parameters or local variables because they go out of scope when the program ends and would be unavailable were the statement to be executed later outside the program. As a workaround, refer instead to user-defined variables, which also have session scope;
"