I want to create a new user with the same initial password every time I insert a row with the username in the table.
I tried but it doesn't work:
CREATE TABLE IF NOT EXISTS Professeur (
professeur_id int NOT NULL AUTO_INCREMENT,
prenom varchar(40) COLLATE utf8_bin NOT NULL,
name varchar(30) COLLATE utf8_bin NOT NULL,
titre char DEFAULT NULL,
PRIMARY KEY (professeur_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin;
CREATE trigger trig_prof AFTER INSERT ON Professeur
FOR EACH ROW CREATE OR replace USER NEW.name#localhost identified BY pwd0;
ERROR:
ER_PARSE_ERROR: 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
'USER NEW.name#localhost identified BY pwd0' at line 2
Triggers have several limitations as described in the fine manual:
"Triggers cannot operate on any tables in the mysql, information_schema or performance_schema database."
Since users are stored in mysql schema (either in global_priv or user) this can't work.
Do things a different way...
Write a Stored Procedure that both creates the table and adds the user. See SQL SECURITY DEFINER for how to temporarily give an enduser root permission. But be aware of the security implications.
Then, to add the table and the user, it is one CALL statement.
Problem solved with a procedure as Rick James has suggested and with Prepared Statements https://dev.mysql.com/doc/refman/8.0/en/sql-prepared-statements.html
I put the following code in my script and I execute the source command SOURCE initdb_gestiondesnotes.SQL
After that I call the procedure with: CALL create_professeur();
DELIMITER $$
CREATE PROCEDURE CREATE_PROFESSEUR()
BEGIN
DECLARE v_a INT Default 1 ;
DECLARE v_nom VARCHAR(42);
DECLARE v_prenom VARCHAR(42);
DECLARE v_login VARCHAR(84);
DECLARE v_titre VARCHAR(5);
DECLARE v_pwd VARCHAR(5);
SET v_pwd = 'p';
simple_loop: LOOP
SET v_nom = CONCAT("prof", LPAD(CAST(v_a AS CHAR), 3, '0'));
SET v_prenom = CONCAT("prenom", LPAD(CAST(v_a AS CHAR), 3, '0'));
SET v_titre = CASE WHEN RAND() > .5
THEN 'M'
ELSE 'F' END;
INSERT INTO Professeur (prenom, nom, titre) VALUES (v_prenom, v_nom, v_titre);
SET v_login = CONCAT(v_prenom, v_nom);
SET #sql1 = CONCAT('CREATE OR REPLACE USER ', v_login, '#localhost identified BY \'p\' ');
PREPARE stm1 FROM #sql1;
EXECUTE stm1;
SET #sql2 = CONCAT('GRANT role_professeur TO ', v_login, '#localhost');
PREPARE stm2 FROM #sql2;
EXECUTE stm2;
SET #sql3 = CONCAT('SET DEFAULT ROLE role_professeur FOR ', v_login, '#localhost');
PREPARE stm3 FROM #sql3;
EXECUTE stm3;
SET v_a=v_a+1;
IF v_a=51 THEN
LEAVE simple_loop;
END IF;
END LOOP simple_loop;
DEALLOCATE PREPARE stm1;
DEALLOCATE PREPARE stm2;
DEALLOCATE PREPARE stm3;
END $$
DELIMITER ;
Now I hove 50 user with prenom001prof001, prenom002prof002... as login and 'p' as password. Again with prepared statements I gave each user the role of role_professeur.
Related
CREATE DEFINER = 'root'#'localhost'
PROCEDURE client_logging_system.Proc_client_Delete(IN in_clientID int)
COMMENT '
-- Parameter:
-- in_clientID: ID of client
'
BEGIN
DECLARE exit handler for sqlexception
BEGIN
ROLLBACK;
end;
START TRANSACTION;
DELETE FROM `client` WHERE `client`.ID = in_clientID;
ALTER TABLE `client` AUTO_INCREMENT = in_clientID;
COMMIT;
END
My proceduce get error on line:
ALTER TABLE `client` AUTO_INCREMENT = in_clientID;
Any suggestion for this problem?
You can't use variables in ALTER statements, it needs a literal number there. You'll need to create dynamic SQL using PREPARE.
SET #st = CONCAT('ALTER TABLE `client` AUTO_INCREMENT = ', in_clientID);
PREPARE stmt FROM #st;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
You can only use limited set of DDL statements inside a routine.
What are you trying to achive with the ALTER TABLE-statement? If you delete a client with id that is not the biggest one, the ALTER TABLE would not make sense.
I have a Mysql table as follows:
CREATE TABLE `login_info` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`user_id` varchar(50) NOT NULL,
`device_id` varchar(50) NOT NULL,
`timestamp` bigint(20) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `login_info_uniq01` (`user_id`, `device_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
The table is empty.
The extend_login_timestamp_using_concat procedure is as follows:
DROP PROCEDURE IF EXISTS `extend_login_timestamp_using_concat` ;;
CREATE PROCEDURE `extend_login_timestamp_using_concat`()
BEGIN
DECLARE done INT DEFAULT 0;
DECLARE login_info_id BIGINT;
DECLARE cur CURSOR FOR SELECT `id` FROM `login_info` WHERE `device_id` = 'WEB';
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1;
SET #extended_timestamp = (SELECT UNIX_TIMESTAMP(DATE_FORMAT(NOW() + INTERVAL 365 DAY,'%Y-%m-01 23:59:59')) * 1000);
SET #id_list = NULL;
SET #id_count = 0;
OPEN cur;
REPEAT FETCH cur INTO login_info_id;
SET #id_list = CONCAT_WS (',', #id_list, login_info_id);
SET #id_count = #id_count + 1;
IF (#id_count = 2) THEN
SET #update_query = CONCAT('UPDATE `login_info` SET `timestamp` = ', #extended_timestamp, ' WHERE `id` in (', #id_list, ')');
PREPARE extend_expire_statement FROM #update_query;
EXECUTE extend_expire_statement;
DEALLOCATE PREPARE extend_expire_statement;
SET #id_list = NULL;
SET #id_count = 0;
END IF;
UNTIL done END REPEAT;
CLOSE cur;
IF (#id_list IS NOT NULL) THEN
SET #update_query = CONCAT('UPDATE `login_info` SET `timestamp` = ', #extended_timestamp, ' WHERE `id` in (', #id_list, ')');
PREPARE extend_expire_statement FROM #update_query;
EXECUTE extend_expire_statement;
DEALLOCATE PREPARE extend_expire_statement;
END IF;
END ;;
DELIMITER ;
Since there is no data in the table, so cursor would fetch 0 rows, and when I run the procedure it return error as follows:
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 ')' at line 1
But when I insert one or more than one rows which matches the cursor criteria it works fine.
How can I prevent the error when cursor doesn't fetch any row?
Any help will be highly appreciated. Thanks in advance.
The problem is simple.
When the table is empty then your FETCH causes NOT FOUND event which fires the CONTINUE handler. The handler sets the variable and returns. Then the rest of the cycle code is executed - and your code prepares incorrect SQL code which causes the error in your EXECUTE. See it in #update_query variable immediately after the SP call falling.
Use not REPEAT but DO cycle. And put IF statement which checks the variable value and leaves the cycle if it is set immediately after FETCH statement.
fiddle (pay attention - I add comment marks in your SQL lines, so you can see what statement produces errorneous SQL).
PS. Do not mix local variables (done, login_info_id) and user-defined variables (#extended_timestamp, #id_list, #id_count) usage. In SP the local variables usage is preferred.
I'm creating a Twitter clone using MySQL and I'm writing a stored procedure to update userfeed table of a user who just followed someone new to include all the tweets of the person whom the user has followed.
CREATE PROCEDURE `populate_userfeed_after_following`(IN username varchar(20), IN following_username varchar(20))
BEGIN
DECLARE last_tweet timestamp default null;
DECLARE following_id int;
SELECT id from users where username=following_username into following_id;
SET #timestamp_tweet = CONCAT('SELECT timestamp FROM userfeed_', username, ' ORDER BY timestamp DESC LIMIT 1 into', last_tweet);
PREPARE timestamp_tweetQuery FROM #timestamp_tweet;
EXECUTE timestamp_tweetQuery;
IF (last_tweet is NULL) THEN
SET #insert_statement = CONCAT('INSERT INTO userfeed_', username, '(tweet_id) SELECT id from tweets
WHERE author=', following_id,
' LIMIT 200');
ELSE
SET #insert_statement = CONCAT('INSERT INTO userfeed_', username, '(tweet_id) SELECT id from tweets
WHERE author=', following_id ,'and timestamp >', last_tweet);
END IF;
PREPARE insertQuery FROM #insert_statement;
EXECUTE insertQuery;
DEALLOCATE PREPARE timestamp_tweetQuery;
DEALLOCATE PREPARE insertQuery;
END
I'm storing the most out-of-date timestamp in a local variable called last_tweet after retrieving it from the userfeed table of the user who followed someone and which I'm checking to be NULL to create a dynamic insert statement.
Whenever I call the procedure by passing the parameters, e.g.,
call populate_userfeed_after_following('joe', 'rocky');
#joe follows rocky
I get an error,
Error Code: 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 'NULL' at line 1
I would appreciate if you point out the mistake in my code.
As #P.Salmon pointed out, use of local variables (using DECLARE statement) would not work in this situation. So I corrected the code to use session variables or user-defined variables (using SET statement) and rewrote it.
The following logic works:
CREATE PROCEDURE `populate_userfeed_after_following`(IN username varchar(20), IN following_username varchar(20))
BEGIN
-- DECLARE last_tweet timestamp default null;
-- DECLARE following_id int;
SET #following_id = 0;
SET #last_tweet = NULL;
SET #followingId_tweet = CONCAT('SELECT id from users where username=\'', following_username, '\' into #following_id');
SET #timestamp_tweet = CONCAT('SELECT timestamp FROM userfeed_', username, ' ORDER BY timestamp DESC LIMIT 1 into #last_tweet');
PREPARE followingIdQuery FROM #followingId_tweet;
EXECUTE followingIdQuery;
DEALLOCATE PREPARE followingIdQuery;
PREPARE timestamp_tweetQuery FROM #timestamp_tweet;
EXECUTE timestamp_tweetQuery;
DEALLOCATE PREPARE timestamp_tweetQuery;
IF (#last_tweet is NULL) THEN
SET #insert_statement = CONCAT('INSERT INTO userfeed_', username, '(tweet_id) SELECT id from tweets
WHERE author=#following_id
LIMIT 200');
ELSE
SET #insert_statement = CONCAT('INSERT INTO userfeed_', username, '(tweet_id) SELECT id from tweets
WHERE author=#following_id and timestamp > #last_tweet');
END IF;
PREPARE insertQuery FROM #insert_statement;
EXECUTE insertQuery;
DEALLOCATE PREPARE insertQuery;
END
The initial error was due to the select statement where I was using the value passed in the parameter to create a dynamic SQL statement and the line below that where SET #timestamp_tweet was used. The correct way to do that was to use prepared statements instead.
For some strange reason, inserting from stored procedure is not working.
This is what Im trying to do:
DROP TABLE IF EXISTS test;
CREATE TABLE IF NOT EXISTS test(
id INT(9) NOT NULL AUTO_INCREMENT
,name VARCHAR(30) NOT NULL
,PRIMARY KEY (id)
) DEFAULT CHARSET=utf8;
insert into test (name) values('A');
Inserting from command line works with no problems.
Then I created a stored procedure to do the same kind of insert:
DROP PROCEDURE IF EXISTS storedtest;
DELIMITER $$
CREATE PROCEDURE storedtest()
BEGIN
declare insert_sql varchar(200);
SET insert_sql = 'insert into test (name) values(3)';
SELECT insert_sql;
PREPARE mystm FROM #insert_sql;
EXECUTE mystm;
END$$
DELIMITER ;
call storedtest();
This gives me the error:
ERROR 1064 (42000): You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near 'NULL' at line 1
NULL? Where did NULL came from?
I also tried changing the sql-insert to look like this (dont know if it is a good way):
SET insert_sql = "insert into test (name) values('3')";
But mysql gives me exactly the same error.
Anyone has a clue?
The NULL MySQL is reporting is an empty user variable #insert_sql, which is different from the local stored procedure local variable insert_sql which you allocated with DECLARE.
MySQL's DECLARE is used for variables local to a stored program, but according to the documentation, PREPARE stmt FROM ... expects either a string literal or a user variable, which are the type preceded with #.
PREPARE stmt_name FROM preparable_stmt
preparable_stmt is either a string literal or a user variable that contains the text of the SQL statement.
You can allocate the untyped user variable with SET so there is no need for DECLARE. You may wish to set it to NULL when you're finished.
DROP PROCEDURE IF EXISTS storedtest;
DELIMITER $$
CREATE PROCEDURE storedtest()
BEGIN
-- Just SET the user variable
SET #insert_sql = 'insert into test (name) VALUES (3)';
SELECT #insert_sql;
-- Prepare & execute
PREPARE mystm FROM #insert_sql;
EXECUTE mystm;
-- Deallocate the statement and set the var to NULL
DEALLOCATE PREPARE mystm;
SET #insert_sql = NULL;
END$$
DELIMITER ;
The Stored Procedure
DELIMITER $$
CREATE PROCEDURE `lms`.`leads_to_bak` ()
BEGIN
SET #table1 = (SELECT `tabler_name` FROM `sets` WHERE `on_off`=0 LIMIT 1);
SET #table2 = CONCAT(#table1, '_bak');
SET #SQL1 = CONCAT('INSERT INTO ',#table2, '(', (SELECT REPLACE(GROUP_CONCAT(COLUMN_NAME), 'lead_id,', '') FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = #table2), ')', ' SELECT ', (SELECT REPLACE(GROUP_CONCAT(COLUMN_NAME), 'lead_id,', '') FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = #table1), ' FROM ', #table1);
PREPARE stmt FROM #sql1;
EXECUTE stmt;
END$$
DELIMITER ;
The Trigger
DELIMITER $$
USE `lms`$$
CREATE TRIGGER `lms`.`after_insert_into_leads`
AFTER INSERT ON `sets` FOR EACH ROW
BEGIN
CALL lms.leads_to_bak();
END$$
DELIMITER ;
The problem
I get a Error Code: 1336. Dynamic SQL is not allowed in stored function or trigger error message when making an INSERT which by implication would execute the trigger and the stored procedure. I am assuming the problem is the Dynamic SQL here:
PREPARE stmt FROM #sql1;
EXECUTE stmt;
I've looked around and there is a thread on stackoverflow on the problem, but no answer. Does anyone have any suggestions for a plausible workaround?
There is no good workaround for the absense of Dynamic SQL in MySQL functions, just klunky cludges. Some things still remain downright impossible to cludge, such as using a dynamically-calculated field name or table name in a SQL query. Yes, once in a while there is a need for doing this sort of thing!
And don't try cheat by putting the Dynamic SQL in a stored procedure and wrapping in a function or trigger, as the question poser tried - MySQL is too clever and will give you the usual obscure error message. Believe me, I have been around all the houses.
Coming from an Oracle PL/SQL and MS SQL Server background, I sorely miss the richness that PL/SQL and (to a small extent) T-SQL offers for writing procedural SQL.
Within the procedure definition, you need to store all your IN/OUT variables.
Change:
CREATE PROCEDURE `lms`.`leads_to_bak` ()
to:
CREATE PROCEDURE `lms`.`leads_to_bak` (
IN table1 varchar(32),
IN table2 varchar(32),
)
Then call doing this:
CALL `lms`.`leads_to_bak`('table1', 'table2')
replacing the strings with your own.
The purpose of using stored procedures is to prevent SQL injection using strictly typed data. You don't technically need to prepare it in the stored procedure if you ONLY send strictly typed input variables in the parameter list.
This way, you handle the string operations prior to the stored procedure call. Keep your stored procs skinny!
Here's an example of one of my stored procedures:
DELIMITER ;
DROP PROCEDURE IF EXISTS `save_player`;
DELIMITER //
CREATE PROCEDURE `save_player` (
IN uid int(15) UNSIGNED,
IN email varchar(100),
IN name varchar(100),
IN passwd char(96),
IN state ENUM('active','suspended','deleted'),
IN user_role ENUM('gamemaster','moderator','player'),
IN locale ENUM('en','fr'),
IN lvl tinyint(1),
IN hp bigint(20),
IN reborn tinyint(1),
IN cross_ref varchar(12),
IN email_verified tinyint(1),
OUT new_id int(15) UNSIGNED
)
BEGIN
DECLARE date_deleted timestamp DEFAULT NULL;
IF uid > 0 AND EXISTS (SELECT id FROM user WHERE `id`= uid) THEN
IF state = 'deleted' THEN
SET date_deleted = CURRENT_TIMESTAMP;
END IF ;
UPDATE `user` SET
`email` = email,
`name` = name,
`passwd` = passwd,
`state` = state,
`user_role` = user_role,
`locale` = locale,
`lvl` = lvl,
`hp` = hp,
`reborn` = reborn,
`cross_ref` = cross_ref,
`email_verified` = email_verified,
`date_deleted` = date_deleted
WHERE `id` = uid;
SET new_id = uid;
ELSE
INSERT INTO user (`email`, `name`, `passwd`, `state`, `user_role`, `locale`, `lvl`, `hp`, `reborn`, `cross_ref`, `email_verified`, `date_created`)
VALUES (email, name, passwd, state, user_role, locale, lvl, hp, reborn, cross_ref, email_verified, NOW());
SELECT LAST_INSERT_ID() INTO new_id;
END IF;
END //
DELIMITER ;