Configure MySQL Stored Procedure with only INT variable - php

I have configured this procedure:
DELIMITER //
CREATE PROCEDURE get_news
(IN news_id INT)
BEGIN
SELECT title, body, datetime FROM News
WHERE id = news_id;
END //
DELIMITER ;
And I have this PHP Code:
$news_id = $_GET["id"];
$dbConnection = new mysqli("localhost","root","","identidad_digital_db");
$sql = "CALL get_news($news_id)";
$result = $dbConnection->query($sql);
$count = $result->num_rows;
if($count==0){
header( 'Location: id_not_found.html' );
}
$m = $result->fetch_assoc()
I thought that my procedure will fix SQL Injection. However, vulnerability still working. "id" must be only an integer. By this way, the basic "7' and '1'='2" injection works.
Why it does not works? How can I configure correctly my procedure? I read the documentation and some examples but I don't know how to do it.
UPDATE
I'am triying with regex but It also does not works:
BEGIN
IF news_id IS NOT NULL AND news_id RLIKE '^[0-9]+$'
THEN
SELECT title, body, datetime FROM News
WHERE id = news_id;
END IF;
END //

You can use CAST for this:
IF CAST(news_id AS UNSIGNED) > 0 THEN
...
END IF;
However, I think you should do this in php:
if(isset($news_id) && is_numeric($news_id)){
...
}

SOLVED!
I configure news_id as varchar(20). Obviously, in my mind these values were int types but they come from a PHP form, so they are strings. By the way, if it helps someone, this is what I got finally:
DELIMITER //
DROP PROCEDURE IF EXISTS get_news //
CREATE PROCEDURE get_news
(IN news_id VARCHAR(20))
BEGIN
IF news_id IS NOT NULL AND news_id REGEXP '^[1-9]+$' THEN
SELECT id, title, body, datetime FROM News
WHERE id = news_id;
END IF;
END //
DELIMITER ;

Related

Which UPDATE statement to change my TIMESTAMP only when i change my char to '1'? PHP, mysql

This is my table in mysql db:
CREATE TABLE admins (
id int NOT NULL AUTO_INCREMENT,
bez varchar(55) DEFAULT 'unknown',
type varchar(7) NOT NULL DEFAULT 'unknown',
exist char NOT NULL DEFAULT '0',
tm TIMESTAMP default CURRENT_TIMESTAMP;
PRIMARY KEY ('id')
);
I want to update my timestamp only when i set exist to '1'.
Not when I change any column.
This is my UPDATE statement:
<?php
if ( isset($_GET["id"]) ) {
$id = $_GET["id"];
$sql = "UPDATE admins SET exist='1' WHERE id=$id";
$con->query($sql);
}
header("location: /index.php");
exit;
?>
I tried different ways to combine it with tm but it didnt worked!
I tried stuff like this:
if ( isset($_GET["id"]) ) {
$id = $_GET["id"];
$tm = date('Y-m-d H:i:s');
$sql = "UPDATE admins SET exist='0', tm=$tm WHERE id=$id";
}
using AND didnt worked.
I tried ON UPDATE CURRENT_TIMESTAMP in sql but it didnt works for me. Because i want only new tm if exist is '1'.
I tried this in sql:
DELIMITER $$
CREATE TRIGGER trigger_name
AFTER UPDATE
ON admins FOR EACH ROW
BEGIN
IF NEW.exists = 1 THEN
SET NEW.tm = NOW();
END IF;
END$$
DELIMITER ;
but i get a sql error message:
ERROR 1362 (HY000): Updating of NEW row is not allowed in after trigger
so i tried to use BEFORE instead of AFTER but i didnt changed the tm at all.
Do you have any ideas?
What can i do to solve my issue?
Goal: UPDATE tm when setting exist to '1'
dont tell me that i am open to sql injections!
Thanks for your help!
Looks like a trigger will solve this for you.
If we make it a BEFORE UPDATE which is allowed to update the NEW values where an AFTER UPDATE trigger is not, as per your error message, and remove a couple of syntax errors, the trigger might look like this
DELIMITER $$
CREATE TRIGGER upd_date_if_exists_is_one
BEFORE UPDATE ON admins FOR EACH ROW
BEGIN
IF NEW.`exist` = 1 AND old.`exist` <> 1 THEN
SET NEW.tm = NOW();
END IF;
END$$
DELIMITER ;
If you leave the tm TIMESTAMP default CURRENT_TIMESTAMP; so that will set the tm column when the initial INSERT is done, but not update the tm when any subsequent updates are done.
UPDATE: Added PDO to account for SQL Injection Attacks.
Change your AFTER UPDATE trigger above to BEFORE UPDATE:
DELIMITER $$
CREATE TRIGGER TR_admins_BU
BEFORE UPDATE
ON admins FOR EACH ROW
BEGIN
IF NEW.exist = '1' THEN
SET NEW.tm = CURRENT_TIMESTAMP;
END IF;
END$$
DELIMITER ;
Declare a constant for exist so you can change it when you want. UPDATE your query, passing in your exist value by your id:
<?php
if (isset($_GET["id"]) ) {
$id = $_GET["id"];
$exist = '1'; //change this to '0' if you do not want to update the timestamp
try {
$conn = new PDO('mysql:host='.HOST.';dbname='.DATABASE,USERNAME,PASSWORD);
$sql = "UPDATE `admins` SET `exist` = :exist WHERE `id` = :id";
$statement = $conn->prepare($sql);
$statement->bindValue(":exist", $exist);
$statement->bindValue(":id", $id);
$statement->execute();
$conn = null; //Disconnect Connection
} catch(PDOException $e) {
echo $e->getMessage();
}
header("location: /index.php");
exit;
?>
See Fiddle.

MySQL generate url using already assigned ID

I'm trying to generate URL in SQL database using already assigned auto_incremented ID
When a new form is submitted then SQL automatically generates and unique lens_id for me. How can I automatically add it there in this lens_url? (Picture below)
You can do this by creating a trigger on your table. Trigger is as follows,
DELIMITER $$
CREATE TRIGGER `UpdateLensURL` BEFORE INSERT ON `your_table_name`
FOR EACH ROW BEGIN
SET NEW.lens_url= CONCAT('localhost:8888/lensview/post.php?id=', (
SELECT AUTO_INCREMENT
FROM information_schema.TABLES
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME = 'your_table_name'
));
END;
$$
DELIMITER ;
There are two options here:
1) Use a stored procedure (which will require code changes for anywhere that inserts rows)
2) Use a trigger and insert as normal - something like below should do the trick:
DELIMITER //
CREATE TRIGGER my_awesome_trigger
BEFORE INSERT
ON your_table_name
FOR EACH ROW
BEGIN
DECLARE next_id int default 0;
SELECT auto_increment INTO next_id
FROM information_schema.tables
WHERE table_name = 'your_table_name'
AND table_schema = DATABASE();
SET NEW.lens_url = CONCAT('localhost:8888/.../', next_id);
END; //
DELIMITER ;
Try something like this
$qry1="insert into tablename(lens_name,lens_url,lens_category,lens_author,lens_discription,lens_repert)values('A','B','C','D','E','F')";// your query to insert data to table
mysqli_query($con,$qry1); // run query
$last_insertid=mysqli_insert_id($con); // save last insetred ID
$url="localhost:8888/lenseview/post.php?id=".$last_insertid; //prepare url
$qry2="upadte tablename set lens_url=$url where lens_id=$last_insertid"; //update query
mysqli_query($con,$qry2); // run your query to update url
I think easiest option would be this
$Last_Lens_IdQ = mysqli_query($conn, "SELECT `lens_id` FROM `table` ORDER BY `lens_id` DESC LIMIT 1");
$Last_LensId = mysqli_fetch_array($Last_Lens_IdQ);
$x = $Last_Lens[0]++;
$LensUrl = "localhost:8888/lenseview/post.php?id=".$x;
Then insert the field and use $LensUrl When inserting the lens url column

Migration script in MySQL

I want to do a Migration data from one table to another.
I wrote a simple PHP script for my purposes but I wanted to do this by MySql script using user defined variables:
the PHP script looks like this:
//MIGRATION
$sql = "SELECT position FROM ts_user_config WHERE position != '' AND position NOT REGEXP '^-?[0-9]+$' GROUP BY TRIM(position) ORDER BY position";
$positions = db_loadColumn( $sql );
foreach ($positions as $key => $pos) {
$sql = "SELECT id FROM user_positions where UPPER(position) = UPPER('$pos')";
$posId = db_loadResult($sql);
if ($posId == null) {
$sql = "INSERT INTO user_positions (position, `desc`) VALUES ('$pos', '$pos')";
db_exec($sql);
$posId = db_insert_id();
}
$sql = "UPDATE ts_user_config SET position='$posId' WHERE TRIM(position)='$pos'";
db_exec($sql);
}
//---------
Could somebody be so kind and rewrite this PHP instructions to MySQL script? I tried to do this but my mySQL knowledge is very low and I couldn't done that.
Please help me if its not too much effort.
Thank you in advance.
I Have DONE IT !!!!!! :)
this is my mySQL script, I dont know if its perfect but its do what I need. Please tell me if I can make something better here. Thanks again :)
drop procedure if exists PositionMigration;
delimiter '//'
CREATE PROCEDURE PositionMigration()
BEGIN
BLOCK1: BEGIN
DECLARE done INT DEFAULT FALSE;
DECLARE pos VARCHAR(100);
DECLARE posId1 INT;
DECLARE posId2 INT;
DECLARE sql1 CURSOR FOR SELECT position FROM ts_user_config WHERE position != '' AND position NOT REGEXP '^-?[0-9]+$' GROUP BY TRIM(position) ORDER BY position;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;
OPEN sql1;
read_loop: LOOP
FETCH sql1 INTO pos;
IF done THEN
LEAVE read_loop;
END IF;
BLOCK2: BEGIN
DECLARE posNotFound INT DEFAULT FALSE;
DECLARE sql2 CURSOR FOR SELECT id FROM user_positions where UPPER(position) = UPPER(pos);
DECLARE CONTINUE HANDLER FOR NOT FOUND SET posNotFound = TRUE;
OPEN sql2;
FETCH sql2 INTO posId1;
IF posNotFound THEN
INSERT INTO user_positions (position, \`desc\`) VALUES (pos, pos);
BLOCK3: BEGIN
DECLARE sql3 CURSOR FOR SELECT LAST_INSERT_ID();
OPEN sql3;
FETCH sql3 INTO posId2;
UPDATE ts_user_config SET position=posId2 WHERE TRIM(position)=pos;
CLOSE sql3;
END BLOCK3;
ELSE
UPDATE ts_user_config SET position=posId1 WHERE TRIM(position)=pos;
END IF;
CLOSE sql2;
END BLOCK2;
END LOOP;
CLOSE sql1;
END BLOCK1;
END;
//
delimiter ';'
call PositionMigration();

After SP Insert, next page have an empty result until reloaded

I have a Stored Procedure (SP from now on) that inserts data to the database (SaveClient, see below). When the SP is done I redirect the PHP page to a different PHP page that lists the entries (FetchObjectList, see below). The list does not return the newly created record until I then reload/refresh the page.
The stored procedure has a COMMIT at the end, I close the database connection in the PHP code after the SP is called and there is a check for errors but nothing goes wrong.
The page itself returns a 200 statuscode which means it isn't cached so can't be browserrelated either.
The current workaround is a sleep(1) in the PHP code but when the code goes live I have no idea if it will suffice. I'd ofcourse rather have MySQL dish out the correct resultset.
EDIT: I'm using the MySQLi object interface of PHP, might be useful to know. ;)
My devcomputer got PHP 5.2.17, MySQL 5.0.51a (InnoDB) and Apache 2.2.17 installed and running on Windows 7 x64.
UPDATE
Added the following line CALL FetchObjectList('client_tbl', NULL, NULL, 1, 'client_tbl.name ASC', NULL, NULL); to the end of SaveClient. The resultset does not have the newly created client in the presented resultset.
UPDATE 2
I tried using the SQL_NO_CACHE as seen here but to no avail.
I will now try the same SQL directly in PHP instead of calling the SPs.
UPDATE 3 - 20 september
I've tried any reasonable answer/comment I've got so far without any luck. I tried to update my PHP and MySQL version today (since I today learned that the live server will run on PHP 5.3.something and MySQL 5.1.something) but did not get it to work. I need to update the PHP to get a more recent php_mysqli.dll/libmysql.dll since the one I got has only supports up to 5.0.51a and there might be my problem since nothing in the actual DB has worked. I tried the libmysql.dll from the MySQL install to no avail.
Note that I also changed the PHP code that I've included since I actually copied the wrong one that was calling the user_tbl and not the client_tbl and also simplified it (removed multiqueries) but still the same result.
I don't know what will happen to the bounty, if it reverts back to me I'll add it again.
Stored Procedure SaveClient
DELIMITER //
DROP PROCEDURE IF EXISTS work.SaveClient//
CREATE PROCEDURE work.SaveClient(
IN ObjectID INT,
IN UserID INT,
IN ClientName VARCHAR(60),
IN VersionFrom DATETIME,
IN VersionTo DATETIME)
root:BEGIN
DECLARE EXIT HANDLER FOR SQLEXCEPTION ROLLBACK;
/*
Default values ---------------------------------------------------------------------------------------------------------
*/
# Used to block INSERT/UPDATEs
SET #DoChanges = TRUE;
SET #Fields = '*';
SET #Version = NULL;
SET #UserVersion = NULL;
SET #DateNow = NOW();
SET #VersionActive = CONCAT(
'( ( NOW() BETWEEN ',
'version_from AND ',
'version_to ) OR ( ',
'version_from < NOW() AND ',
'version_to IS NULL ) )'
);
IF VersionFrom IS NULL THEN
SET VersionFrom = #DateNow;
END IF;
/*
Search for client ------------------------------------------------------------------------------------------------------
*/
IF ObjectID IS NOT NULL THEN
SET #Client = CONCAT(
'SELECT version INTO #Version FROM client_tbl WHERE object_id = ',
ObjectID,
' AND ',
#VersionActive
);
PREPARE stmt FROM #Client;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
# Check if there are any changes
IF #Version IS NOT NULL THEN
SELECT name INTO #Name FROM client_tbl WHERE name = ClientName AND version = #Version;
IF #Name = ClientName THEN
SET #errorMsg = "Duplicate entry";
SET #errorCode = "S0000002";
SELECT #errorCode, #errorMsg;
LEAVE root;
END IF;
END IF;
END IF;
/*
Search for user ---------------------------------------------------------------------------------------------------------
*/
# Create this as a function
IF UserID IS NOT NULL THEN
SET #User = CONCAT(
'SELECT version INTO #UserVersion FROM user_tbl WHERE object_id = ',
UserID,
' AND ',
#VersionActive
);
PREPARE stmt FROM #User;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
END IF;
IF #UserVersion IS NULL THEN
SET #errorMsg = "User is missing";
SET #errorCode = "U0000099";
SELECT #errorCode, #errorMsg;
LEAVE root;
END IF;
/*
Add the client ---------------------------------------------------------------------------------------------------------
*/
# Close the current version
IF #Version IS NOT NULL THEN
IF #DoChanges = TRUE THEN
CALL UpdateVersion(
ObjectID,
UserID,
#Version,
#DateNow,
'client_tbl'
);
SET #Version = #Version + 1;
END IF;
ELSE
SET #Version = 1;
END IF;
IF #DoChanges = TRUE THEN
IF ObjectID IS NULL THEN
INSERT INTO
object_tbl
(
object_class_id,
created,
created_by
)
VALUES(
2,
NOW(),
UserID
)
;
SET ObjectID = LAST_INSERT_ID();
END IF;
INSERT INTO
client_tbl
(
object_id,
version,
version_from,
version_to,
changed,
changed_by,
name
)
VALUES(
ObjectID,
#Version,
VersionFrom,
NULL,
#DateNow,
UserID,
ClientName
)
;
END IF;
COMMIT;
END //
DELIMITER ;
Stored Procedure FetchObjectList
DELIMITER //
DROP PROCEDURE IF EXISTS work.FetchObjectList//
CREATE PROCEDURE work.FetchObjectList(
IN ObjectType VARCHAR(60),
IN ObjectSubType VARCHAR(60),
IN ObjectSubID INT,
IN IsActive INT,
IN OrderBy VARCHAR(100),
IN SetStart INT,
IN MaxResults INT)
root:BEGIN
DECLARE EXIT HANDLER FOR SQLEXCEPTION ROLLBACK;
# Allow the "JSON" output be a max of 8kb
SET GLOBAL group_concat_max_len = 8096;
/*
Default values ---------------------------------------------------------------------------------------------------------
*/
SET #Fields = '*';
SET #VersionWhere = '1'; # Get everything
SET #Special = '';
SET #OrderBy = '';
SET #SetStart = '';
SET #MaxResults = '';
SET #JoinIn = '';
IF IsActive = 1 THEN
SET #VersionWhere = CONCAT(
'( NOW() BETWEEN ',
ObjectType,
'.version_from AND ',
ObjectType,
'.version_to OR ( ',
ObjectType,
'.version_from < NOW() AND ',
ObjectType,
'.version_to IS NULL ) )'
);
END IF;
IF OrderBy != '' THEN
SET #OrderBy = CONCAT(
'ORDER BY ',
OrderBy
);
END IF;
/*
Specials for each type -------------------------------------------------------------------------------------------------
*/
/*
- Clients ------------
*/
IF ObjectType = 'client_tbl' THEN
SET #Fields = '
*,
client_tbl.object_id AS object_id,
(
SELECT
COUNT(*) AS Total
FROM
client_user_privilege_tbl cup
WHERE
cup.client_id = client_tbl.object_id
) AS usercount
';
END IF;
/*
- Configuration ------------
*/
IF ObjectType = 'configuration_tbl' THEN
SET #Fields = '
*
';
END IF;
/*
Add upp the query to run -----------------------------------------------------------------------------------------------
*/
SET #Query = CONCAT(
'SELECT ',
#Fields,
' FROM ',
ObjectType,
' ',
#JoinIn,
' WHERE ',
#VersionWhere,
' ',
#Special,
#OrderBy
);
PREPARE stmt FROM #Query;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
COMMIT;
END //
DELIMITER ;
PHP CODE SNIPPET (Updated 20 september)
$query = "CALL FetchObjectList('client_tbl', NULL, NULL, 1, NULL, NULL, NULL)";
addTrace($query);
$rs = $db->query($query);
if( $rs ) {
addTrace('Query done -> Results: ' . $rs->num_rows);
while($r = $rs->fetch_assoc()){
$fetchArray[] = $r;
}
$count = $rs->num_rows;
$rs->close();
$db->next_result();
} else {
addTrace('Query failed -> ' . $db->error);
flushTrace();
exit;
}
Since it's a pretty aged version of mysql it would not surprise me that this would be bug related but one thing I would -in your place- want to know is if this would work by not using transactions at all. (e.g. autocommit = on).
For that version 5.0 I would also check the query cache and disable it all together instead of per query (see SHOW VARIABLES LIKE 'have_query_cache'; SET GLOBAL query_cache_size =0; ). That would at the least eliminate those playing a role in this problem, reproduce(or try) the problem and see if anything changed. If not, I would start searching for specific bugs, especially when the query cache is disabled and it still does this without using transactions.
I verified the support for 5.0 mysql (innodb).
innodb_flush_log_at_trx_commit = 1
innodb_flush_method = O_DIRECT
Set these options specifically in your my.cnf, the top one is most important. They explain nicely what those do.
See http://dev.mysql.com/doc/refman/5.0/en/innodb-parameters.html#sysvar_innodb_flush_log_at_trx_commit
The problem with mysql_query is that it does not support multiple resultsets. And that is exactly what your stored procedures do – they tend to return more than one resultset. Whenever you call an SP, the exit status is secretly carried in an (empty) resultset along. If you add it up with your own output from a procedure, some of the resultsets from the query will be ignored by your PHP retrieval code. When you try to run another query, the pending resultset(s) will still be in the buffer.

Get Updated Value in MySQL instead of affected rows

I've been trying to find an answer to this question, but haven't found any definitive "yes" or "no" in all my research.
I'm running a simple MySQL query like this:
UPDATE item SET `score`=`score`+1 WHERE `id`=1
Is there a way for that query to return the updated value, instead of the number of rows affected? Just as a reference, I'm doing this in PHP, so the actual code looks like:
$sql = "UPDATE item SET `score`=`score`+1 WHERE `id`=1";
$new_value = mysql_query($sql);
//Unfortunately this does not return the new value
I know I could do a second query and just SELECT the value, but I'm trying to cut down on queries as much as possible. Is there a way?
You can do it with a stored procedure that updates, and then selects the new value into an output parameter.
The following returns one column new_score with the new value.
DELIMITER $$ -- Change DELIMITER in order to use ; withn the procedure
CREATE PROCEDURE increment_score
(
IN id_in INT
)
BEGIN
UPDATE item SET score = score + 1 WHERE id = id_in;
SELECT score AS new_score FROM item WHERE id = id_in;
END
$$ -- Finish CREATE PROCEDURE statement
DELIMITER ; -- Reset DELIMITER to standard ;
In PHP:
$result = mysql_query("CALL increment_score($id)");
$row = mysql_fetch_array($result);
echo $row['new_score'];
No, there's nothing like postgresql's UPDATE ... RETURNING output_expression in MySQL (yet?).
If you don't want to run another Query SELECT then here is another way to do it. I have modified Mr. Berkowski code for reference:
DELIMITER $$
CREATE PROCEDURE increment_score
(
IN id_in INT
)
BEGIN
set #newScore := null;
UPDATE item SET score = IF((#newScore := score+1) <> NULL IS NULL, #newScore, NULL) WHERE id = id_in;
SELECT #newScore;
END
DELIMITER ;
No you cant. You could make a function or stored procedure that could do the insert and return the updated value but that would still require you to execute two queries from within the function or stored procedure.
You can create a trigger, and you will know everything about the modifications.

Categories