MySQL stored procedure - insert multiple rows - mysql

My application is logging the details of every http request in several MySQL tables via a stored procedure which is returning a unique request id to the application.
CALL http_req('ip', 'url', 'method', 'timestamp', #error, #request_id);
Now I also want to log all http request headers into a table, each header in a seperate row:
CREATE TABLE `http_header` (
`id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
`request_id` INT(10) UNSIGNED NOT NULL,
`name` VARCHAR(255) NOT NULL COLLATE 'utf8_unicode_ci',
`value` VARCHAR(255) NOT NULL COLLATE 'utf8_unicode_ci',
PRIMARY KEY (`id`))
The problem is that each client has a different number and types of headers. I have not found a way to pass all header details to my stored procedure and then insert them into the above table.
Currently I have to generate and execute a second insert query from my application after the stored procedure call to save the headers:
INSERT INTO http_header (request_id, name, value)
VALUES (20153, 'cache-control', 'max-age=0'),
(20153, 'accept', 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8'),
(20153, 'accept-encoding', 'gzip,deflate,sdch');
Is is possible save the second query and insert the headers from inside the stored procedure? Like passing all headers as a single string and parse it inside the stored procedure?

Yes, it is possible. MySQL does support sufficient flow control structures (REPEAT, IF) and string processing (LOCATE(), SUBSTRING()) to allow splitting the header strings in the database. Here is a very simplistic example:
CREATE PROCEDURE http_req(IN ip CHAR(12), IN url VARCHAR(512), IN method CHAR(8), IN ts DATETIME, IN headers TEXT, OUT err INT, OUT request_id INT)
BEGIN
DECLARE loc INT;
DECLARE hloc INT;
DECLARE hdr TEXT DEFAULT NULL;
DECLARE hval TEXT DEFAULT NULL;
DECLARE s TEXT DEFAULT NULL;
START TRANSACTION;
INSERT INTO http_requests VALUES (NULL,INET_ATON(ip), url, method, ts);
SELECT last_insert_id() INTO request_id;
REPEAT
SET loc=LOCATE("\n",headers);
IF (loc = 0) THEN
SET s=headers;
ELSE
SET s=SUBSTRING(headers,1,loc-1);
SET headers=SUBSTRING(headers,loc+1);
END IF;
SET hloc=LOCATE(':',s);
IF (hloc = 0) THEN
SET hdr=s;
SET hval='';
ELSE
SET hdr=SUBSTRING(s,1,hloc-1);
SET hval=SUBSTRING(s,hloc+1);
END IF;
INSERT INTO http_header VALUES (null,request_id,hdr,hval);
UNTIL (loc=0) END REPEAT;
COMMIT;
END
There are some obvious problems with this code; like your headers will be stored incorrectly if they contain linefeed (\n). Also there is no error management (e.g. err return value is not populated correctly; no rollback). Fixing these has been left as an exercise to the reader ;)

Related

Why won't my SQL passwordhashing-procedure run?

I am trying to create a SQL procedure that hashes password inputs. This code won't run and I am not getting any useful response errors.
The first part creates the table, the second creates the procedure. When I call on my procedure in the third part it send the values into the procedure. There the password is supposed to be hashed using SHA2_512 and inserted into the table we made eralier.
I used online research to make this code, the parts I don't get is:
The N before my values
The SetNoCount
The #responsemessage
-- makes Admin table
CREATE TABLE IF NOT EXISTS `AdminUser` (
`AdminID` smallint(6) NOT NULL AUTO_INCREMENT,
`Username` char(15) NOT NULL,
`PasswordHash` BINARY(64) NOT NULL,
`Fornavn` char(30) NOT NULL,
`Etternavn` char(40) NOT NULL,
`Email` char(40) NOT NULL,
PRIMARY KEY (`AdminID`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- Makes hashing procedure
CREATE OR REPLACE PROCEDURE vm_ski.addAdmin
#pUsername NVARCHAR(50),
#pPassword NVARCHAR(50),
#pFornavn NVARCHAR(30),
#pEtternavn NVARCHAR(40),
#pEmail NVARCHAR(40),
#responseMessage NVARCHAR(250)='' OUTPUT
AS
BEGIN
SET NOCOUNT ON
BEGIN TRY
INSERT INTO vm_ski.AdminUser (Username, PasswordHash, Fornavn, Etternavn, Email)
VALUES(#pUsername, HASHBYTES('SHA2_512', #pPassword), #pFornavn, #pEtternavn, #pEmail)
SET #responseMessage='Success'
END TRY
BEGIN CATCH
SET #responseMessage=ERROR_MESSAGE()
END CATCH
END;
-- Admin example
DECLARE #responseMessage NVARCHAR(250)
EXECUTE vm_ski.addAdmin
#pUsername = N'sondre',
#pPassword = N'example'
#pFornavn = N'Sondre'
#pEtternavn = N'Morgendal'
#pEmail = N'sondre.example#gmail.com'
;
This is not a direct answer to the question; this is a security note on the methodology of the question
Do NOT hash passwords in MySQL. The data given to MySQL is plaintext, and easily intercepted by MySQL processing logs as well as possibly numerous other places before being dumped in the database (such as if message packets sent to the database are non-localhost and are non-TLS). ( Why? )
When hashing passwords you want to be doing so as early in the process as possible. This typically means using PHP password_hash and simply dumping only the hashed data in the MySQL.
If you do not use PHP to interact with your SQL then you can use other server methods such as Argon2 or Libsodium.
Also as a side point you should be using the mb4 UTF-8 charset and collations - principly utf8mb4_general_ci ( Why? )

MySQL Stored procedure for updating two tables (2nd with auto increment value from 1st)

I want to update two tables at the same time in my database. One table is for groups, and the other table is for members of groups:
CREATE TABLE IF NOT EXISTS groups (
group_id INTEGER UNSIGNED AUTO_INCREMENT,
group_name VARCHAR(150) NOT NULL DEFAULT '',
group_created TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (group_id)
) ENGINE=InnoDB DEFAULT CHARACTER SET utf8;
CREATE TABLE IF NOT EXISTS group_members (
group_mem_user_id INTEGER UNSIGNED NOT NULL,
group_mem_group_id INTEGER UNSIGNED NOT NULL,
group_mem_role TINYINT DEFAULT 1,
group_mem_created TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT group_mem_pk PRIMARY KEY (group_mem_user_id, group_mem_group_id),
FOREIGN KEY (group_mem_user_id) REFERENCES user (user_id),
FOREIGN KEY (group_mem_group_id) REFERENCES groups (group_id)
) ENGINE=InnoDB DEFAULT CHARACTER SET utf8;
I want to use a stored procedure to create an entry in group and create an entry in group_members with the id that was just created for the group.
I know how to do this on the server (I have a java server and I'm using Spring's JdbcTemplate to make calls to the database) but I thought it would be better and more efficient to do this in a stored procedure.
The two individual queries are (im using prepared statements):
INSERT INTO groups (group_name) VALUES (?)
and
INSERT INTO group_members (group_mem_user_id, group_mem_group_id, group_mem_role) VALUES (?,?,?)
But I'm not sure how to merge these into one stored procedure.
DELIMITER //
DROP PROCEDURE IF EXISTS create_group //
CREATE PROCEDURE create_group(
#in/out here
)
BEGIN
#no idea
END //
DELIMITER ;
Ideally I would like it to return some value describing whether the operation was sucessful or not.
I use the following procedure:
DELIMITER //
DROP PROCEDURE IF EXISTS create_group //
CREATE PROCEDURE create_group(
IN create_group_group_name VARCHAR(150),
IN create_group_user_id INT
)
BEGIN
DECLARE EXIT HANDLER FOR SQLEXCEPTION, SQLWARNING
BEGIN
ROLLBACK;
END;
START TRANSACTION;
INSERT INTO groups (group_name) VALUES (create_group_group_name);
INSERT INTO group_members (group_mem_user_id, group_mem_group_id, group_mem_role) VALUES (create_group_user_id, LAST_INSERT_ID(), 2);
COMMIT;
END //
DELIMITER ;
In my server I use it like:
JdbcTemplate jt = new JdbcTemplate(DB.getDataSource(DB_USER));
int i = jt.update("CALL create_group (?,?)", new Object[] {groupName, userId});
if (i != 1)
throw new SQLException("Error creating group with name=" + groupName + " for userid=" + userId);
i == 1 if everything went well. Groups will never be created if the user is not added as a member (fixing the problem with my first iteration below).
OLD
(non-transactional, causes a problem if the second insert fails then the group is created with no members, it might work in some cases which is why I leave it here but it doesn't work for me)
The following procedure works. It does not return anything and I am just using the fact that the procedure completes without error to assume that it was all ok.
DELIMITER //
DROP PROCEDURE IF EXISTS create_group //
CREATE PROCEDURE create_group(
IN create_group_group_name VARCHAR(150),
IN create_group_user_id INT
)
BEGIN
INSERT INTO groups (group_name) VALUES (create_group_group_name);
INSERT INTO group_members (group_mem_user_id, group_mem_group_id, group_mem_role) VALUES (create_group_user_id, LAST_INSERT_ID(), 2);
END //
DELIMITER ;

Using stored procedure in MySQL

I have a weather database in MySQL. My database get data from arduino, but sometimes the arduino have some error and send error value in my database. I want to make a stored procedure to reject this error. I want using if then in stored procedure. Example if temperature < 20 then MySQL reject this data. Is it possible? Help me please with the coding
this is my table
CREATE TABLE `cuaca_maritim`.`weather_data` (
`idweather` INT(10) NOT NULL,
`temperature` DECIMAL(4,2) NOT NULL,
`HUMID` DECIMAL(4,2) NOT NULL,
`AIRPRESSURE` DECIMAL(6,2) NOT NULL,
`WIND` DECIMAL(4,2) NOT NULL,
PRIMARY KEY (`idweather`))
ENGINE = InnoDB
DEFAULT CHARACTER SET = utf8
COLLATE = utf8_bin;
Perhaps you just want a view:
create view good_weather_data as
select wd.*
from weather_data
where temperature >= 20;
I don't really see why a stored procedure would be necessary. You might want a trigger that rejects invalid data values when they are loaded.
I think you can use the following stored procedure to do your work.
DELIMITER #
CREATE OR REPLACE PROCEDURE add_weather_data (IN temp INT)
proc_main: BEGIN
IF (temp > 20) THEN
INSERT INTO weather_data(temperature) VALUES (temp);
END IF;
END proc_main #
DELIMITER;
Here I have only consider the temp you can do the same for other parameters also

Update value each time new rows is created

I have two tables: config(last_inserted_id) and element(id) is there any chance to get the last inserted id any time when new rows are created in element table and execute a update in column last_inserted_id at config table?
I have wrote this:
CREATE TRIGGER UPDATE_CONFIG_VALUES AFTER INSERT ON element
BEGIN
UPDATE config SET last_inserted_id = last_insert_id();
END;
END;
Is that right? What happen if I delete a row in element table? Should the value get updated in config table or not" How I avoid this?
config table
CREATE TABLE IF NOT EXISTS `cmplatform`.`isrl_config` (
`id` INT NOT NULL AUTO_INCREMENT,
`logo_address` VARCHAR(250) NOT NULL,
`name` VARCHAR(250) NOT NULL,
`rif` VARCHAR(20) NOT NULL,
`address` TEXT NOT NULL,
`phone` VARCHAR(14) NOT NULL,
`last_retention_number` INT NOT NULL,
PRIMARY KEY (`id`))
ENGINE = InnoDB
TEST this ( Not tested)
CREATE TRIGGER element_inserted_tg AFTER INSERT ON element
FOR EACH ROW
BEGIN
DECLARE count INT;
SELECT COUNT(1) INTO count FROM config;
IF count == 1
UPDATE config SET last_inserted_id = NEW.id;
ELSE
INSERT INTO config VALUES (NEW.id);
END IF;
END;
CREATE TRIGGER element_deleted_tg AFTER DELETE ON element
FOR EACH ROW
BEGIN
UPDATE config SET last_inserted_id = (SELECT MAX(id) FROM element);
END;
Trigger is traditional way to do this requirement but not unique way. Another way is process it in your data access code. Let say that you have a DAO method to create new Element, you can get the id of element created and update it into last_inserted_id. If you do this way, you have to makesure there is ONLY ONE THREAD calling insert element method at a time.

Using a check contraint in MySQL for controlling string length

I'm tumbled with a problem!
I've set up my first check constraint using MySQL, but unfortunately I'm having a problem. When inserting a row that should fail the test, the row is inserted anyway.
The structure:
CREATE TABLE user (
id INT UNSIGNED NOT NULL AUTO_INCREMENT,
uname VARCHAR(10) NOT NULL,
fname VARCHAR(50) NOT NULL,
lname VARCHAR(50) NOT NULL,
mail VARCHAR(50) NOT NULL,
PRIMARY KEY (id),
CHECK (LENGTH(fname) > 30)
);
The insert statement:
INSERT INTO user VALUES (null, 'user', 'Fname', 'Lname', 'mail#me.now');
The length of the string in the fname column should be too short, but it's inserted anyway.
I'm pretty sure I'm missing something basic here.
MySQL doesn't enforce CHECK constraints, on any engine.
Which leads me to ask, why would you declare the fname column as VARCHAR(50), but want to enforce that it can only be 30 characters long?
That said, the only alternative is to use a trigger:
CREATE TRIGGER t1 BEFORE INSERT ON user
FOR EACH ROW BEGIN
DECLARE numLength INT;
SET numLength = (SELECT LENGTH(NEW.fname));
IF (numLength > 30) THEN
SET NEW.col = 1/0;
END IF;
END;
As mentioned above you have to use a trigger, MySQL doesn't support check, also when you have multiple statements inside your trigger block, like declaring variables or control flows, you need to start it with begin and end and enclose your trigger inside two delimiters:
Note: If you use MariaDB use // after the first delimiter and before the second delimiter, otherwise if you use MySQL use $$ instead.
delimiter //
create trigger `user_insert_trigger` before insert on `user` for each row
begin
declare maximumFnameLength int unsigned;
declare fNameLength int unsigned;
set maximumFnameLength = 30;
set fNameLength = (select length(new.fNameLength));
if (fNameLength > maximumFnameLength) then
signal sqlstate '45000'
set message_text = 'First name is more than 30 characters long.';
end if;
end
//
delimiter ;