This question already has answers here:
Can I bind an array to an IN() condition in a PDO query?
(23 answers)
Closed 7 years ago.
Note: I see this question as a probable serious issue in PDO Drivers. I can pretty much understand the difference between an array and string. So, please consider testing this on your sandbox before Deleting or making duplicate.
$pdo = db()->getInstance();
$sql = "SELECT * FROM clients WHERE client_id IN :clients";
$params = [ ":clients" => "(223,224,225)" ];
$stmt = $pdo->prepare($sql);
try{
$stmt->execute($params);
} catch( \Exception $e){
die("Query Execution Failed" . $e->getMessage());
}
Query Execution FailedSQLSTATE[42000]: Syntax error or access
violation: 1064 You have an error in your SQL syntax; check the manual
that corresponds to your MySQL server version for the right syntax to
use near ''(223,224,225)'' at line 1
The issue here is instead of translating to:
SELECT * FROM clients WHERE client_id IN (223,224,225)
it is translating to:
SELECT * FROM clients WHERE client_id IN '(223,224,225)'
There are no arrays here. I am just providing a parameter clients to replace :clients. Why does it add the quotes around?
First of all, I agree with you (somewhat). It's a issue, not very serious one. Maybe they kept it knowingly. Or, maybe your stupid question has some debate potentials.
Every PDO parameter gets wrapped with quotes. (Personally I think should not be). When you pass the IN string at once, it puts quote around it which fails the query. But, you can do that (put quotes) with individual item. So, pass parameter to each item instead of preparing string before.
Now, to the solution:
$pdo = db()->getInstance();
$sql = "SELECT * FROM clients WHERE client_id IN :clients";
$clients = [223,224,225];
/* You could use bind with ? here. I am just making this with named parameters so that you could echo SQL */
$params = [];
$replacement = [];
foreach ($clients as $key => $value){
$replacement[] = ":client$key";
$params[":client" . $key] = $value;
}
$sql = str_replace(":clients", "(" . implode(",",$replacement) . ")", $sql);
echo $sql;
/* SELECT * FROM clients WHERE client_id IN (:client0,:client1,:client2) */
$stmt = $pdo->prepare($sql);
try{
$stmt->execute($params);
print_pre($stmt->fetchAll(PDO::FETCH_ASSOC));
} catch( \Exception $e){
die("Query Execution Failed" . $e->getMessage());
}
This works like a charm. I tried by creating a dummy clients table. But the thing is your actual SQL now becomes:
SELECT * FROM clients WHERE client_id IN ('223','224','225')
Most people might not give a shit about it but you might lose some performance because the query is converting the id to string with quotes, especially you have a large database, complex queries and prefer integers.
Related
I created a series of helper functions in php that I have been using because they make prepared mysqli statements a lot quicker and easier to follow for me. They have worked on other projects but for some reason the problem on this particular project is eluding me... It works perfectly offline but when I upload it to my server I get 0 rows returned. I am assuming it must be a setting somewhere but I can't for the life of me figure out what it is. No errors in the Apache log appear when I run this.
In this particular case I am trying to verify that a series of values exists in the database. If they do, return true. If they don't, return false.
Here is the helper function:
function verifyRow($a_verify) {
//Pass $mysqli connection variable
global $mysqli;
//Create a string from the selection criteria
$selection = implode(", ", (array) $a_verify->selection);
//Transfer table contents to table variable just for consistency
$table = $a_verify->table;
//Create a string from the parameter types
$type = implode("", (array) $a_verify->type);
//Push a reference to the string of types to the parameter array
$a_params[] =& $type;
//For each result parameter, push a column = result string into the colvals array and create a reference to the parameter in the parameters array
foreach ($a_verify->columns as $key => $value) {
$a_colvals[] = "{$value} = ?";
$a_params[] =& $a_verify->results->$key;
}
//Create a string from the column = result array
$colvals = implode(" AND ", $a_colvals);
//Generate a query
$query = "SELECT {$selection} FROM {$table} WHERE {$colvals} LIMIT 1";
//Prepare statement and error checking
if (!($stmt = $mysqli->prepare($query))) {
print "Prepare failed: (" . $mysqli->errno . ") " . $mysqli->error;
}
//Bind parameters and error checking
if (!(call_user_func_array(array($stmt, 'bind_param'), $a_params))) {
print "Binding parameters failed: (" . $mysqli->errno . ") " . $mysqli->error;
}
//Execute statement and error checking
if (!$stmt->execute()) {
print "Execute failed: (" . $stmt->errno . ") " . $stmt->error;
}
//Get the result
$result = $stmt->get_result();
//Bind the result to a variable
$row = $result->fetch_assoc();
//If the row exists, return true
if ($row) {
return true;
}
//If the row doesn't exist, return false
else {
return false;
}
}
Here is how I am trying to retrieve the result:
// Confirm there is a user in the db with this user code
$a_verify = (object) array(
"selection" => (object) array(
"*"
),
"table" => "`users`",
"columns" => (object) array(
"`code`"
),
"results" => (object) array(
"'123456'"
),
"type" => (object) array(
"s"
)
);
print verifyRow($a_verify); //Ideally 'true'
Here is how the "users" database is set up:
|-----|---------------|--------|
| id | email | code |
--------------------------------
| 0 | test#test.com | 123456 |
--------------------------------
Any idea what the problem might be? It's driving me crazy.
Helper functions are great and here I am totally with you. But... you call this dazzling skyscraper of code "a lot quicker and easier" than vanilla SQL for real?
For some reason you are constantly moving your code back and forth from one format into another. For example, you are creating an array array("*") then convert it into object (object) and then... convert it back into array (array) $a_verify->selection. Why?
Or you take a simple SQL query, SELECT * FROM users WHERE code = ? and create a multiple-line construction where SQL keywords are substituted with array keys with an addition of a lot of quotes, parentheses and stuff. And then convert it back into SQL.
SQL is an amazing language, it survived for decades thanks to its simplicity and strictness. There is no reason to dismantle it into pieces to add a structure. SQL already has a structure. And it has many features that just aren't supported by your helper. In the end, with so much effort you get a severely limited SQL version with complicated syntax and entangled implementation. You call it a helper function?
Last but not least. Such functions where you are adding column and table names freely are asking for SQL injection. And your other question displays this appalling practice - you are adding the user input directly in your SQL. Why bother with prepared statements if you have an injection anyway? See this answer on how to deal with such situations.
Let me show you a real helper function. It makes your code short and readable and it supports full features of SQL (ordering the results for example):
function prepared_query($mysqli, $sql, $params, $types = "")
{
$types = $types ?: str_repeat("s", count($params));
$stmt = $mysqli->prepare($sql);
$stmt->bind_param($types, ...$params);
$stmt->execute();
return $stmt;
}
If you're interesting in how it works you can read the explanation I wrote here.
Now let's rewrite your helper function based on mine
function verifyRow($mysqli, $sql, $parameters) {
$result = prepared_query($mysqli, $sql, $parameters)->get_result();
return (bool)$result->fetch_assoc();
}
Just two lines of code!
And now to verify the actual row:
var_dump(verifyRow($mysqli, "SELECT 1 FROM users WHERE code = ?", ['12345']));
One line instead of a dozen.
Now to the question why it doesn't find anything. There are two possible reasons.
indeed there is some misconfiguration.
a much simpler case: there is no such data in the table you are querying at the moment
To test for the former you must enable PHP error reporting. If your code doesn't work as expected then there must be an error somewhere and all you need is to catch it. Add these two lines to your code
error_reporting(E_ALL);
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
ini_set('log_errors',1); // well to be 100% sure it will be logged
in order to make sure that all PHP and mysql errors will present themselves. Then run the code again. If it won't produce any error then likely your system is configured properly.
Now to test your data. Write a different query that would return all rows from the table
$sql = "SELECT code FROM users WHERE 1 = ?";
$result = prepared_query($mysqli, $sql, [1])->get_result();
var_dump($result->fetch_all(MYSQLI_ASSOC));
and see what you really have in your database. Probably there are no rows or a different code value. A hint: there could be non-printable characters that would prevent the matching. to reveal them use a very handy function rawurlencode(): it will leave all basic characters as is but convert everything else into hex codes making them an easy spot
This question already has answers here:
Insert query on page load, inserts twice?
(3 answers)
Closed 2 years ago.
I usually don't post detailed versions of my code; however, in this case it may be necessary to figure out the problem. I have a class method that I cannot stop from executing twice. Is there any particular information I am missing on MySQLi prepared statements? I have read similar questions that asks the same to no avail.
I previously asked a question about using eval for dynamic queries in prepared statements as far as it's convention and best practices. I was told to use call_user_func_array() and it worked perfectly; however, I failed to notice that the statement executed twice each time, even with the old eval() code. So I put together a snippet of my ACTUAL code which should pretty much explain itself through my comments
function insert($table, $query)
{
/**
*
* This code assumes you have a MySQLi connection stored in variable $db
* USAGE: insert(table, array('field' => 'value');
*
**/
// Sets the beginning of the strings for the prepared statements
$fields = $values = "(";
$types = "";
$params = array();
foreach($query as $key => $val)
{
// array keys = fields, and array values = values;
$fields.= $key;
// concatenate the question marks for statement
$values.= "?";
// concatenate the type chars
$types.= is_string($val) ? "s" : (is_int($val) ? "i" : (is_double($val) ? "d" : "b"));
// pass variables to array params by reference for call_user_func_array();
$params[] = &$query[$key];
if($val == end($query))
{
$fields .= ")";
$values .= ")";
array_unshift($params, $types);
}
else
{
$fields .= ", ";
$values .= ", ";
}
}
$str = "INSERT INTO {$table} {$fields} VALUES {$values}";
if($stmt = $db->prepare($str))
{
call_user_func_array(array($stmt, 'bind_param'), $params);
/**
*
* This is where I am pulling my hair out of my head and being 3
* nothces away from banging my own head into the screen and
* being without a computer at all.
*
* I have tried everything I can think of. I gotta be missing
* something
*
* IT JUST KEEPS SENDING 2 ROWS DANG IT!
*
**/
/////////////////////
$stmt->execute();//// <---Help is needed here
/////////////////////
//-- Close connection;
$stmt->close();
}
else
{
//-- Send a nice readable error msg
die("<center><h3>FAULTY QUERY STRING</h3><h4>Please check query string</h4><p>{$str}</p>");
}
}
Changed code format from OOP to regular function for testing without having to create a class.
old question, but people might still stumble upon it, I did.
I think the mysqli_query function is mainly for retrieving data, I had the same problem, and fixed it by using mysqli_real_query on insert and update queries.
I am a php beginner.
I have the following script which works if I do not use _GET['version'] in the query, but works if I remove it. There is no error; I am not sure why it is not working.
<?php
// Specify your table name
$hostname = 'localhost';
$dbname = 'stats';
$table_name = 'st_stats';
$username = 'test';
$password = 'test';
try
{
$conn = new PDO("mysql:host=$hostname;dbname=$dbname",$username,$password);
//By default mode is silent and exception is not thrown. So I it to throw ex
$conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
// If the query is like this no error is given but page shows up blank
$stmt = $conn->query("SELECT * FROM $table_name where version = $_GET['version']", PDO::FETCH_ASSOC);
// This works if uncomment below line instead and comment line above
//$stmt = $conn->query("SELECT * FROM $table_name", PDO::FETCH_ASSOC);
$count = $stmt->rowCount();
echo("<h1>currently $count records</h1>");
}
catch(PDOException $e)
{
echo 'ERROR: ' . $e->getMessage();
}
?>
I want to access the page like this
http://www.mydomain/records.php?version=1.2
Note that version column does exit in the table
You could try to avoid a bit of sql injection here by preparing the statement properly:
$v_term = $_GET['version'];
$query = "SELECT * FROM $table_name where version = :term";
$result = $conn->prepare($query);
$result->bindValue(":term",$v_term);
$result->execute();
Also, run the statement straight from the db if you can to make sure you are getting records back. Other than that, there is no other way to debug this for you from what you given us.
Maybe version is not an integer therefore need quotes ?
"SELECT * FROM $table_name where verion = '".$_GET['version']."'",
Anyway you are vulnerable to sql injection and also misusing PDO
You should at least bindParam/bindValue
Or use execute() and past the $_GET value
As documented under Variable parsing:
There are two types of syntax: a simple one and a complex one.
[ deletia ]
Example #8 Simple syntax example
[ deletia ]
echo "He drank some $juices[koolaid1] juice.".PHP_EOL;
[ deletia ]
Complex (curly) syntax
This isn't called complex because the syntax is complex, but because it allows for the use of complex expressions.
[ deletia ]
// Works, quoted keys only work using the curly brace syntax
echo "This works: {$arr['key']}";
That is, you can reference associative arrays from within a double-quoted string in one of two ways:
// simple - don't quote your keys
"... $_GET[version] ..."
// complex - you may quote your keys, but must surround the expression in braces
"... {$_GET['version']} ..."
HOWEVER, you shouldn't be doing either here. You should instead be using a parameterised statement in order to prevent SQL injection attacks:
$stmt = $conn->prepare("SELECT * FROM $table_name WHERE verion = ?");
$stmt->execute([$_GET['version']]);
This doesnt work because you're trying to access $_GET['version'] an array variable within a string here
"SELECT * FROM $table_name where version = $_GET['version']", PDO::FETCH_ASSOC
placing {} around the variable will fix this one issue
$stmt = $conn->query("SELECT * FROM $table_name where verion = {$_GET['version']}", PDO::FETCH_ASSOC);
But you should also sanitize this value before you put it right int a sql statement
You have verion rather than version in your query. You're also not passing the value of $_GET['version'], you're passing the string "$_GET['version']" right into the query. Update your query to this:
$stmt = $conn->query("SELECT * FROM $table_name where version = {$_GET['version']}", PDO::FETCH_ASSOC);
Wrapping a variable that's inside a double quoted string ("") in curly braces ({}) evaluates to the value of the variable.
If you do this you will be wide open to SQL injection attacks. Be sure to sanitize the variable before you run the query, or better yet consider prepared statements.
I am trying to convert some old PHP ODBC queries over to PDO Prepared statements and am getting an error I cannot find too much information on.
The Error is:
"[DataDirect][ODBC Sybase Wire Protocol driver][SQL Server]There is no host variable corresponding to the one specified by the PARAM datastream. This means that this variable '' was not used in the preceding DECLARE CURSOR or SQL command. (SQLExecute[3801] at ext\pdo_odbc\odbc_stmt.c:254)"
I am searching for a single row in the database using a 6 digit ID that is stored in the database as a VARCHAR but is usually a 6 digit number.
The database connection is reporting successful.
The ID passed by the query string is validated.
The prepared statement results in the above error.
The backup straight ODBC_EXEC statement in the else clause returns the data I am looking for.
//PDO Driver Connect to Sybase
try {
$pdo = new PDO("odbc:Driver={Sybase ASE ODBC Driver};NA=server,5000;Uid=username;Pwd=password;");
$pdo_status = "Sybase Connected";
} catch(PDOException $e) {
echo 'Connection failed: ' . $e->getMessage();
}
if((isset($_GET['id'])) AND ($_GET['id'] != "")) {
//Validate ID String
if(!preg_match("/^[A-Za-z0-9]{5,7}/",$_GET['id'])) {
$query1_id = FALSE;
echo "Invalid ID";
exit;
} else {
$query1_id = $_GET['id'];
}
$query1 = $pdo->prepare("SELECT * FROM People WHERE PersonId= ?");
$query1->execute(array($query1_id));
if($query1->errorCode() != 0) {
$person_data = $query1->fetch(PDO::FETCH_ASSOC);
echo "Person Data from PDO: ";
print_r($person_data);
} else {
$errors = $query1->errorInfo();
echo $errors[2];
//Try the old way to confirm data is there.
$odbc_query1 = "SELECT * FROM People WHERE PersonId='$query1_id' ";
$person_result = odbc_exec($conn,$odbc_query1) or die("Error getting Data, Query 1");
$person_data = odbc_fetch_array($person_result);
echo "Person Data from ODBC_EXEC: ";
print_r($person_data);
}
It also fails if I use:
$query1 = $pdo->prepare("SELECT * FROM People WHERE PersonId= :id ");
$query1->execute(array(":id"=>$query1_id));
Does anyone have experience with this error?
Edit: Sybase Manual says this about the error...
Error 3801: There is no host variable corresponding to the one specified by the PARAM datastream. This means that this variable `%.*s' was not used in the preceding DECLARE CURSOR or SQL command.
Explanation:
Adaptive Server could not perform the requested action. Check your command for missing or incorrect database objects, variable names, and/or input data.
Which is odd because my error (quoted at the top) doesn't tell me which variable has no host.
Also fails if I use...
$query1 = $pdo->prepare("SELECT * FROM People WHERE PersonId= :id ");
$query1->bindParam(':id',$query1_id,PDO::PARAM_STR); //Or PARAM_INT
$query1->execute();
The query works if I place the variable in the query like this...
$query1 = $pdo->prepare("SELECT * FROM People WHERE PersonId= '$query1_id'");
So I think it has something to do with the parameter not being bound to the placeholder but I can't figure out why.
If I can't work this out I'll have to revert to building my query as a string and hoping my input validation is bullet proof.
Your problem seems to be with the default data type PHP assigns to variables in the placeholders. The SQL Statement is looking for a number but PHP is interpreting it as something else. You can prevent this using quotes around the placeholder variable. Notice that in the statements that work you have apostrophes ('') around the value that PHP sees:
$query1 = $pdo->prepare("SELECT * FROM People WHERE PersonId= '$query1_id'");
Try this when using the placeholder it should be the same:
$query1 = $pdo->prepare("SELECT * FROM People WHERE PersonId= ':id'");
What's the correct way to code the following
SELECT * FROM table WHERE value = $row['item']
$row['item'] echos correctly, but does not seem to work in the mysql query. Been having this problem for a few days. I've tried .$row['item']. and a few other variations but I must be doing something wrong.
The better more appropriate approach is to use mysqli and prepared statements ie:
$stmt = $mysqli->prepare("SELECT * FROM table WHERE value =?");
$stmt->bind_param("s",$row['item']); // I am assuming row['item'] is a string
$stmt->execute();
If you can't use mysqli or absolutely refuse to you can use this:
$query = "SELECT * FROM table WHERE value = '".mysql_real_escape_string($row['item'])."'";
The answer sort of depends on what is held within the $row['item'] variable. If it's a numeric value, then the query above should be fine. Since it's not working, I assume that the value of that variable is actually a string. In that case, you need to surround the value in quotes so that the database can correctly identify it as a string value (otherwise, it would just be gibberish "commands" that the database can't identify, causing the query to fail).
Regardless of the above, you shouldn't be directly inserting variables into a query under pretty much any circumstances. The reason is that it opens you up to SQL injection if you're not extremely careful. For example, if your $row['item'] variable was wrapped in single quotes in the query, but contained a single quote in its value, then the database would interpret the quote within the variable as the ending quote for the entire parameter, and it would screw up the query. Worse still, a hacker could take advantage of this to end your query entirely, then add a second query of his own making onto it (or they could introduce a UNION query on the end of the original, etc.). At the very least, you should be running something like mysql_real_escape_string() on the variable before using it:
$sql = "SELECT * FROM table WHERE value = " .
mysql_real_escape_string($row['item']);
The best way to get around this and secure your queries is to use prepared statements. These are queries that have placeholders in them instead of concatenated variables. You prepare the query with these placeholders, then you issue additional commands to the database to tell it what values to place in those placeholders. The database then takes care of the tricky issue of sanitizing these variables so that they don't cause any damage. You can use PDO for this:
try {
$dbh = new PDO(DB_DSN,
DB_USER,
DB_PASS,
array(PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION)
);
} catch (PDOException $e) {
echo "Connection failed: " . $e->getMessage();
exit();
}
// create query with a named placeholder
$sql = "SELECT * FROM table WHERE value = :value";
try {
$stmt = $dbh->prepare($sql);
// tell PDO to substitute the value in $row['item']
// for the named parameter specified above.
$stmt->bindValue(":value", $row['item']);
// execute the query and load the results into an array
$stmt->execute();
$records = $stmt->fetchAll();
} catch (PDOException $e) {
echo "Query failed: " . $e->getMessage();
exit();
}
foreach ($records as $record) {
// use db records
}
The way I usually recommend doing it is something like this:
$sql = sprintf("SELECT * FROM table WHERE value = '%s'",
mysql_real_escape_string($row['item']));
$item = mysql_real_escape_string($row['item']);
$mysqlQuery = "SELECT * FROM table WHERE value = '" . $item . "'";
you are missing single quotes
SELECT * FROM table WHERE value = '{$row['item']}'
PHP example