mysql prepared statement "truncate table ?" returns null - php

in a function to truncate a table I can use
$stmt = $mysqli->prepare("truncate table packed_items");
and $stmt is set to a mysqli_stmt Object, but
if I try
$stmt = $mysqli->prepare("truncate table ?");
then $stmt is set to null and the statment:
$stmt->bind_param("s", $mytable)
will crash with error
Call to a member function bind_param() on a non-object in
I am using parameterized prepared statements to select,insert and update with no problem.

you cannot bind any SQL literal but data one. no keyword, no operator, no identifier.
if you really need to truncate your tables dynamically, knowing no name already (as truncating tables at random is obviously a sign of very bad design), check the table name against white list, format it correctly, and then interpolate in a query string.

Related

MySQLi: Number of bind variables doesn't match number of fields in prepared statement [duplicate]

I’ve been trying to code a login form in PHP using a prepared statement but every time I try to log in I get the following error:
mysqli_stmt::bind_result(): Number of bind variables doesn't match number of fields in prepared statement
Here is my code:
<?php
$mysqli = new mysqli("localhost", "root" , "" , "security");
$stmt = $mysqli->prepare("SELECT username AND password FROM users WHERE username = ?");
$username = $_POST['name'];
$stmt->bind_param('s', $username);
$stmt->execute();
$stmt->bind_result($password, $username);
$stmt->fetch();
Can someone tell me why this is happening?
$mysqli->prepare("SELECT username, password FROM users WHERE username = ?");
$username = $_POST['name'];
$stmt->bind_param('s' ,$username);
$stmt->execute();
$stmt->bind_result($username, $password);
Your select syntax was wrong, the correct syntax is
SELECT field1, field2, field3 FROM TABLE WHERE field1 = ? AND field2 = ?
To select more fields simply separate them by a comma and not an AND
Explanation
The error message clearly states that the number of columns you are SELECTing does not match the number of variables you provided to mysqli_stmt::bind_result(). They need to match exactly.
For example:
-- ↓ 1 ↓ 2 ↓ 3
SELECT col1, col2, col3 FROM tableA
There are 3 columns being fetched, so you need to provide 3 variables.
$stmt->bind_result($var1, $var2, $var3);
There could be a number of reasons why the column count doesn't match variable count.
Count your columns and variables
The simplest cause is that you made a mistake in the count. Do a recount of both. Maybe you changed the SQL but forgot to adjust bind_result()?
SELECT *
Using SELECT * is not recommended with bind_result(). The number of columns in the table could change as a result of schema changes or joins and will break your application. Always list all the columns explicitly!
Logical problem with SQL
The code from the question contains a logical mistake. SELECT username AND password produces a single column in the result. The AND keyword evaluates to a boolean expression. To select multiple columns you must use ,. Maybe there is another logical error in the query that causes the SQL to produce a different number of columns than you expected?
UPDATE and INSERT
DML statements such as INSERT, UPDATE and DELETE do not produce result sets. You can't bind variables to such prepared statement. You need to execute another SELECT statement to fetch the data.
Fetching an array from the prepared statement
The return value of mysqli_stmt::bind_result() is not an array, it's just a boolean. If you expected this function to return an array, then you are probably looking for get_result() with fetch_all() instead.
To select an array you need to get the mysqli_result object first.
$stmt = $mysqli->prepare("SELECT username AND password FROM users WHERE username = ?");
$stmt->bind_param('s', $username);
$stmt->execute();
$mysqli_result = $stmt->get_result();
// The object can then be iterated or used with fetch_* methods
foreach($mysqli_result as $row) {
}
// or
$arrayRow = $mysqli_result->fetch_assoc();
If this function doesn't exist in your PHP installation, then it means you have PHP not installed properly. You need to either recompile it, or enable mysqlnd (e.g. in cPanel).
If you are only learning PHP, it would be much easier for you to learn PDO instead.

PHP + mySQL: Prepare, bind_param not working as I'd expect it

I'll put here two examples where $stmt = $mysqli->prepare() + $stmt->bind_param() deny to work and I can't see for myself why.
Not working:
if ($stmt = $mySQLi->prepare("DROP DATABASE ?")) {
$stmt->bind_param('s', $db_name);
$stmt->execute();
$stmt->store_result();
}
Current working workaround:
if ($stmt = $mySQLi->prepare("DROP DATABASE $db_name")) {
//$stmt->bind_param('s', $db_name);
$stmt->execute();
$stmt->store_result();
}
Not working:
if ($stmt = $strSQLi->prepare("SELECT ? FROM Strings.texts WHERE keyName = ? LIMIT 1")) {
$stmt->bind_param('ss', strtolower($lang), strtolower("help_".$key));
$stmt->execute();
$stmt->store_result();
$stmt->bind_result($result);
}
Current working workaround:
if ($stmt = $strSQLi->prepare("SELECT {strtolower($lang)} FROM EVEStrings.texts WHERE keyName = ? LIMIT 1")) {
$stmt->bind_param('s', strtolower("help_".$key));
$stmt->execute();
$stmt->store_result();
$stmt->bind_result($result);
}
Any idea WHY?
This is how mysqli::prepare() is working. It's completely written in the documentation.
http://php.net/manual/en/mysqli.prepare.php
Note:
The markers are legal only in certain places in SQL statements. For
example, they are allowed in the VALUES() list of an INSERT statement
(to specify column values for a row), or in a comparison with a column
in a WHERE clause to specify a comparison value.
However, they are not allowed for identifiers (such as table or column
names), in the select list that names the columns to be returned by a
SELECT statement, or to specify both operands of a binary operator
such as the = equal sign. The latter restriction is necessary because
it would be impossible to determine the parameter type. It's not
allowed to compare marker with NULL by ? IS NULL too. In general,
parameters are legal only in Data Manipulation Language (DML)
statements, and not in Data Definition Language (DDL) statements.
This part mostly: they are not allowed for identifiers (such as table or column names)
The idea of binding parameters it that you are sending to the database engine the query and in the runtine it binds the values you have given. If the table is not specified, the engine cannot build a valid statement, thus to continue executing the query and bind params is impossible.
I would not suggest to use dynamic table names in general, whether it is safe or not. However, if you really insist to do it, do not let the user to decide that. Decide the {strtolower($lang)} on application level (i.e. from an array), not from the user input.
As far as I know from PDO (and I think it's the same for mysqli) this can't be done.
bind_param and bind_value() can only bind values, not table or column names.
You have to filter your data manually, maybe with a whitelist method.

mysqli_stmt::bind_result(): Number of bind variables doesn't match number of fields in prepared statement

I’ve been trying to code a login form in PHP using a prepared statement but every time I try to log in I get the following error:
mysqli_stmt::bind_result(): Number of bind variables doesn't match number of fields in prepared statement
Here is my code:
<?php
$mysqli = new mysqli("localhost", "root" , "" , "security");
$stmt = $mysqli->prepare("SELECT username AND password FROM users WHERE username = ?");
$username = $_POST['name'];
$stmt->bind_param('s', $username);
$stmt->execute();
$stmt->bind_result($password, $username);
$stmt->fetch();
Can someone tell me why this is happening?
$mysqli->prepare("SELECT username, password FROM users WHERE username = ?");
$username = $_POST['name'];
$stmt->bind_param('s' ,$username);
$stmt->execute();
$stmt->bind_result($username, $password);
Your select syntax was wrong, the correct syntax is
SELECT field1, field2, field3 FROM TABLE WHERE field1 = ? AND field2 = ?
To select more fields simply separate them by a comma and not an AND
Explanation
The error message clearly states that the number of columns you are SELECTing does not match the number of variables you provided to mysqli_stmt::bind_result(). They need to match exactly.
For example:
-- ↓ 1 ↓ 2 ↓ 3
SELECT col1, col2, col3 FROM tableA
There are 3 columns being fetched, so you need to provide 3 variables.
$stmt->bind_result($var1, $var2, $var3);
There could be a number of reasons why the column count doesn't match variable count.
Count your columns and variables
The simplest cause is that you made a mistake in the count. Do a recount of both. Maybe you changed the SQL but forgot to adjust bind_result()?
SELECT *
Using SELECT * is not recommended with bind_result(). The number of columns in the table could change as a result of schema changes or joins and will break your application. Always list all the columns explicitly!
Logical problem with SQL
The code from the question contains a logical mistake. SELECT username AND password produces a single column in the result. The AND keyword evaluates to a boolean expression. To select multiple columns you must use ,. Maybe there is another logical error in the query that causes the SQL to produce a different number of columns than you expected?
UPDATE and INSERT
DML statements such as INSERT, UPDATE and DELETE do not produce result sets. You can't bind variables to such prepared statement. You need to execute another SELECT statement to fetch the data.
Fetching an array from the prepared statement
The return value of mysqli_stmt::bind_result() is not an array, it's just a boolean. If you expected this function to return an array, then you are probably looking for get_result() with fetch_all() instead.
To select an array you need to get the mysqli_result object first.
$stmt = $mysqli->prepare("SELECT username AND password FROM users WHERE username = ?");
$stmt->bind_param('s', $username);
$stmt->execute();
$mysqli_result = $stmt->get_result();
// The object can then be iterated or used with fetch_* methods
foreach($mysqli_result as $row) {
}
// or
$arrayRow = $mysqli_result->fetch_assoc();
If this function doesn't exist in your PHP installation, then it means you have PHP not installed properly. You need to either recompile it, or enable mysqlnd (e.g. in cPanel).
If you are only learning PHP, it would be much easier for you to learn PDO instead.

ON DUPLICATE KEY UPDATE not working in PDO

INSERT INTO b (id, website...)
VALUES (:id, :website...)
ON DUPLICATE KEY UPDATE
website=:website ...
I have a MYSQL QUERY, I have SET id unique, why
website=:website ...
is not working, when I change to website="whatever" it works. anyone know why?
$job_B->bindValue(':website', $website, PDO::PARAM_STR);
As a general tip, you shouldn't be "duplicating" inserted values when doing an ON DUPLICATE KEY. Mysql provides the VALUES() function for this purpose, e.g.
INSERT INTO foo (bar) VALUES (:baz)
ON DUPLICATE KEY UPDATE bar := :baz
can be better rewritten as
INSERT INTO foo (bar) VALUES (:baz)
ON DUPLICATE KEY UPDATE bar = VALUES(bar)
^^^^^^^^^^^
VALUES() will re-use the value assigned to the specified field in the VALUES (...) section automatically, without requiring binding another variable into the query.
You have run into an unfortunate and misleading behavior of PDO's named parameters in a prepared statement. Despite assigning names, you cannot actually use a parameter more than once, as mentioned in the prepare() documentation:
You must include a unique parameter marker for each value you wish to pass in to the statement when you call PDOStatement::execute(). You cannot use a named parameter marker of the same name twice in a prepared statement. You cannot bind multiple values to a single named parameter in, for example, the IN() clause of an SQL statement.
This means you'll need to bind the parameter twice, with two different names, and consequently two different bindValue() calls:
$stmt = $pdo->prepare("
INSERT INTO b (id, website...)
VALUES (:id, :website_insert...)
ON DUPLICATE KEY UPDATE
website=:website_update ...
");
// Later, bind for each
$job_B->bindValue(':id', ...);
// Same value twice...
$job_B->bindValue(':website_insert', $website, PDO::PARAM_STR);
$job_B->bindValue(':website_update', $website, PDO::PARAM_STR);
The use of VALUES() to refer to the new row and columns is deprecated beginning with MySQL 8.0.20, and is subject to removal in a future version of MySQL.

Why can't I write PDO prepared statements in quotes?

I had the following piece of code with PDO prepared statements:
$stmt = $conn->prepare('SELECT `myColumn1` FROM my_table '.
'WHERE `myColumn2`=:val LIMIT 1');
$stmt->bindValue(":val", $value);
$stmt->execute();
$row = $stmt->fetch(PDO::FETCH_ASSOC);
This works fine. It sends the following query:
113 Query SELECT `myColumn1` FROM my_table WHERE `myColumn2`=":val" LIMIT 1
and it returns the correct value.
But it doesn't work if I change the first line to
$stmt = $conn->prepare('SELECT `myColumn1` FROM my_table '.
'WHERE `myColumn2`=":val" LIMIT 1');
or
$stmt = $conn->prepare('SELECT `myColumn1` FROM my_table '.
'WHERE `myColumn2`=':val' LIMIT 1');
The same query is sent, but PDO returns false.
Can anybody explain why?
From the page you quote:
The parameters to prepared statements don't need to be quoted; the driver automatically handles this.
The purpose of the quotation marks is to delimit string data from the rest of the query, since it cannot easily be separated (unlike numbers, which have an obvious format). Since using prepared statements means that query and data are passed separately, the quotes are unnecessary.
One of the advantages of prepared statements are that types are handled for you (sort of...). In other words, prepared statements allow MySQL (or whatever RDBMS) to decide how to handle data. When putting quotes, that would force it to be a string which doesn't make sense. If it's supposed to be a string, then the server will handle that.

Categories