postgresql to_tsquery() with PDO's prepared statement - php

If write directly
$sql = "... to_tsquery( 'word_1 | word_2' )";
Works, but how to use this syntax with PDO's prepared statement?
What is right syntax for this:
$sql = " ... to_tsquery( :word_1 | :word_2 ) "; //this is incorrect
$sth = $db->prepare( $sql );
$sth->execute(
array( ':word_1'=>"word_1", ':word_2'=>"word_2" )
);

Like so:
denis=# select to_tsquery('(foo | bar) & baz');
to_tsquery
---------------------------
( 'foo' | 'bar' ) & 'baz'
(1 row)
denis=# select (to_tsquery('foo') || to_tsquery('bar')) && to_tsquery('baz');
?column?
---------------------------
( 'foo' | 'bar' ) & 'baz'
(1 row)

Related

peculiar behaviour of dynamically generated mysqli prepared statement

What started out yesterday as the beginnings of an answer for, or improvements to, code found in this question has now become something of an enigma. I cannot figure why the dynamically generated query produces different results to the equivilent when run in the mysqli client or even when written out in php as a standard prepared statement ( losing the dynamic element )
For anyone wishing to experiment I included the table schema and dummy records.
The dynamic query is built based upon existence of certain variables ( these would, in the original question, have been POST variables ) but here are static - though they can be negated by commenting them out which will in turn affect the WHERE clause that is generated.
<style>
body, body *{font-size:0.85rem;}
h1,h2{font-size:0.95rem;text-decoration:underline}
pre{white-space: pre-wrap;word-wrap: break-word;}
</style>
<?php
/*
to create a suitable table and populate with dummy data
create table `volopp` (
`id` int(10) unsigned not null auto_increment,
`taskname` varchar(50) not null default '0',
`industryname` varchar(50) not null default '0',
`country` varchar(50) not null default '0',
`orgname` varchar(50) not null default '0',
`photo` varchar(50) not null default '0',
primary key (`id`)
)
engine=innodb;
insert into `volopp` (`id`, `taskname`, `industryname`, `description`, `country`, `orgname`, `photo`, `status`) values
(1, 'squirrel juggling', 'outdoor pursuits', 'squirrels are members of the family sciuridae, a family that includes small or medium-size rodents. the squirrel family includes tree squirrels, ground squirrels, chipmunks, marmots, flying squirrels, and prairie dogs amongst other rodents.', 'scotland', 'squirrel jugglers association', 'squirrel.jpg', 1),
(2, 'hedgehog pickling', 'food and drink', 'a hedgehog is any of the spiny mammals of the subfamily erinaceinae, in the eulipotyphlan family erinaceidae. there are seventeen species of hedgehog in five genera found through parts of europe, asia, and africa, and in new zealand by introduction.', 'england', 'hog heaven association', 'hedgehog.jpg', 0),
(3, 'haggis hunting', 'outdoor pursuits', 'wild haggis is a creature of scottish folklore, said to be native to the scottish highlands. it is comically claimed to be the source of haggis, a traditional scottish dish that is in fact made from the innards of sheep.', 'scotland', 'campbell\'s haggis hunt', 'lesser-haggis.jpg', 1),
(4, 'nessie netting', 'outdoor pursuits', 'the loch ness monster or nessie is said to be a creature that inhabits loch ness in the scottish highlands. it is often described as large in size with a long neck and one or more humps protruding from the water.', 'scotland', 'nessienets.org.uk', 'loch-ness.jpg', 0),
(5, 'dwarf tossing', 'outdoor pursuits', 'dwarf-tossing, also called midget-tossing, is a pub/bar attraction in which people with dwarfism, wearing special padded clothing or velcro costumes, are thrown onto mattresses or at velcro-coated walls. participants compete to throw the person with dwarfism the farthest.', 'scotland', 'highlandgames.scot', 'highland-games.jpg', 1),
(6, 'stickleback stuffing', 'indoor pursuits', 'sticklebacks are small, elongated fishes that reach a maximum length of about 18 cm (7 inches). the members of the family are characterized by a row of 2 to 16 spines on the back, which are positioned in front of a soft-rayed dorsal fin.', 'wales', 'stickleback.org', 'spinysucker.jpg', 0),
(7, 'squirrel suckling', 'historic pastimes', 'tree rats', 'ireland', 'weirdness abounds', 'treerat.gif', 0);
For reference
+----+----------------------+-------------------+--------------------+----------+-------------------------------+--------------------+--------+
| id | taskname | industryname | description | country | orgname | photo | status |
+----+----------------------+-------------------+--------------------+----------+-------------------------------+--------------------+--------+
| 1 | squirrel juggling | outdoor pursuits | Squirrels are m... | scotland | Squirrel Jugglers Association | squirrel.jpg | 1 |
| 2 | Hedgehog Pickling | food and drink | A hedgehog is a... | england | Hog Heaven Association | hedgehog.jpg | 0 |
| 3 | Haggis Hunting | outdoor pursuits | Wild haggis is ... | scotland | Campbell's Haggis Hunt | lesser-haggis.jpg | 1 |
| 4 | Nessie Netting | outdoor pursuits | The Loch Ness M... | scotland | NessieNets.org.uk | Loch-Ness.jpg | 0 |
| 5 | Dwarf Tossing | outdoor pursuits | Dwarf-tossing, ... | scotland | highlandgames.scot | highland-games.jpg | 1 |
| 6 | Stickleback Stuffing | indoor pursuits | Sticklebacks ar... | wales | stickleback.org | spinyfucker.jpg | 0 |
| 7 | squirrel suckling | historic pastimes | tree rats... | ireland | weirdness abounds | treerat.gif | 0 |
+----+----------------------+-------------------+--------------------+----------+-------------------------------+--------------------+--------+
*/
/*
utility functions to try to assist debugging
*/
function pre( $data, $header ){
$title = $header ? sprintf( '<h1>%s</h1>', $header ) : '';
printf( '<pre>%s%s</pre>', $title, print_r($data,true) );
}
function preview_mysqli( $sql, $args ){
$index=0;
for( $i=0; $i < strlen( $sql ); $i++ ){
$char = substr( $sql, $i, 1 );
if( $char == '?' ) {
$param = $args[ $index ];
if( !strstr( $param, '"' ) )$param=sprintf( '"%s"', $param );
$sql = substr_replace( $sql, $param, $i, 1 );
$index++;
}
}
return preg_replace( '#(\t)#', ' ', $sql );
}
function varname( $var ){
foreach( $GLOBALS as $name => $value )if( $value === $var ) return $name;
return false;
}
/*
This writes out the equivilent php code one would use
to run the query manually without generating on the fly.
This produces different results but is, I'm sure, essentially
the same query being executed.
*/
function preview_mysqli_statement( $sql, $args ){
$types=array();
foreach( $args as $arg )$types[]=type( $arg );
$vars=array();
foreach( $args as $arg )$vars[]=sprintf( '$%s', varname( $arg ) );
return preg_replace('#\t#',' ',sprintf('
$sql="%s";
$stmt=$db->prepare( $sql );
$stmt->bind_param( "%s", %s );
$res=$stmt->execute();
if( $res ){
$res=$stmt->get_result();
while( $rs=$res->fetch_object() ){
pre( $rs, "-- record --" );
}
}',
$sql,
implode( '', $types ),
implode( ', ', $vars )
));
}
/*
utility to return string for the variable type
used when creating the statement binding
*/
function type($arg){
switch( gettype( $arg ) ){
case 'integer':
case 'int': return 'i';
case 'float':
case 'double': return 'd';
default: return 's';
}
}
/* standard db connection */
$dbhost = '127.0.0.1';
$dbuser = 'root';
$dbpwd = 'xxx';
$dbname = 'xxx';
$db = new mysqli( $dbhost, $dbuser, $dbpwd, $dbname );
/* parameters used to generate where clause. Comment out to negate and modify query */
$search_taskname='squirrel';
$search_industry='outdoor pursuits';
$search_country='scotland';
$search_orgname='association';
$search_description='folklore';
/* placeholders */
$where=array();
$args=array();
$types=array();
/* Clause joining verb : AND | OR */
$joiner='or';
/* create where clause conditions */
if( !empty( $search_taskname ) ){
$where[]='`taskname` like ?';
$search_taskname="%{$search_taskname}%";
$args[]=$search_taskname;
}
if( !empty( $search_industry ) ){
$where[]='`industryname`=?';
$args[]=$search_industry;
}
if( !empty( $search_country ) ){
$where[]='`country`=?';
$args[]=$search_country;
}
if( !empty( $search_orgname ) ){
$where[]='`orgname` like ?';
$search_orgname="%{$search_orgname}%";
$args[]=$search_orgname;
}
if( !empty( $search_description ) ){
$where[]='`description` like ?';
$search_description="%{$search_description}%";
$args[]=$search_description;
}
if( isset( $search_status ) ){
$where[]='`status`=?';
$args[]=$search_status;
}
if( count( $where )==0 )exit('bad foo: no "WHERE" clause parameters');
/* construct the full SQL statement with full where clauses */
$sql=sprintf('select id,taskname,industryname,country,orgname,photo,status
from `volopp`
%s;',
count( $where ) > 0 ? sprintf( 'where %s', implode( sprintf( ' %s ', $joiner ), $where ) ) : ''
);
$params=&$args;
$types=array();
foreach( $params as $param )$types[]=type( $param );
$types=implode( '', $types );
/* debug statements */
pre( preg_replace( '#\t#', ' ', $sql ), 'The generated sql statement to be passed to `prepare` method.' );
pre( preview_mysqli( $sql, $params ), 'Effective query: Can be run in mysql client' );
pre( preview_mysqli_statement( $sql, $params ), 'Generated php code to emulate the whole process. [copy]' );
$stmt=$db->prepare( $sql );
if( $stmt ){
$vars=array();
$vars[]=&$types;
foreach( $params as $param ) $vars[]=&$param;
$callback=array( $stmt, 'bind_param' );
call_user_func_array( $callback, $vars );
$result = $stmt->execute();
if( $result ){
$result=$stmt->get_result();
$i=1;
while( $rs=$result->fetch_object() ){
pre( $rs, sprintf(' --> Live record %d', $i ) );
$i++;
}
$stmt->free_result();
$stmt->close();
}
}
/*
if you copy the generated php code from debug output
and run... it should run and produce different
results.
*/
?>
The above screenshot shows 1 record returned when script is run with default parameters.
Running the generated PHP code version, as below, yields the same results as when run in the mysql client ( see above screenshot ).
<?php
$dbhost = '127.0.0.1';
$dbuser = 'root';
$dbpwd = 'xxx';
$dbname = 'xxx';
$db = new mysqli( $dbhost, $dbuser, $dbpwd, $dbname );
function pre( $data, $header ){
$title = $header ? sprintf( '<h1>%s</h1>', $header ) : '';
printf( '<pre>%s%s</pre>', $title, print_r($data,true) );
}
$search_taskname='squirrel';
$search_industry='outdoor pursuits';
$search_country='scotland';
$search_orgname='association';
$search_description='folklore';
/* clauses that use `like` */
$search_taskname="%{$search_taskname}%";
$search_orgname="%{$search_orgname}%";
$search_description="%{$search_description}%";
$sql="select id,taskname,industryname,country,orgname,photo,status
from `volopp`
where `taskname` like ? or `industryname`=? or `country`=? or `orgname` like ? or `description` like ?;";
$stmt=$db->prepare( $sql );
$stmt->bind_param( "sssss", $search_taskname, $search_industry, $search_country, $search_orgname, $search_description );
$res=$stmt->execute();
if( $res ){
$res=$stmt->get_result();
while( $rs=$res->fetch_object() ){
pre( $rs, "-- record --" );
}
}
?>
Sorry for the long post. If anyone can figure out what is going on I'd be happy to hear. No doubt I have missed something obvious and will feel stupid someone points out a glaring oops.
Your problem is in the segment...
if( $stmt ){
$vars=array();
$vars[]=&$types;
foreach( $params as $param ) $vars[]=&$param;
$callback=array( $stmt, 'bind_param' );
print_r($vars);
call_user_func_array( $callback, $vars );
The output of which is...
Array
(
[0] => sssss
[1] => %folklore%
[2] => %folklore%
[3] => %folklore%
[4] => %folklore%
[5] => %folklore%
)
To make this call easier, I would tend to use splat anyway...
if( $stmt ){
$stmt->bind_param($types, ...$params);
which gives you the output you want (I think).

Wordpress Form php code submitting blank values

This code is supposed to check to see if a user is logged in when posting an input, if so, it then checks to see if the ItemID and User ID already have an entry in the table, and if so, it updates that entry rather than creating a duplicate.
This is the output I need from it:
+--------+-------+----------+
| ItemID | Price | user |
+--------+-------+----------+
| 1 | 20 | 27 |
+--------+-------+----------+
This is what I'm getting:
+--------+-------+----------+
| ItemID | Price | user |
+--------+-------+----------+
| 0 | 0 | 27 |
+--------+-------+----------+
Here is the full function if needed : http://pastebin.com/W0UM68UT
if ( is_user_logged_in() ) {
$hf_user = get_current_user_id();
$hf_username = $hf_user->user_login;
global $quanid;
$inputValue = isset($_POST[$quanid]);
global $newdb;
$newdb = new wpdb( 'user', 'pass', 'db', 'localhost' );
$retval = $newdb->get_results("SELECT * FROM $table WHERE ItemID = '$quanid' AND user = '$hf_username' ");
if($retval > 0)
{
//If user exists, update
$newdb->replace($table,array(
'ItemID' => '$quanid',
'Price' => $inputValue,
'user' => $hf_username
)
);
}
else
{
global $table;
global $newdb;
$newdb->insert(
$table,
array(
'ItemID' => $quanid,
'Price' => $inputValue,
'user' => $hf_username
)
);
}
} else {
global $error;
$error = "Error: You must be logged in to submit prices";
return;
}
}
Please don't use globals...
How to avoid using PHP global objects?
Change your SELECT statement to a count for better performance:
SELECT count( 1 ) FROM $table WHERE ItemID = '$quanid' AND user = '$hf_username'
On to your question:
It appears your global $quanid; and isset($_POST[$quanid]); return unexpected values so you should see where they're set. Try using:
var_dump right below these two lines:
global $quanid;
$inputValue = isset($_POST[$quanid]);
var_dump( $quanid );
var_dump( $inputValue );
var_dump( $_POST);
die;

how to show values from mysql database without repeating values in php

When Iam showing database value from table its just repeating them according to content. First table which is content.
id | category_id | name
-----------------------------
1 | 2 | Jason
2 | 1 | Richard
3 | 2 | John
category table:-
id | name
---------
1 | Manager
2 | Employee
I use query:
$query=mysql_query("SELECT * FROM content");
while($row=mysql_fetch_array($query)){
$data[] = array('id'=> $row['id'],
'category_id' => $row['category_id'],
'name' => $row['name']
);
}
for($i=0;$i<count($data);$i++){
echo $data[$i]['category_id'];
echo $data[$i]['name']."<br/>";
}
OUTPUT:-
2 Jason
2 John
1 Richard
But what i want is:-
OUTPUT:-
2 Jason John
1 Richard
Now when echo out the result it shows me category_id 2 times having category_id=2 with its 2 name but what i want is to show only category_id 1 times and its corresponding 2 name of category_id=2. Please help.
Try this approach, it worked for me:
$query = mysql_query("SELECT COUNT(*), category_id, name,
GROUP_CONCAT(name separator ' ') as name FROM content
WHERE category_id=1 OR category_id=2 GROUP BY category_id
ORDER BY category_id DESC");
while($row=mysql_fetch_array($query)){
echo $row['category_id'] . " " . $row['name'] . "<br>";
}
Plus, just a quick note about mysql_* functions being deprecated.
Use mysqli with prepared statements, or PDO with prepared statements, they're much safer.
Use this query:
SELECT id, category_id, GROUP_CONCAT(name SEPARATOR ' ') AS name
FROM content
GROUP BY category_id
See here for some quick tutorials on how to use GROUP BY and GROUP_CONCAT:
w3schools.com / GROUP BY
mysqltutorial.org / GROUP_CONCAT
You shouldn't be using mysql as it is deprecated so to help you along I've done this with PDO to show its not as daunting as you may think.
PDO connection to database:
try{
$db = new PDO ('mysql:host=YourHost;dbname=YourDatabaseName', 'YourDatabaseUserName', 'YourDatabasePassword');
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
}catch(PDOException $e){
echo $e->getMessage();
die();
}
The above connection uses a a try catch with a way of making any errors cleaner and easier to read by making use of the getMessage() function which removes a lot of the un needed info.
The query you're after in PDO:
$sql = "SELECT GROUP_CONCAT(name SEPARATOR ' ') AS names FROM yourtable GROUP BY category_id";
$query = $db->query($sql);
$results = $query->fetchAll(PDO::FETCH_OBJ);
foreach($results as $result){
echo "<pre>", ($result->names), "</pre>";
}
This is the most basic way to use PDO to query your database but if you start using it like this you will find it easier to then develop the more in depth and secure ways of putting data in and getting data out of your database that help to prevent sql injection.
Hope this is a little food for thought and helps in getting you to move to a more secure API.
try this
select DISTINCT(*) from content
or you can be more specific with ie DISTINCT(columnname)
<?php
// Using MySQL GROUP_CONCAT
$sql = 'SELECT category_id, GROUP_CONCAT(name separator \' \') FROM content GROUP BY category_id';
// Or PHP reduce as below
// Dummy Records
$records = [
['id' => 1, 'category_id' => 2, 'name' => 'Jason'],
['id' => 2, 'category_id' => 1, 'name' => 'Richard'],
['id' => 3, 'category_id' => 2, 'name' => 'John']
];
$result = array_reduce($records, function($carry, $record) {
if (!array_key_exists($record['category_id'], $carry)) {
$carry[$record['category_id']] = [];
}
$carry[$record['category_id']][] = $record['name'];
return $carry;
}, []);
foreach ($result as $categoryId => $names) {
echo $categoryId . ' ' . implode(' ', $names) . "\n";
}

How to prevent mysql injection when using mysql IN clause without activeRecord in Yii?

I have an array with ids that I get from client. And I want use those ids in my sql query with IN clause. But this query goes on a table that has no model. So there is no active record (criteria) query possible.
** Table userTasks **
--------------------
| idUser | idTasks |
---------+----------
| 1 | 1 |
---------+----------
| 1 | 2 |
---------+----------
| 1 | 3 |
---------+----------
First approach does not work because params are always considered as strings. So :tasks is a string '1,2,3' instead of a comma separated list of ids:
$sql = 'SELECT COUNT(*) AS matches
FROM userTasks
WHERE idUser = :idUser
AND idTask IN (:tasks)';
$result = Yii::app()->db->createCommand($sql)
->queryRow(true,[
':idUser' => $idUser,
':tasks' => implode(',', $tasks)]); //$tasks is a simple array of ids [1,2,3]
So my workaround:
foreach($tasks as $task) //$tasks is a simple array of ids [1,2,3]
{
$inTasks[] = (int) $task;
}
$sql = 'SELECT COUNT(*) AS matches
FROM userTasks
WHERE idUser = :idUser
AND idTask IN (' . implode(',', $inTasks . ')';
$result = Yii::app()->db->createCommand($sql)
->queryRow(true,[':idUser' => $idUser]);
Having come across this problem a few times in my projects I have come-up with the following Yii work-around using CDbCriteria which is a little hacky, but gives the security of param count matching.
I would also use queryScalar() in this instance to get the result directly.
When applied to your example my code would be:
$idUser = 1;
$tasks = array(1,2,3);
$criteria = new CDbCriteria();
$criteria->addInCondition('idTask',$tasks);
$sql = '
SELECT COUNT(*) matches
FROM userTasks
WHERE idUser = :idUser
AND '.$criteria->condition;
$command = Yii::app()->db->createCommand($sql);
$command->bindValue('idUser',$idUser);
$command->bindValues($criteria->params);
$result = $command->queryScalar();
For preventing SQL injection in Yii IN clause we need to bind parameters in IN clause, Yii CDB criteria queries don't have this functionality in built. so you can use below code.
$products_ids = array(234,100,405,506);
map the array for binding
$in_query = implode(',', array_fill(0, count($products_ids), '?'));
Prepare the commadn object for select
$command = Yii::app()->db->createCommand()
->select('product_id, product_name, product_image, product_price')
->from('products')
->where('product_id IN(' . $in_query . ')');
bind the parameters
foreach ($products_ids as $k => $product_id){
$command->bindValue(($k+1),$product_id,PDO::PARAM_INT);
}
get the result
$products = $command->queryAll();

A general single sql query

I have a table like this:
id | roll_no | name
---------------------
1 | 111 | Naveed
2 | 222 | Adil
3 | 333 | Ali
If I have data like this:
$fields = array( "id" , "roll_no" ) and $values = array( "1,111", "2,222" );
It means I have to write a sql query to get records from table where (id != 1 and roll_no != 111) and (id != 2 and roll_no != 222). It means 3rd record will be fetched.
If I have data like this:
$fields = array( "id" ) and $values = array( "2", "3" );
It means I have to write a sql query to get records from table where (id != 2) and (id != 3). It means 1st record will be fetched.
Q: How to write a general single query using php to get data from table using above two data arrays.
Thanks
select * from dummy where concat_ws (',', id, roll_no) not in ('1,111', '2,222')
Complete solution:
$tableName = "test";
$fields = array( "id" , "roll_no" );
$values = array( "1,111", "2,222" );
$fieldsStr = implode(',', $fields);
$valuesStr = implode("','", $values);
$sql = "SELECT *
FROM $tableName
WHERE concat_ws(',', $fieldsStr ) NOT IN ( '$valuesStr' )";
You will probably always have to explode the Array in PHP and pass the values as a string into the query (sprintf) so you probably can, and should, do all in PHP.
One thing that catches my eye is that you are always using ID's. Are the ID's a unique or primary field? If so just forget about the roll_no as your query will be faster using just ID's.
Complete solution with the help of accepted answer.
$tableName = "test";
$fields = array( "id" , "roll_no" );
$values = array( "1,111", "2,222" );
$fieldsStr = implode(',', $fields);
$valuesStr = implode("','", $values);
// Get all records from remote table
$sql = "SELECT * FROM $tableName WHERE concat_ws(',', $fieldsStr ) NOT IN ( '$valuesStr' )";

Categories