Using placeholders in MySQL 'COOPERATIVE' LOCK queries [duplicate] - mysql

This question already has answers here:
How to include a PHP variable inside a MySQL statement
(5 answers)
Closed 2 years ago.
I have used the so-called cooperative LOCK successfully in MySQL in the past. I had custom functions in my PHP code that ACQUIRE LOCK, RELEASE LOCK and CHECK whether lock is in effect. For example, to lock, I did something like :
//code
"SELECT GET_LOCK( 'unique_string', -1 ) AS acquired"
// code
return ( $row['acquired'] == 1);
And to unlock, I did something like:
// code
"SELECT RELEASE_LOCK( 'unique_string' ) AS released"
// code
return ( $row['released'] == 1 );
And lastly, to check if lock is in effect, I did:
//code
"SELECT IS_USED_LOCK( 'unique_string' ) AS connection_id"
//code
return ( NULL != $row['connection_id'] );
In all these cases, I used PDO::query() method ( i.e. no prepared statement) since my SELECT statements do not contain any parameters
Now, there is a need to concatenate input from the user to unique_string. Is there a way to make the unique_string contain a placeholder so as to use PDO::prepare() and avoid possible SQL injection attack?
Thanks.

There should be no issue at all in using a prepared statement, just use
$statement = PDO::prepare("SELECT GET_LOCK( ?, -1 ) AS acquired");
$statement->execute([$uniqueString]);
You can also used named params, would recommend looking into PDO's prepared statements docs.

Related

PDO query works but prepared statement does not [duplicate]

This question already has answers here:
How can I prevent SQL injection in PHP?
(27 answers)
Closed 2 months ago.
I have the written the following code which works fine and shows me the nickname:
$stmt2 = $pdo->query("SELECT nick FROM users WHERE ID=12");
$nn = $stmt2->fetch();
echo $nn["0"];
now I tried to do it as a prepared statement, so I can use different ID numbers. But it does not work, it display nothing.
$stmt3 = $pdo->prepare("SELECT nick FROM users WHERE ID=?");
$stmt3->execute(12);
$nn3 = $stmt3->fetch;
echo $nn3["0"];
I tried looking if I did something wrong but i simply can not see what is wrong the prepared statement.
You need to pass an array as the argument to the execute method when using a prepared statement. The argument should contain the values that you want to bind to the placeholders in the prepared statement. In your case, you need to pass an array containing the value of the ID you want to use as the parameter in the query.
$stmt3->execute([12]);
You are missing the () after fetch when trying to retrieve the result from the prepared statement. The fetch method returns a row from the result set as an array, so you need to call it like a function to retrieve the result.
$nn3 = $stmt3->fetch();
When accessing an element in the array returned by fetch, you need to use the key of the element, not its index. In this case, you can use the key "nick" to access the nickname.
echo $nn3["nick"];
Here's the corrected code:
$stmt3 = $pdo->prepare("SELECT nick FROM users WHERE ID=?");
$stmt3->execute([12]);
$nn3 = $stmt3->fetch();
echo $nn3["nick"];

Perl + DBI + MySQL: How To Run Multiple Queries/Statements [duplicate]

This question already has answers here:
Perl DBI - run SQL Script with multiple statements
(4 answers)
Closed 8 years ago.
At the moment, I am running multiple statements on MYSQL as below;
my $sth1 = $dbh->prepare("ALTER TABLE whatever....");
my $sth2 = $dbh->prepare("UPDATE whatever....");
my $sth3 = $dbh->prepare("ALTER TABLE whatever....");
my $sth4 = $dbh->prepare("DROP TABLE whatever....");
my $sth5 = $dbh->prepare("DROP TABLE whatever....");
$sth1->execute;
$sth1->finish;
$sth2->execute;
$sth2->finish;
$sth3->execute;
$sth3->finish;
$sth4->execute;
$sth4->finish;
$sth5->execute;
$sth5->finish;
This code works fine.
However, I have about more than 50 such queries. So you can imagine the magnitude of above lines. What I pasted above is just 5 queries.
Question:
Is there a better elegant way of running multiple MySQL queries/Statements using Perl DBI ?
At the very least, you should just iterate of your sql strings. Also would be a good idea to add or die to your execute methods:
my #sql = (
q{ALTER TABLE whatever....},
q{UPDATE whatever....},
q{ALTER TABLE whatever....},
q{DROP TABLE whatever....},
q{DROP TABLE whatever....},
);
for (#sql) {
my $sth = $dbh->prepare($_);
$sth->execute or die $dbh->errstr;
}
DBD::mysql has a parameter mysql_multi_statements:
As of MySQL 4.1, support for multiple statements separated by a semicolon (;) may be enabled by using this option. Enabling this option may cause problems if server-side prepared statements are also enabled.

How to run 'SELECT FOR UPDATE' in Laravel 3 / MySQL

I am trying to execute SELECT ... FOR UPDATE query using Laravel 3:
SELECT * from projects where id = 1 FOR UPDATE;
UPDATE projects SET money = money + 10 where id = 1;
I have tried several things for several hours now:
DB::connection()->pdo->exec($query);
and
DB::query($query)
I have also tried adding START TRANSACTION; ... COMMIT; to the query
and I tried to separate the SELECT from the UPDATE in two different parts like this:
DB::query($select);
DB::query($update);
Sometimes I get 0 rows affected, sometimes I get an error like this one:
SQLSTATE[HY000]: General error: 2014 Cannot execute queries while other unbuffered queries are active. Consider using PDOStatement::fetchAll(). Alternatively, if your code is only ever going to run against mysql, you may enable query buffering by setting the PDO::MYSQL_ATTR_USE_BUFFERED_QUERY attribute.
SQL: UPDATE `sessions` SET `last_activity` = ?, `data` = ? WHERE `id` = ?
I want to lock the row in order to update sensitive data, using Laravel's database connection.
Thanks.
In case all you need to do is increase money by 10, you don't need to lock the row before update. Simply executing the update query will do the job. The SELECT query will only slow down your script and doesn't help in this case.
UPDATE projects SET money = money + 10 where id = 1;
I would use diferent queries for sure, so you can have control on what you are doing.
I would use a transaction.
If we read this simple explanations, pdo transactions are quite straightforward. They give us this simple but complete example, that ilustrates how everithing is as we should expect (consider $db to be your DB::connection()->pdo).
try {
$db->beginTransaction();
$db->exec("SOME QUERY");
$stmt = $db->prepare("SOME OTHER QUERY?");
$stmt->execute(array($value));
$stmt = $db->prepare("YET ANOTHER QUERY??");
$stmt->execute(array($value2, $value3));
$db->commit();
}
catch(PDOException $ex) {
//Something went wrong rollback!
$db->rollBack();
echo $ex->getMessage();
}
Lets go to your real statements. For the first of them, the SELECT ..., i wouldn't use exec, but query, since as stated here
PDO::exec() does not return results from a SELECT statement. For a
SELECT statement that you only need to issue once during your program,
consider issuing PDO::query(). For a statement that you need to issue
multiple times, prepare a PDOStatement object with PDO::prepare() and
issue the statement with PDOStatement::execute().
And assign its result to some temp variable like
$result= $db->query ($select);
After this execution, i would call $result->fetchAll(), or $result->closeCursor(), since as we can read here
If you do not fetch all of the data in a result set before issuing
your next call to PDO::query(), your call may fail. Call
PDOStatement::closeCursor() to release the database resources
associated with the PDOStatement object before issuing your next call
to PDO::query().
Then you can exec the update
$result= $db->exec($update);
And after all, just in case, i would call again $result->fetchAll(), or $result->closeCursor().
If the aim is
to lock the row in order to update sensitive data, using Laravel's database connection.
Maybe you can use PDO transactions :
DB::connection()->pdo->beginTransaction();
DB::connection()->pdo->commit();
DB::connection()->pdo->rollBack();

Can PDO rowCount() after UPDATE query show difference between "no changes made" and "unexisting row"?

I'm doing an update query with PDO. I would like to figure out if my update query did not change anything in the database, since:
the passed values are the same as already present in the database. I know that rowCount() in such a case returns 0.
the row I'm trying to update does not exist in the database. As far as I can see, rowCount()in such cases also returns 0.
Am I forced to precede my UPDATE by a SELECT statement, to figure out if the record I'm trying to update does in fact exist? Or is there another common practice for this sort of thing.
I've been perusing through the documentation, but cannot find a conclusive answer:
http://php.net/manual/en/pdostatement.rowcount.php
I've come across this StackOverflow answer, that suggests that rowCount() might return NULL in some scenario's, but I don't think it's apliccable to my scenario:
see Why does PDO rowCount() return 0 after UPDATE a table without modifying the existing data?
From the comments in this question:
If the data hasn't been modified, the rowCount will be zero. If the
data was modified, the rowCount will be one or higher. If there was an
error, rowCount will be null or false or something non-zero.
UPDATE
I've found another question that gives an example of the proposition in the comments below:
Getting the insert and update ID with PDO
UPDATE2
Another question proposes another solution, via PDO::MYSQL_ATTR_FOUND_ROWS
PDO - check if row was updated?
You could add conditionals to your 'where' clause so such as " and ColumnToUpdate <> 'NewValue'"
I've solved it using the suggestions of #hjpotter92.
// UID is the unique ID of my table, autoincremented etc...
// Firstly, let's try to update my row
$query = 'UPDATE my_table SET x=0, y=1, uid=LAST_INSERT_ID(uid) WHERE z=2';
$sth = $dbh->prepare($query);
if($sth->execute()) {
if($dbh->lastInsertId() == 0) { // Record was not found, so insert it.
$query = 'INSERT INTO my_table (x,y) VALUES (0,1)';
$sth = $dbh->prepare($query);
$sth->execute();
if($sth->rowCount() > 0) {
echo $dbh->lastInsertId(); // Return the UID of the inserted row
}
}
}

What's the fastest way to check if a URL already exists in a MySQL table? [duplicate]

This question already has an answer here:
If url already exists in url table in mysql. Break operation in php script
(1 answer)
Closed 9 months ago.
I have a varchar(255) column where I store URL's in a MySQL database. This column has a unique index.
When my crawler encounters a URL, it has to check the database to see if that URL already exists. If it exists, the crawler selects data about that entry. If it does not exist, the crawler adds the url. I currently do this with the following code:
$sql = "SELECT id, junk
FROM files
WHERE url = '$url'";
$results = $this->mysqli->query( $sql );
// the file already exists in the system
if( $results->num_rows > 0 )
{
// store data to variables
}
// the file does not exists yet... add it
else
{
// insert new file
$sql = "INSERT INTO files( url )
VALUES( '$url' )";
$results = $this->mysqli->query( $sql );
}
I realize there are lots of ways to do this. I've read that using a MySQL if/else statement could speed this up. Can someone explain how MySQL would handle that differently, and why that may be faster? Are there other alternatives I should test? My crawlers are doing a lot of checking like this, and speeding up this process could be a significant speed boost for my system.
First of all, URLs are going to get much longer than varchar(256).
Second of all, because they're that long you don't want to do string compares, it gets very slow as the table grows. Instead, create a column with a hash value and compare that.
You should index the hash column, of course.
As for the actual insert, an alternative is to put a unique constraint on the hash. Then do your inserts blindly, allowing SQL to reject the dupes. (But you'll have to put an exception handler into your code, which has its own overhead.)
Considering not using transactions, to insert a new row if an old row does not exist by the WHERE condition, you can use:
"INSERT INTO files( url ) VALUES ( $url ) WHERE NOT EXISTS ( SELECT * FROM files WHERE url = $url );"
I can't think of a "one-line-commond" to select and insert at the same time.
I would do the insert first and check for success(affected_rows), then select. If you check first, and then do the insert, the possibility exists that the url got inserted during that small time window. And, you would need to add more code to handle this situation.