I need to use user variable in prepare statement of mysql
stored procedure. (The purpose is to substitute the table name in
drop table command, which is unable to be injected via ? and
passed via execute using... statement because it is not a data element).
I suppose the user variables are session-wide global variables.
I suppose the stored procedure accessing the user variable must be
synchronized to protect against unwanted behaviour when it is called
simultaneously more times within single session (which I cannot prevent).
How to perform such synchronization?
Is there any chance it is performed internally by the mysql?
It seems like mysql get_lock() & co. uses logic that does not help much:
get_lock('a') followed by get_lock('b') destroys state of a. Maybe I have terribly missed some point here...
For those who would ask the "what exactly would you like to do" question:
drop procedure if exists drop_t_table; delimiter $$
create procedure drop_t_table(in in_t_table_name varchar(128))
begin
declare sql_drop varchar(256) default 'drop temporary table if exists ';
--
-- I would suspect sql_drop_table user variable guard should be locked here...
--
set #sql_drop_table = concat(sql_drop, in_t_table_name);
--
-- What if the procedure is preempted to another call here
-- and the sql_drop_table gets different table name?
--
prepare exe from #sql_drop_table;
--
-- ...and unlocked here
--
execute exe;
deallocate prepare exe;
end$$ delimiter ;
Variables declared within your stored procedure are local to the procedure. An example is sql_drop in your code.
Each session is, basically, single-threaded. You can't do more than one thing at a time within a session. There's no way to call a stored procedure more than once from within a particular session.
If you have more than one session you can call the same stored procedure from both of them. But, a DROP TABLE operation is basically idempotent: If you call it more than once, it has the same effect as calling it just once. It isn't precisely idempotent: it throws an error if the table doesn't exist. But, still, dropping the same table more than once isn't any more destructive than dropping it once.
Temporary tables (a) are only visible to the session that created them, and (b) vanish when the session ends. So, going to a lot of trouble to drop them explicitly might be overkill.
So, with respect, you might be overthinking this problem.
Related
I currently have all of my sql queries written in my PHP files, within each class method. Is it possible to move all of these queries into stored procedures or stored functions in the database & simply pass the corresponding values / arguments into them from PHP?
I have read some of the documentation & it still appears unclear.
Thank you. :)
DELIMITER $$
create procedure `accounting`.`delete_invoice_line` (invoice_line_id INT)
BEGIN
delete from invoice_line where id = invoice_line_id;
END;
$$
DELIMITER ;
I had to figure the format for creating the procedure. I am following this pattern & it appears to be working properly. Then granting execute privileges for the user name.
Thank you all for your input. :)
Most queries can be moved into stored procedures, but probably not all of them. See mariadb's documentation on which SQL statements cannot be used in stored procedures:
ALTER VIEW; you can use CREATE OR REPLACE VIEW instead. LOAD DATA and
LOAD TABLE. INSERT DELAYED is permitted, but the statement is handled
as a regular INSERT.
LOCK TABLES and UNLOCK TABLES.
References to local variables within prepared statements inside a stored routine (use user-defined variables instead).
BEGIN (WORK) is treated as the beginning of a BEGIN END block, not a transaction, so START TRANSACTION needs to be used instead.
The number of permitted
recursive calls is limited to max_sp_recursion_depth. If this variable
is 0 (default), recursivity is disabled. The limit does not apply to
stored functions.
Most statements that are not permitted in prepared
statements are not permitted in stored programs. See Prepare
Statement:Permitted statements for a list of statements that can be
used.
SIGNAL, RESIGNAL and GET DIAGNOSTICS are exceptions, and may be
used in stored routines
Having said this, even though a SQL statement can be moved into a stored procedure, you may not necessarily want to do that due to code complexity or performance reasons.
I have a database to which several real users have access using JDBC. The separation of these users is very crucial, such that they are neither able see nor manipulate each other's data.
To separate the users' data, I decided to create the few corresponding tables for every user, and only give the owning user the permissions on this table. This is all done automatically in a stored procedure.
Besides this table separation, I want a user only to execute stored procedures to work with his tables, not arbitrary SQL queries.
Let's consider an example procedure like
DELIMITER //
CREATE PROCEDURE MyProcedure(IN p_identifier VARCHAR(30), IN p_key INT(10))
BEGIN
SET #p_identifier = p_identifier;
SET #p_key = p_key;
SET #s = CONCAT('DELETE FROM ', p_identifier, '_mytable WHERE key =?;');
PREPARE stmt FROM #s;
EXECUTE stmt USING #p_key,
DEALLOCATE PREPARE stmt;
END //
DELIMITER ;
And use it like CALL MyProcedure('ad3e981b', 2);.
So far, so good.
Problems arise since this procedure is used by all users (the first paraeter is the table-prefix). This is bad, because once a user gets execution privileges for MyProcedure, he is able to to provide any table-prefix at will and mess with other user's data - so my user separation is gone. This is the problem I need to solve.
I currently see two solutions:
Don’t use stored procedures: Just give INSERT/DELETE/SELECT privileges on the user's table only to the user. This is simple and works - but I'd prefer to only call stored procedures over JDBC, not arbitrary SQL queries.
Make a stored procedure for EVERY user, in which the table prefix is invariant (e.g, table-prefix_MyProcedure(), which is only executable by one user). Then, assign the execution privilege only to the corresponding user. However, this should be done automatically on request - hence, I need to be able to create a stored procedure with a parameterized name to set this up (phew). As the title suggests, this is the way I want to go - but I really struggle with the syntax of dynamically creating a stored procedure (in a stored procedure) and thus, I'm really not sure if this is how I should proceed.
Any ideas how I got approach this problem?
Regards,
raute
I have execute only access to a stored procedure.
This SP seems to select some data from multiple tables, and returns one row. I need to store two columns of the output of this SP into a table.
Is there any way to do this within MySQL?
If it returns a row, this is a stored function and not a stored procedure. You can use something like the following to insert into your table:
INSERT INTO tablename SELECT (SELECT col1, col2 FROM (SELECT somefunction()))
Otherwise, it will be a stored procedure and you should do something like this, assuming that #var1 and #var2 are output parameters:
CALL someprocedure(#var1, #var2, #var3)
INSERT INTO tablename SELECT(#var1, #var2)
See the documentation about Create Procedure and Create Function for more information about functions versus procedures.
MySQL has an extension to stored procedures that allows the procedure to return one or more result sets to the client, as if the client had issued a SELECT query... but those results are ephemeral. They don't persist and they can't be stored in variables or otherwise accessed after the procedure finishes -- they can only be "fetched" the one time.
There is a way to make them accessible without breaking the way the procedure already works, as I discussed here, but you can't do it without a change to the procedure:
How to use Table output from stored MYSQL Procedure
The idea is for the procedure to write its output in a temporary table, and then return it to the caller by calling SELECT against the temporary table -- but to leave the temporary table behind so that the caller can access it directly if desired.
That's not exactly the same as what you're asking though, which is why I didn't mark this question as a duplicate, since you, unlike the other poster, do not appear to have administrative control of the procedure... but unless you can make the case for a change like this, there's not another way within MySQL to access those returned values, since they only exist in the result-set that's returned.
Of course, procedures do have optional OUT parameters, where you can hand variables to the procedure as part of arguments you use to call it, and it can set those variables, so that they'll have the values you need when the procedure is done, but that only works when the return values are scalars and would require a change to the procedure's interface, since procs in MySQL do not have "optional" arguments... if the procedure were changed to permit this, it would require an increased number of arguments to be provided every time it was called, and if other components are calling it, that could easily break other things.
I have a procedure that creates a table, is it possible to have a view (or similar) that can call the procedure then select from the table?
I've tried this:
DELIMITER $$
CREATE DEFINER=`root`#`localhost` FUNCTION `new_routine`(p1 INT) RETURNS int(1)
BEGIN
CALL rMergeDateFields();
RETURN 1;
END
CREATE VIEW `db`.`vIntervals` AS
SELECT new_routine(0) AS col1;
SELECT * FROM MergedData;
but I get this error
Error 1422: Explicit or implicit commit is not allowed in stored function or trigger.
Any ideas?
No, you cannot. Views are typically read-only operations; and that behavior cannot be guaranteed if stored-procedures are invoked.
Related question:
How to call Stored Procedure in a View?
Is it possible to call stored procedure in view?
Here is a canonical resource:
http://dev.mysql.com/doc/refman/5.1/en/view-updatability.html
Some views are updatable. That is, you can use them in statements such as UPDATE, DELETE, or INSERT to update the contents of the underlying table. For a view to be updatable, there must be a one-to-one relationship between the rows in the view and the rows in the underlying table. There are also certain other constructs that make a view nonupdatable.
As invoking the stored procedure cannot assure 1:1 relationships with view rows, the update is not permitted.
You can't do this from a view, but a stored procedure itself can return a result set.
DELIMITER $$
CREATE DEFINER=`root`#`localhost` PROCEDURE `merge_and_select` ()
BEGIN
CALL rMergeDateFields();
SELECT * FROM MergeData;
END $$
If you CALL merge_and_select() the rMergeDateFields procedure will be run and then you will get the contents of the MergeData table returned to you, in one step... which sounds like what you're wanting.
This is, however, a really bad implementation, because there's no control for concurrent calls to merge_and_select(), so all kinds of things could go wrong if it's run more than once at the same time.
However, depending on what you really need rMergeDateFields() to do, it's possible that you could rewrite rMergeDateFields() to actually perform whatever work you need done and return it directly to the client without using the MergeData table using an unbounded SELECT, as shown above.
Anything you SELECT in a stored procedure without using INTO a variable is returned to the client as a response from the CALL.
When inserting a new row in a table T, I would like to check if the table is larger than a certain threshold, and if it is the case delete the oldest record (creating some kind of FIFO in the end).
I thought I could simply make a trigger, but apparently MySQL doesn't allow the modification of the table on which we are actually inserting :
Code: 1442 Msg: Can't update table 'amoreAgentTST01' in stored function/trigger because it is already used by statement which invoked this stored function/trigger.
Here is the trigger I tried :
Delimiter $$
CREATE TRIGGER test
AFTER INSERT ON amoreAgentTST01
FOR EACH ROW
BEGIN
DECLARE table_size INTEGER;
DECLARE new_row_size INTEGER;
DECLARE threshold INTEGER;
DECLARE max_update_time TIMESTAMP;
SELECT SUM(OCTET_LENGTH(data)) INTO table_size FROM amoreAgentTST01;
SELECT OCTET_LENGTH(NEW.data) INTO new_row_size;
SELECT 500000 INTO threshold;
select max(updatetime) INTO max_update_time from amoreAgentTST01;
IF (table_size+new_row_size) > threshold THEN
DELETE FROM amoreAgentTST01 WHERE max_update_time = updatetime; -- and check if not current
END IF;
END$$
delimiter ;
Do you have any idea on how to do this within the database ?
Or it is clearly something to be done in my program ?
Ideally you should have a dedicated archive strategy in a separate process that runs at off-peak times.
You could implement this either as a scheduled stored procedure (yuck) or an additional background worker thread within your application server, or a totally separate application service. This would be a good place to put other regular housekeeping jobs.
This has a few benefits. Apart from avoiding the trigger issue you're seeing, you should consider the performance implications of anything happening in a trigger. If you do many inserts, that trigger will do that work and effectively half the performance, not to mention the lock contention that will arise as other processes try to access the same table.
A separate process that does housekeeping work minimises lock contention, and allows the work to be carried out as a high-performance bulk operation, in a transaction.
One last thing - you should possibly consider archiving records to another table or database, rather than deleting them.