PHP PDO::prepare query building - problems with mysql functions - php

Related question, but not helpful to me: Why cant you pass MYSQL functions into prepared PDO statements?
Here's the deal: I'm writing an abstraction layer to PHP PDO and implementing a query builder.
This exact problem is occurring only in INSERT statements. Here's an example of my code:
$db->insert('table_name')
->keys(array('abc', 'def', 'ghi'))
->values(array($var1, $var2, $var3)) // can take a 2D array if you want to insert multiple rows at the same time
->execute();
The underlying code builds the query string with ?'s instead of values. For this particular example the query would result in the following:
INSERT INTO `table_name`
(`abc`, `def`, `ghi`)
VALUES
(?, ?, ?)
Upon calling execute(), it passes the values to PDOStatement::execute() as single dimension array (i.e. all values associated with the question marks are put in a single array). And this is where the problems start - the PDOStatement::execute() does not process MySQL functions as such, but quotes them as strings, thus breaking the query:
INSERT INTO `table_name`
(`abc`, `def`, `ghi`)
VALUES
('123', 456, 'NOW()') -- error, incorrect datetime value: 'NOW()'
The question is - how do I make this work while still maintaining the same interface? I know I could just check if the value of the column is a MySQL function and put it in directly instead of the question mark, but there are many functions one could use there and that would suck.
Edit: so it seems that for now the easiest option would be to simply set the values to leave alone like this: $var3 => 'noquote'. It's not really a good one, but it works.

Add another argument for that method:
table name
values (as associative array)
sql (as associative array)
Keep in mind, that you cannot use '?' when you want to do access the columns, e. g. in COLUMN1 + 1 or COLUMN1 + COLUMN2.

I procede like that:
$bdd->prepare(INSERT INTO `table_name` (`abc`, `def`, `ghi`) VALUES (?, ?, ?))
$bdd->execute(array('abcValue', 'devValue', 'ghiValue'))

Your last query comes up with an error ( -- error, incorrect datetime value: 'NOW()')
Try formatting your date like this instead:
DATE_FORMAT(now(), '%Y-%m-%d %H:%i:%s'))

Related

How to insert empty string into a date column using PDO?

I am using a PDO prepared statement to insert values. One of the values is a date, and sometimes it can be empty. In MySQL schema, the date column is set to allow NULL values.
Let's assume date_column is a date and it allows NULL. When I do this:
$query = "INSERT INTO tbl(date_column) VALUES(?)";
$stmt = $pdo->prepare($query);
$stmt->execute(['']);
This is giving me this error:
SQLSTATE[22007]: Invalid datetime format: 1292 Incorrect date value: '' for column 'date_column' at row 1
In phpMyAdmin, I can execute this query without errors. It sets the date to 0000-00-00
INSERT INTO tbl(date_column) VALUES('')
Isn't that the same query that is executed by PDO behind the scenes in the code example above? What's wrong?
All I want is to be able to insert empty string '' as a date without errors. I don't care if it is set to 0000-00-00
This is too long for a comment.
This is a fundamental problem:
All I want is to be able to insert empty string '' as a date.
Strings are strings, and dates are dates. You should not want to insert an empty string into the column because that makes no sense. Instead, you should want to insert either a NULL value or the default value.
So, your code should look like:
INSERT INTO tbl(date_column)
VALUES(NULL)
or:
INSERT INTO tbl(date_column)
VALUES(DEFAULT)
The fact that MySQL does better type checking on prepared queries with parameters is actually a good thing.
Empty string is not a null... NULL is a NULL
$stmt->execute([null]);

Selecting columns from array in Doctrine QueryBuilder

I want to select the columns given in an array and then group based on those columns but I can't figure out how to pass array values through select() in Doctrine. I've tried a bunch of different variations of this and I can't get it to work. $factors is just a standard array with column names as values.
$qb->select(":factors")
->from("Table")
->where("type = :type")
->groupBy(":factors")
->setParameter("factors", $factors)
->setParameter("type", $type);
Parameters are not ment to be used in a SELECT statement. Read about prepared statements in PDO, Doctrine is just using this functionallity.
They are only used to compare with stored database values, not column or table names. Prepared statements basically help you escape those values used in a query, which could be a bit difficult for strings containing " or '.
If you want to use your $factors array for variable select statments you could just do
$qb->select(implode(',', $factors))
but in that case you have to prevent injections attacks by yourself. Best would be to have a whitelist of allowed values in $factors.
Same holds for GROUP BY statement.

SELECT and INSERT not selecting

When I execute
INSERT INTO `survey`
(`id`,`name`,`date_start`,`date_end`)
values
(:id,:name,NULL,DATE_ADD(NOW(), INTERVAL 1 MINUTE))
on duplicate key UPDATE `name`=:name;
SELECT coalesce(:id,LAST_INSERT_ID()) as 'id'
it inserts a new data fine, but doesn't select the id (which is needed later on in my php code)
I've tried this suggestion
$db->setAttribute(PDO::ATTR_EMULATE_PREPARES, true);
but this SQL throws errors (due to duplicate parameters)
SELECT ASCII(substr(`perm` FROM floor(:i/8)+1))&(1<<(:i%8))>0 as perm FROM `user` WHERE `id`=:id
I'm in a lose-lose situation, re-writing all my SQL code to not have duplicate parameters would be very messy, doing a separate select straight after inserting may not return the id I want. Any suggestions would be great
You cannot run two queries at the same time, only one at the time.
If you want to do the whole thing at once then create a stored procedure.
Same goes for complex queries, when it gets complicated you want to have your logic in the database.
Here is an example:
DELIMITER //
CREATE PROCEDURE sp_insert_survey(IN `p_id`,
IN `p_name`,
IN `p_date_start`,
IN `p_date_end`)
BEGIN
INSERT INTO `survey`(`id`,`name`,`date_start`,`date_end`)
VALUES (p_id, p_name, p_date_start, p_date_end);
SELECT `id`,`name`,`date_start`,`date_end`
FROM survey WHERE `id` =LAST_INSERT_ID();
END //
DELIMITER ;
Call the sp from PDO:
$stmt = $db->prepare('CALL sp_insert_survey(?, ?, ?, ?)');
then fetch the data as a SELECT query.
Upon typing this up, one of the similar questions that came up on the right getting last inserted row id with PDO (not suitable result) gave a suitable answer in the question itself, although I'm a little dubious considering the method is being questioned itself.
$db->lastInsertId();
Seems to work for me, but in the question linked it isn't working as desired for the questioner, so I'm not entirely settled with this answer. It does seem to be a bit of a hack.

Find if string is MySQL function in PHP

I have a function that takes an array and creates a SQL statement based on they key/value pairs of the array. For example:
name=>SomeKittens
It'd turn into
(`name`) VALUES ('SomeKittens')
The only problem is when I use a MySQL string function such as NOW().
creation_date=>NOW()
turns into
(`creation_date`) VALUES ('NOW()')
Note that NOW() is escaped. Is there any way to detect if the value is a MySQL string function? (besides of course $value === "NOW()")
I'm using the Joomla DBO but am open to PDO/MySQLi solutions as well.
(relevant chat discussion)
If you allow functions with arguments I don't think you will be able to protect your db against SQL injections.
If you allow only functions w/o arguments (like NOW()) you might as well hardcode a list.
You may simply want to define a constant like MYSQL_NOW that when creating your query you know to convert to a NOW() function call rather than 'NOW()' string.

Sending a list of values of the same field to MySQL stored procedure

I have a MySQL table with ID as a primary key and other for this matter non-important fields.
What I would like to do is delete multiple records by sending a list of IDs for deletion as a parameter to stored procedure.
I know how to do this manually (building a query directly in PHP) but I would like to avoid that and do all my SQL directly in the DB.
Tried searching SO but couldn't find any related questions. Sorry if this is a duplicate.
Thanks
In accordance to http://dev.mysql.com/doc/refman/5.1/en/faqs-stored-procs.html#qandaitem-B-4-1-17 you can't do it directly.
But I think you can try to the following trick:
Create string of you ids in php like 'id1,id2,id3'.
Use prepared statement for binding this sting on fly.
Maybe it helps.
You could try something like
DELETE FROM sometable WHERE FIND_IN_SET(idfield, #param)
no idea if this'd work (and don't have access to a mysql instance right now to test on). Basically the problem is that if you pass in a comma-separated value list into a paramter, it'll just be a string inside the sproc, and doing a WHERE id IN ('1,2,3') would fail, since that's just a simple string and not at all the same as WHERE id IN (1,2,3). The find_in_set() function should take care of that.
I gave +1 to #Marc B for clever use of FIND_IN_SET(). It won't be able to use an index, so the performance won't be good, but it should work.
Another solution that can work (but will be slow as well, because it can't use an index):
DELETE FROM sometable
WHERE CONCAT(',', param, ',') LIKE CONCAT('%,', idfield, ',%')
The solution that #Andrej L describes isn't really parameter binding, it's interpolation of a stored procedure argument into a dynamic SQL string prior to preparing it.
SET sql = CONCAT('DELETE FROM sometable WHERE idfield IN (', param, ')');
PREPARE stmt FROM sql;
EXECUTE stmt;
You can't parameterize a list of values with a single parameter, even if the parameter's value looks like a comma-separated list of integers.
Interpolation can work, and it will benefit from an index, but be careful to filter the string so it contains only numeric digits and commas. Otherwise you introduce a significant risk of SQL injection (debunking the claim that some people make that stored procedures are inherently more secure).

Categories