What I'm trying to do is pass an array of content tags into my postgresql database query.
I've got this function:
public static function arrayToPdo($array, $pdocon)
{
foreach($array as &$val)
{
$val = $pdocon->quote($val);
}
return implode(",", $array);
}
So that converts the array into a string of quoted values.
I've echoed it and I get 'home','test' which are the tags I expected.
So then I want to bind the return value of that function in my PDO. Here's the where part of the query.
where #> array[:tags]
Now I've substituted the placeholder with the values 'home' and 'test' and it works so the problem lies with how the binding works I think.
When I do the following it works fine:
"where #> array[" . arrayToPdo($array, $conn) . "]"
So here's the code where I bind the value:
$st->bindvalue(":tags", arrayToPdo($array, $conn), PDO::PARAM_STR);
I have another value being bound correctly just before so I feel like it should work.
I've been messing with it for a day and a half and I can't figure it out.
Basically when I enter the quoted values into the query string it works; when I concatenate the function into the query string it works; but when I bind it it doesn't.
There are no errors, I only know it is not working because the query doesn't return any records. I haven't found a way to see what is being inserted into the query string when it is executed so I'm not sure what the problem is.
Any help here would be much appreciated.
In array[:tags], :tags is a set of values, not a single value. Only single values can be bound.
What you probably want here is to use a syntax for an array that represents it as a single value.
Here: PHP array to postgres array
you'll find a function that converts a PHP array into such a syntax.
Then the clause in your query should look like:
"... where #> cast(:myarray as text[])"
Note that there is no array constructor, it's a cast.
Bind it as
$st->bindvalue(":tags", to_pg_array($array), PDO::PARAM_STR);
making sure that PDO is configured to use real prepared statements:
$db->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
(otherwise I suspect that PDO could mess when requoting the parameter. With true prepared statements, parameters are passed by the driver outside of the query and are not quoted).
Related
In PHP, I pull a large amount of JSON data from a URI, then serialize it into an associative PHP array via the built-in json_decode function.
Then, I create an array:
$inserts = array();
I loop through the JSON associative array, adding a new key/value pair to my $inserts array for each item in the JSON array:
foreach($JSON_data as $key => $value) {
$inserts[] = "(".mysql_real_escape_string($value["prop1"]).","
.mysql_real_escape_string($value["prop2"]).","
.mysql_real_escape_string($value["prop3"]).")";
}
Then, I perform a bulk insert simply by imploding the inserts I already prepared:
mysql_query("INSERT INTO `MyTable` (`col1`,`col2`,`col3`) VALUES ".implode(",",$inserts));
Anyways, I found that the mysql_* family is no longer suggested to be used. So I'm wondering how this type of pattern is suppose to be accomplished using prepared statements or w/e the new accepted constructs are? My concerns are to eliminate SQL injection, and also to update MySQL as quickly as possible with fewer than 10 concurrent, open connections (preferably 1). Also, to keep things as simple and quick as possible.
Or, if there's a new pattern or preferred method to perform such a bulk transaction.
If you use a prepared statement, you can loop over your $JSON_data array with a foreach loop and run the INSERT with that chunk of the data.
Using prepared statements will reduce the overhead of building the query, simply sending the new data to the database on each iteration of the loop.
$query = mysqli_prepare("INSERT INTO `MyTable` (`col1`,`col2`,`col3`)
VALUES(?,?,?)");
foreach($JSON_data as $key => $value) {
$query->bind_param('sss',$value["prop1"],$value["prop2"],$value["prop3"];
$query->execute();
}
Note that the first argument to bind_param() tells it how many values you will be binding, as well as the type for each value.
s corresponds to string data, i corresponds to integer data, d corresponds to double (floating point), and b corresponds to binary data.
One other word of caution, do NOT quote any string data, as the s datatype tells mysql to expect a string. If you quote the ? in the prepared statement, it will tell you the number of params is wrong. If you quote the strings, it will be quoted in mysql.
EDIT:
If you want to use the same paradigm (inserting multiple rows with one query), there are ways to do it. One way is to create a class that will aggregate the bind_param calls and do one bind_param when you execute the query. Code for that is here.
Use Mysqli or PDO
Here is how you would utilize prepared statements with Mysqli
<?php
//Basic layout to using parametized queries in PHP to prevent Bobby-tables
$VARIABLE = "Some Data";
$mysqli = new mysqli("SERVER","USER","PASSWORD","DATABASE");
$query = $mysqli->prepare("SELECT COLUMN_LIST FROM TABLE WHERE COLUMN = ?");
$query->bind_param('s',$VARIABLE); //'s' for string, use i for int d for double
$query->execute();
//Get results
$query->bind_result($VARIABLE_NAMES_MATCHING_COLUMN_NAMES_GO_HERE);
$query->fetch();
echo $VARIABLE_LIST_MATCHING_COLUMN_LIST;
?>
This question already has answers here:
Use an array in a mysqli prepared statement: `WHERE .. IN(..)` query [duplicate]
(8 answers)
Build SELECT query with dynamic number of LIKE conditions as a mysqli prepared statement
(2 answers)
Closed 11 months ago.
I'm currently facing a difficulty where putting a comma-separated values to a MySQL NOT IN doesn't give me the result I was hoping for. There must be something I'm missing as I'm unsure what to search for this particular problem. Running only the MySQL code works, but passing the parameter from another PHP function didn't.
Here's the code that's giving me a problem:
$uid = 1;
$selected_uids = '1,2';
$result = $db->retrieveFollowingWithCondition($uid, $selected_uids);
...then somewhere along the code...
public function retrieveFollowingWithCondition($uid, $selected_uids) {
$stmt = $this->conn->prepare("SELECT *
FROM `friendlist`
WHERE `uid` = ? AND `buddy_uid` NOT IN (?)
GROUP BY `buddy_uid`;");
$stmt->bind_param("is", $uid, $selected_uids);
...}
I've tested just putting '2' in $selected_uids and it actually works. But once there's comma involved, the code runs but the $selected_uids are still in the result. Not sure this is a bad practice or just needing a minor adjustment to the code. Anyway, I'm really looking forward to understand why it's not working for me.
By using s in bind_param you are telling PHP to treat the entire contents of $selected_uids as a string. Therefore, "1,2" is treated as ('1,2') instead of (1,2). Your problem is that bind_param doesn't support arrays, so support of IN queries is limited. There are a number of alternatives to get around this limitation, but since you are dealing with a list of ints, I would probably do a raw string concat.
// using is_numeric because is_int("1") === false
$filtered = array_filter('is_numeric', $selected_uids);
// You could also just call array_map('intval', $selected_uids);
// Depending on your needs.
if(!$filtered) {
return; // No valid values
}
$filteredStr = implode(',', $filtered);
$stmt = $this->conn->prepare("SELECT *
FROM `friendlist`
WHERE `uid` = ? AND `buddy_uid` NOT IN ($filteredStr)
GROUP BY `buddy_uid`;");
Should also be noted: if I were trying to use strings for an IN query, I would likely do the following:
$filtered = array_map([$this->conn, 'escape_string'], $queried);
$inQuery = '\'' . implode('\',\'', $filtered) . '\'';
I find that notation cleaner and easier than a dynamically generated bind_param format string.
You should bind every parameter in IN(...) separately, but method bind_param doesn't support multiple calls. There is a nice class that can do this and you can find it on PHP documentation pages:
Custom class for multiple bind_param
I want to make a "dynamic" WHERE clause in my query based on a array of strings. And I want to run the created query using Mysqi's prepared statements.
My code so far, PHP:
$searchArray = explode(' ', $search);
$searchNumber = count($searchArray);
$searchStr = "tags.tag LIKE ? ";
for($i=1; $i<=$searchNumber-1 ;$i++){
$searchStr .= "OR tags.tag LIKE ? ";
}
My query:
SELECT tag FROM tags WHERE $searchStr;
More PHP:
$stmt -> bind_param(str_repeat('s', count($searchArray)));
Now this obviously gives me an error since the bind_param part only contains half the details it need.
How should I proceed?
Are there any other (better) way of doing this?
Is it secure?
Regarding the security part of the question, prepared statements with placeholders are as secure as the validation mechanism involved in filling these placeholders with values up. In the case of mysqli prepared statements, the documentation says:
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 clearly excludes any possibility of modifying the general semantic of the query, which makes it much harder (but not impossible) to divert it from its original intent.
Regarding the dynamic part of your query, you could use str_repeat in the query condition building part, instead of doing a loop:
$searchStr = 'WHERE tags.tag LIKE ?' .
str_repeat($searchNumber - 1, ' OR tags.tag LIKE ?');
For the bind_param call, you should use call_user_func_array like so:
$bindArray[0] = str_repeat('s', $searchNumber);
array_walk($searchArray,function($k,&$v) use (&$bindArray) {$bindArray[] = &$v;});
call_user_func_array(array($stmt,'bind_param'), $bindArray);
Hopefully the above snippet should bind every value of the $bindArray with its corresponding placeholder in the query.
Addenum:
However, you should be wary of two things:
call_user_func_array expects an integer indexed array for its second parameter. I am not sure how it would behave with a dictionary.
mysqli_stmt_bind_param requires its parameters to be passed by reference.
For the first point, you only need to make sure that $bindArray uses integer indices, which is the case in the code above (or alternatively check that call_user_func_array doesn't choke on the array you're providing it).
For the second point, it will only be a problem if you intend to modify the data within $bindArray after calling bind_param (ie. through the call_user_func_array function), and before executing the query.
If you wish to do so - for instance by running the same query several times with different parameters' values in the same script, then you will have to use the same array ( $bindArray) for the following query execution, and update the array entries using the same keys. Copying another array over won't work, unless done by hand:
foreach($bindArray as $k => $v)
$bindArray[$k] = some_new_value();
or
foreach($bindArray as &$v)
$v = some_new_value();
The above would work because it would not break the references on the array entries that bind_param bound with the statement when it was called earlier. Likewise, the following should work because it does not change the references which have been set earlier up.
array_walk($bindArray, function($k,&$v){$v = some_new_value();});
A prepared statement needs to have a well-defined number of arguments; it can't have any element of dynamic functionality. That means you'll have to generate the specific statement that you need and prepare just that.
What you can do – in case your code actually gets called multiple times during the existence of the database connection - is make cache of those prepared statements, and index them by the number of arguments that you're taking. This would mean that the second time you call the function with three arguments, you already have the statement done. But as prepared statements don't survive the disconnect anyway, this really only makes sense if you do multiple queries in the same script run. (I'm deliberately leaving out persistent connections, because that opens up an entirely different can of worms.)
By the way, I'm not an MySQL expert, but would it not make a difference to not have the where conditions joined,but rather writing WHERE tags in (tag1, tag2, tag3, tag4)?
Solved it by the help of an answer found here.
$query = "SELECT * FROM tags WHERE tags.tag LIKE CONCAT('%',?,'%')" . str_repeat(" OR tags.tag LIKE CONCAT('%',?,'%')", $searchNumber - 1)
$stmt = $mysqli -> prepare($query);
$bind_names[] = str_repeat('s', $searchNumber);
for ($i = 0; $i < count($searchArray); $i++){
$bind_name = 'bind'.$i; //generate a name for variable bind1, bind2, bind3...
$$bind_name = $searchArray[$i]; //create a variable with this name and put value in it
$bind_names[] = & $$bind_name; //put a link to this variable in array
}
call_user_func_array(array($stmt, 'bind_param'), &$bind_names);
$stmt -> execute();
Where and when do you use the quote method in PDO? I'm asking this in the light of the fact that in PDO, all quoting is done by the PDO object therefore no user input should be escaped/quoted etc. This makes one wonder why worry about a quote method if it's not gonna get used in a prepared statement anyway?
When using Prepared Statements with PDO::prepare() and PDOStatement::execute(), you don't have any quoting to do : this will be done automatically.
But, sometimes, you will not (or cannot) use prepared statements, and will have to write full SQL queries and execute them with PDO::exec() ; in those cases, you will have to make sure strings are quoted properly -- this is when the PDO::quote() method is useful.
While this may not be the only use-case it's the only one I've needed quote for. You can only pass values using PDO_Stmt::execute, so for example this query wouldn't work:
SELECT * FROM tbl WHERE :field = :value
quote comes in so that you can do this:
// Example: filter by a specific column
$columns = array("name", "location");
$column = isset($columns[$_GET["col"]]) ? $columns[$_GET["col"]] : $defaultCol;
$stmt = $pdo->prepare("SELECT * FROM tbl WHERE " . $pdo->quote($column) . " = :value");
$stmt->execute(array(":value" => $value));
$stmt = $pdo->prepare("SELECT * FROM tbl ORDER BY " . $pdo->quote($column) . " ASC");
and still expect $column to be filtered safely in the query.
The PDO system does not have (as far as I can find) any mechanism to bind an array variable in PHP into a set in SQL. That's a limitation of SQL prepared statements as well... thus you are left with the task of stitching together your own function for this purpose. For example, you have this:
$a = array(123, 'xyz', 789);
You want to end up with this:
$sql = "SELECT * FROM mytable WHERE item IN (123, 'xyz', 789)";
Using PDO::prepare() does not work because there's no method to bind the array variable $a into the set. You end up needing a loop where you individually quote each item in the array, then glue them together. In which case PDO::quote() is probably better than nothing, at least you get the character set details right.
Would be excellent if PDO supported a cleaner way to handle this. Don't forget, the empty set in SQL is a disgusting special case... which means any function you build for this purpose becomes more complex than you want it to be. Something like PDO::PARAM_SET as an option on the binding, with the individual driver deciding how to handle the empty set. Of course, that's no longer compatible with SQL prepared statements.
Happy if someone knows a way to avoid this difficulty.
A bit late anwser, but one situation where its useful is if you get a load of data out of your table which you're going to put back in later.
for example, i have a function which gets a load of text out of a table and writes it to a file. that text might later be inserted into another table. the quote() method makes all the quotes safe.
it's real easy:
$safeTextToFile = $DBH->quote($textFromDataBase);
I'm trying to use odbc_prepare and odbc_execute in PHP as follows:
$pstmt=odbc_prepare($odb_con,"select * from configured where param_name='?'");
$res=odbc_execute($pstmt,array('version'));
var_dump($res); //bool(true)
$row = odbc_fetch_array($pstmt);
var_dump($row); //bool(false)
The first var_dump returns true so the execute succeeds, but there is no row returned. A row does indeed exist with the param_name = 'version'. Why is no row returned?
To make things interesting, I ran another very simple example in php using a prepared insert.
$pstmt=odbc_prepare($odb_con,"insert into tmp1 values(?,'?')");
This line, by itself, inserted a row into the database!! Surely this is just wrong? The data entered was col 1 = blank, col 2 = ?
Any advice on where to start fixing this would be appreciated, thanks.
Edit: This is in PHP 5.2.8
Try removing the single quotes from the query string and adding them to the parameter value itself:
$pstmt=odbc_prepare($odb_con,"select * from configured where param_name=?");
$res=odbc_execute($pstmt,array(" 'version'"));
var_dump($res); //bool(true)
$row = odbc_fetch_array($pstmt);
var_dump($row); //bool(false)
The single space character at the beginning of the parameter value is very important--if the space is not there, it will treat the variable as a path to a file.
From http://www.php.net/manual/en/function.odbc-execute.php:
If you wish to store a string which
actually begins and ends with single
quotes, you must add a space or other
non-single-quote character to the
beginning or end of the parameter,
which will prevent the parameter from
being taken as a file name.
when I read this paragraph
Any parameters in parameter_array which start and end with single quotes will be taken as the name of a file to read and send to the database server as the data for the appropriate placeholder.
If you wish to store a string which actually begins and ends with single quotes, you must add a space or other non-single-quote character to the beginning or end of the parameter, which will prevent the parameter from being taken as a file name. If this is not an option, then you must use another mechanism to store the string, such as executing the query directly with odbc_exec()).
It seems to me that it isn't necessary to add single quotes ' to a string, only if you really want to have the quotes as text in the DB
Therefore if I only want to insert the text, without the single quotes I would write something like that ...
see this example from odbc-prepare
http://www.php.net/manual/en/function.odbc-prepare.php
Use this example for IBM DB/2:
$q = "update TABLE set PASS=? where NAME=?";
$res = odbc_prepare ($con, $q);
$a = "secret"; $b="user";
$exc = odbc_execute($res, array($a, $b));
This would result in the following statement
$pstmt=odbc_prepare($odb_con,"select * from configured where param_name=?");
$name = "version";
$params = array($name);
$res=odbc_execute($pstmt,$params);
var_dump($res); //bool(true)
$row = odbc_fetch_array($pstmt);
var_dump($row); //bool(false)
See that I not only removed the qoutes for the value in the params array but also removed the qoutes in the SQL statement.
please give feedback if this was right
You should not enclose variables in quotes in a prepared statement:
$pstmt=odbc_prepare($odb_con,"select * from configured where param_name=?");
$res=odbc_execute($pstmt,array(" 'version'"));
should be:
$pstmt=odbc_prepare($odb_con,"select * from configured where param_name=?");
$res=odbc_execute($pstmt,array("version"));
Question marks represent parameter placeholders, the value passed is meant to represent an unescaped, unenclosed value, which will be properly escaped by the SQL interpreter.
EDIT:
Gah, ignore me, misread php.net
odbc_fetch_array accepts as it's parameter the result of odbc_execute, you seem to be passing in the prepared statement.
What DBMS are you using? The fact that the lone insert prepare statement seems to be executed against the database rather than being prepared points to either a poor implementation of php (unlikely) or the DBMS not supporting prepared sql. If the latter is the case it is possible that their way of supporting the command with out the functionality is just to execute the statement leading to the results you get. If the DBMS does support prepared statements and the php implementation handles it properly there is some kind of issue with the insert being executed which also needs some investigation.
Did you try using double quotes? i.e.
$res=odbc_execute($pstmt,array("version"));