(Side-note: Blocking ! in question titles doesn't stop smart-arses like me putting U+203C Double Exclamation Mark instead :p)
After a quick round of debugging, I found this:
$query = <<<END
SELECT
`column1`, `column2`,
SOME_FUNCTION(`column3`) -- process in PHP instead?
FROM `tablename`
WHERE `condition` BETWEEN ? AND ?
END;
$stmt = $pdo->prepare($query);
$stmt->execute(array(1,10));
And got this:
Uncaught Exception » RuntimeException » PDOException:
SQLSTATE[HY093]: Invalid parameter number: number of bound variables does not match number of tokens
Do you see the problem?
The question mark in the comment -- process in PHP instead? is interpreted as a token to bind a parameter to! PDO then expects three parameters instead of the two that were passed.
Now, obviously the simple solution was to just rewrite the comment, but this feels like I'm avoiding what may be a bigger issue.
Is there perhaps something wrong with PDO, or is there an option I can set to have it understand MySQL comments?
You can have comments, but not those with ? in them or other things like :x that would be interpreted as placeholders.
PDO does not understand SQL syntax. It's treating all that text as something that needs to be examined for placeholders. When emulating prepared statements this is what will happen.
Related
Here i have a minor problem with my sql query. But realy i am unable to figure out what to do.
I have write the query too below so that you guys can understand how this error can be resolved.
Immediate help will surely be appreciated.
The type for column "att_date" defined as "timestamp with time zone" in the postgres database.
Thanks
select (select emp_code ||' - '||first_name ||' '||last_name from person where person_id = da.person_id) as emp, att_date, timein, timeout, totalhrs,
totalothrs, isholiday, (select element_name from elements where element_id = da.element_id) as leavetype,daily_attendance_id
from daily_attendance da
where att_date between coalesce('$prd_st'::date,'01-01-1990'::date) and coalesce('$prd_end'::date,'12-31-4712'::date)
and ad_client_id = $_SESSION[clientid]
order by 3;";
Immediate cause: Confusion between empty string '' and NULL
'' is not the same as NULL.
coalesce(NULL, '01-01-1990'::date) is 01-01-1990. coalesce('', '01-01-1990'::date) is an error, and will emit exactly the error message you get:
regress=# SELECT coalesce('', '01-01-1990'::date);
ERROR: invalid input syntax for type date: ""
LINE 1: SELECT coalesce('', '01-01-1990'::date);
^
when run standalone from psql.
If you want NULL, write NULL, not ''.
Root cause: bad coding practices
Read the PHP manual on SQL injection and http://bobby-tables.com/ then go fix your code to use PHP Data Objects (PDO) or pg_query_params with a parameters array. PHP programmers usually call this "prepared statements", though "parameterised queries" is more accurate.
Not only will this fix your problem, it'll get rid of the gaping security holes in your code.
Never interpolate strings into SQL. If someone tricks your app (say, with a direct POST, modified form, etc) into sending ');DROP TABLE daily_attendance;-- you'll be pretty annoyed.
Hint: If you pass the PHP null value to pg_query_params or a PDO parameter bind method, it'll be passed as the database NULL. So you don't need to special-case entering the NULL keyword when using bound parameters ("prepared statements").
I have a query that looks like this:
SELECT CONCAT('path/to/page/?id=', id) AS link FROM users WHERE name = ?
I am using PDO to prepare this statement and I am getting the error
Invalid parameter number: number of bound variables does not match number of tokens
because it thinks the question mark in the CONCAT string is a placeholder.
Is there any way to escape the question mark so PDO knows that it is not a placeholder?
Please no comments about other ways to get the link. I am changing old code that goes into an old templating engine so it would be A LOT less work to find a way to escape the question mark than to not put a question mark in the query.
PDO is not confused by the question mark inside the quotes. I just test this with PHP 5.5.15.
$sql = "SELECT CONCAT('path/to/page/?id=', id) AS link FROM foo WHERE name = ?;";
$stmt = $pdo->prepare($sql);
$stmt->bindValue(1, 'name');
$stmt->execute();
print_r($stmt->fetchAll());
It works fine, with no error about a wrong number of parameters. Your error is caused by the way you're binding parameters, not by the SQL syntax.
I suspect you haven't shown us the whole SQL query, because WHERE without FROM is a syntax error anyway. So you must have additional parameter placeholders that you haven't shown us. It would also be helpful if you show us the way you're binding parameters (or passing parameters to execute()).
This is a very old question, but the escaping of question marks have landed into PHP 7.4.
A question mark is escaped by adding a second... question mark.
In your query, you could add this:
$sql = "SELECT CONCAT('path/to/page/??id=', id) AS link FROM users WHERE name = ?";
For usage with json or jsonb operator in postgresql:
$sql = "SELECT '["a", "b", "c"]'::jsonb ?? 'a'";
I spent few hours trying to insert a query using PDO, and finally I realized that I can`t do it (dont know how).
The problem is in fact that column name have "?" in it. One of columns is named "If HSM Visa to what year?". Because of that, Every time I do insert I get either:
- wrong number of parameters passed or
- cant mix name and ? in query.
I gave up from this, and I'm going to alter mysql table I got to work with (who is naming columns with question marks anyway ?), but I'm still curious.
INSERT INTO `tbl_maindetails` (`Id`,`Title`,`If HSM Visa to what year?`) VALUES (?, ?, ?)
Thanks,
Goran
$dbh->setAttribute(PDO::ATTR_EMULATE_PREPARES, FALSE);
will solve the issue, I believe.
I have the same problem! I tried to get around the confusion between the character "?" in the column (field) names and the same character "?" as a positional (un-named) placeholder in PDO, by switching to NAMED parameter binding... but the problem STILL persists :(
Now, PDO sees the "?" in the column names and thinks I'm mixing up named and positional parameters!
For example:
UPDATE myTable SET `title` = :title, `Underwater?` = :Underwater
WHERE ID='123'
together with the following binding:
Array ( [:title] => test [:Underwater] => 0)
produces thee following ERROR MESSAGE:
"SQLSTATE[HY093]: Invalid parameter number: mixed named and positional parameters"
Notice that I avoided placing any "?" inside the placeholder itself (it's ":Underwater" rather than ":Underwater?", but it was no help...)
Clearly, this is a bug! PDO is seeing the "?" in the column names, and jumping to the conclusion that it's a positional placeholder, even if we are strictly using NAME parameter binding!
I'll see if I can report this bug...
Since MySQL allows question marks in the column names, I see no compelling reason why we must avoid them! (Though, in the short term, I'll do that, sigh...)
Here is the only (ugly) solution that I could come up with:
$db->query("SET #column_name = 'question?'");
$db->query("SET #sql_text = CONCAT('INSERT INTO table1 SET `', #column_name, '` = ?')");
$db->query("PREPARE stmt FROM #sql_text;");
$db->query("SET #v = ?", 'something');
$db->query("EXECUTE stmt USING #v");
echo $db->insert_id();
This will insert a new row into table1 with question? = 'something'
This code is using a PDO wrapper so you'll need to adjust for that.
In the long run I think you made the right choice by renaming the column.
Today I encountered a bug (in PDO) I never saw before, but is kinda obvious when you think about it.
I got the following error:
Warning: PDOStatement::execute() [pdostatement.execute]: SQLSTATE[HY093]: Invalid parameter number:
The query I was using was similar to the following:
SELECT
x
FROM
y
WHERE
-- CHECKING IF X = :Z --
x = :y
AND
1 = 2
Obviously I had more parameters and a longer query.
Why does it give me this error?
The solution is obvious: PDO disregards comments as such and tries to bind the non-existent variable ':Z'. You can't use parameters in comments in PDO (unless you do bind them).
There's a similar bug using question marks in comments.
This question already has answers here:
Getting raw SQL query string from PDO prepared statements
(16 answers)
Closed 6 years ago.
In PHP, when accessing MySQL database with PDO with parametrized query, how can you check the final query (after having replaced all tokens)?
Is there a way to check what gets really executed by the database?
So I think I'll finally answer my own question in order to have a full solution for the record. But have to thank Ben James and Kailash Badu which provided the clues for this.
Short Answer
As mentioned by Ben James: NO.
The full SQL query does not exist on the PHP side, because the query-with-tokens and the parameters are sent separately to the database.
Only on the database side the full query exists.
Even trying to create a function to replace tokens on the PHP side would not guarantee the replacement process is the same as the SQL one (tricky stuff like token-type, bindValue vs bindParam, ...)
Workaround
This is where I elaborate on Kailash Badu's answer.
By logging all SQL queries, we can see what is really run on the server.
With mySQL, this can be done by updating the my.cnf (or my.ini in my case with Wamp server), and adding a line like:
log=[REPLACE_BY_PATH]/[REPLACE_BY_FILE_NAME]
Just do not run this in production!!!
You might be able to use PDOStatement->debugDumpParams. See the PHP documentation .
Using prepared statements with parametrised values is not simply another way to dynamically create a string of SQL. You create a prepared statement at the database, and then send the parameter values alone.
So what is probably sent to the database will be a PREPARE ..., then SET ... and finally EXECUTE ....
You won't be able to get some SQL string like SELECT * FROM ..., even if it would produce equivalent results, because no such query was ever actually sent to the database.
I check Query Log to see the exact query that was executed as prepared statement.
I initially avoided turning on logging to monitor PDO because I thought that it would be a hassle but it is not hard at all. You don't need to reboot MySQL (after 5.1.9):
Execute this SQL in phpMyAdmin or any other environment where you may have high db privileges:
SET GLOBAL general_log = 'ON';
In a terminal, tail your log file. Mine was here:
>sudo tail -f /usr/local/mysql/data/myMacComputerName.log
You can search for your mysql files with this terminal command:
>ps auxww|grep [m]ysqld
I found that PDO escapes everything, so you can't write
$dynamicField = 'userName';
$sql = "SELECT * FROM `example` WHERE `:field` = :value";
$this->statement = $this->db->prepare($sql);
$this->statement->bindValue(':field', $dynamicField);
$this->statement->bindValue(':value', 'mick');
$this->statement->execute();
Because it creates:
SELECT * FROM `example` WHERE `'userName'` = 'mick' ;
Which did not create an error, just an empty result. Instead I needed to use
$sql = "SELECT * FROM `example` WHERE `$dynamicField` = :value";
to get
SELECT * FROM `example` WHERE `userName` = 'mick' ;
When you are done execute:
SET GLOBAL general_log = 'OFF';
or else your logs will get huge.
What I did to print that actual query is a bit complicated but it works :)
In method that assigns variables to my statement I have another variable that looks a bit like this:
$this->fullStmt = str_replace($column, '\'' . str_replace('\'', '\\\'', $param) . '\'', $this->fullStmt);
Where:
$column is my token
$param is the actual value being assigned to token
$this->fullStmt is my print only statement with replaced tokens
What it does is a simply replace tokens with values when the real PDO assignment happens.
I hope I did not confuse you and at least pointed you in right direction.
The easiest way it can be done is by reading mysql execution log file and you can do that in runtime.
There is a nice explanation here:
How to show the last queries executed on MySQL?
I don't believe you can, though I hope that someone will prove me wrong.
I know you can print the query and its toString method will show you the sql without the replacements. That can be handy if you're building complex query strings, but it doesn't give you the full query with values.
I think easiest way to see final query text when you use pdo is to make special error and look error message. I don't know how to do that, but when i make sql error in yii framework that use pdo i could see query text