Variable scope within a stored procedure? - mysql

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

Related

MySql prepare statement - is it possible to parametrize column name or function name?

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.

MySQL stored procedure `EXECUTE` command with user defined variable rather than input parameter

I am trying to create a stored procedure in MySQL which is not supposed to be vulnerable to SQL injection. Hence I am using prepared statements inside this. I have a Patient table to which I want to add data using this procedure. This is what my stored procedure looks like.
DROP PROCEDURE IF EXISTS CreatePatient;
DELIMITER ##
CREATE PROCEDURE CreatePatient (IN alias VARCHAR(20))
BEGIN
PREPARE q1 FROM 'insert into Patient values (?)';
set #alias = alias;
EXECUTE q1 USING #alias;
END ##
DELIMITER ;
When I tried to run this without setting a new variable #alias,
EXECUTE q1 USING alias;
I am getting an SQL syntax error. From my understanding, it doesn't seem right to create a variable within the method body just to assign it the input variable to the procedure. What am I missing here?
Mysql has 3 types of variables
User Defined Variables
Local variables
session variables
User defined variables have session scope while local variables have a block scope i.e within BEGIN-END Block.
Because local variables are in scope only during stored program execution, references to them are not permitted in prepared statements created within a stored program. Prepared statement scope is the current session, not the stored program, so the statement could be executed after the program ends, at which point the variables would no longer be in scope. For example, SELECT ... INTO local_var cannot be used as a prepared statement. This restriction also applies to stored procedure and function parameters
See official docs

MySQL #variable in prepare statement concurrency issue

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.

In mysql dynamic stored proc, is #var required to build a prepared statement?

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.

Dynamic MySQL with local variables

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;
"