set #switch_schema= concat('use ', v6_schema, ';');
select #switch_schema;
PREPARE s3 from #switch_schema;
EXECUTE s3;
Prepare statement does not support 'Use'; is there a solution to this?
The workaround is the following but I am looking for a more robust solution
set #db := v6_schema;
drop temporary table if exists tempdb.activeUnits;
set #query = concat ('create temporary table tempdb.activeUnits
select *
from ',#db,'.activemodelunits_blue
where active_datetime = (Select max(active_datetime) from ',#db,'.ActiveModelUnits_blue) ';
PREPARE s3 from #query;
EXECUTE s3;
I'm afraid since you can't run USE as a prepared statement, your options are limited.
USE before you call this routine
You could call USE from your application before calling the procedure where you reference the table.
USE v6_schema;
CALL MyProcedure();
USE inside a CASE
If you are writing this code in a stored procedure, you can use the CASE statement.
BEGIN
CASE v6_schema
WHEN 'myschema1' THEN USE myschema1;
WHEN 'myschema2' THEN USE myschema2;
WHEN 'myschema3' THEN USE myschema3;
ELSE USE mydefaultschema;
END CASE;
END;
This means you're limited to the finite list of schemas for which you have coded. You can't make this adapt to any future schema name you think of in the future, without updating the code.
Use qualified table names
This is the workaround you mentioned in your question. Concatenate the schema name with table names, every time you reference those tables in prepared queries.
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 stored procedure in a database that accepts string arguments that are inserted directly into a query. I have client-side code to escape the inputs, but that doesn't stop anyone with permission to execute that procedure with bad arguments and inject SQL.
Current implementation is something like this:
CREATE PROCEDURE grantPermissionSuffix (perm VARCHAR(30), target VARCHAR(30), id VARCHAR(8), host VARCHAR(45), suffix VARCHAR(45))
BEGIN
SET #setPermissionCmd = CONCAT('GRANT ', perm, ' ON ', target, ' TO ''', id, '''#''', host, ''' ', suffix, ';');
PREPARE setPermissionStmt FROM #setPermissionCmd;
EXECUTE setPermissionStmt;
DEALLOCATE PREPARE setPermissionStmt;
FLUSH PRIVILEGES;
END
Clearly this is a recipe for disaster. How can I prevent an injection opportunity? Is there a standard SQL function to escape input? Is there one for MySQL? Is there another way to get the same result without extra client-side code?
My fear is that the only solution will be client-side prepared statements, which is not an option at this time. I need all the logic to be handled on a server, requiring clients to only call this procedure (I don't want to have to grant users permission to modify tables/permissions directly, only to handle it with procedures they're allowed to execute).
You can prepare statements with ? placeholders for variables and later EXECUTE USING the variables, just like in client-side prepared statements, at least according to the manual. I'm not sure how well this would work when substituting table names, though, but this is limited by prepare rules, so if it doesn't work in server-side, it wouldn't work on client-side either.
UPDATE:
Apparently, mysql doesn't recognize ? placeholders in prepared GRANT query. In that case, you'll have to take care of it manually. Some tips are in that answer - namely, using ` (backtick) to escape identifiers and using a whitelist for keywords - that way, you also gain fine-grain control on what you do allow in your procedure.
I would add that for your specific purposes it might be better to select from information_schema and mysql tables to control that, for example, db and table passed to you actually exist. You can use prepared statements with placeholders for that, so it's safe. Something like this will check db and table:
PREPARE mystat FROM 'SELECT count(*) into #res FROM INFORMATION_SCHEMA.TABLES WHERE upper(TABLE_SCHEMA)=UPPER(?) and UPPER(TABLE_NAME)=UPPER(?)';
set #db = 'mydb'; --these two are params to your procedure
set #table = 'mytable';
set #res = 0;
execute mystat using #db, #table;
select #res; --if it's still 0, then no db/table exists, possibly an attack is happening.
Checking user and host can be done like that, too, using mysql.users table instead. For the rest of params, you'll have to build a whitelist.
Yet another way I see is to check for allowed characters using a regular expression - there's REGEXP command for that. For example, you can control that your procedure parameter has only alphabetic uppercase with if #var REGEXP '^[A-z]+$'. To my knowledge, it's impossible to perform an SQL injection using only A-z.
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.
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.
I need to use a native sql query in Hibernate with use of variable.
But hibernate throws an error saying: Space is not allowed after parameter prefix
So there is a conflict with the := mysql variable assignment and hibernate variable assignment.
Here is my sql query:
SET #rank:=0;
UPDATE Rank SET rank_Level=#rank:=#rank+1 ORDER BY Level;
the hibernate code (jpa syntax):
Query query = em.createNativeQuery(theQuery);
query.executeUpdate();
I can't use a stored procedure because my sql query is dynamically generated ('Level' can be 'int' or 'force'...)
How can I do this ?
thanks
Well, I finally use stored procedure (yes, what I don't want initially) to create dynamic query (I don't think it was possible).
Here is my code:
The stored procedure:
DELIMITER |
DROP PROCEDURE IF EXISTS UpdateRank |
CREATE PROCEDURE UpdateRank(IN shortcut varchar(30))
BEGIN
SET #rank=0;
SET #query=CONCAT('UPDATE Rank SET ', shortcut, '=#rank:=#rank+1 ORDER BY ', shortcut);
PREPARE q1 FROM #query;
EXECUTE q1;
DEALLOCATE PREPARE q1;
END;
|
DELIMITER ;
The tip is the use of the CONCAT function to dynamically create a query in the stored procedure.
Then, call the procedure in classic hibernate function:
Query q = em.createNativeQuery("CALL updateRank('lvl')");
q.executeUpdate();
I'll copy paste my answer from https://stackoverflow.com/a/25552002/3987202
Another solution for those of us who can't make the jump to Hibernate 4.1.3.
Simply use /*'*/:=/*'*/ inside the query. Hibernate code treats everything between ' as a string (ignores it). MySQL on the other hand will ignore everything inside a blockquote and will evaluate the whole expression to an assignement operator.
I know it's quick and dirty, but it get's the job done without stored procedures, interceptors etc.
Use MySQL Proxy to rewrite the query after Hibernate has sent the query to the database.
For example supply Hibernate with this,
UPDATE Rank SET rank_Level=incr(#rank) ORDER BY Level;
but rewrite it to this,
UPDATE Rank SET rank_Level=#rank:=#rank+1 ORDER BY Level;