PDO error checking for SQL queries - php

I am trying to add more error outputs to my code as it is growing. But I am not sure how deep I need to go. For instance, if I send a PDO DELETE FROM command to a table, is it necessary for me to then query the table again to see if the row was deleted (and confirm to the user)? Or should I rely on error handling, as in, if there was no error, it was definitely successful.
Thanks.

You should wrap every SQL statement in a try and catch block to see if any SQL exceptions or any other exceptions occured.
Depending on how you execute your PDO queries you have two options.
You can execute a statement directly with the PDO class using the exec() function. This function will return the number of affected rows for each query. However this function does not return the number of affected rows for an SELECT query.
So for delete queries you would use
$connection = new PDO($dsn, $username, $password);
$affectedRows = $connection->exec('DELETE from users WHERE id = 1);
This way affectedRows will probably be 1 or 0 if the user doesn't exist. If any other errors occur the catch part of the try-catch block will make sure to fetch these.
If you want to execute an SELECT query and find out if it was successful I would recommend doing this
$connection = new PDO($dsn, $username, $password);
$statement = $connection->prepare(SELECT * FROM users);
$statement->execute();
$returnedUsers = $statement->fetchAll();
As you can see, calling the method $connection->prepare() will return a statement which is an object of the PDOStatement class.
If you have parameters set in the prepare query you would want to set these through the bindParam() methods of the PDOStatement class.
Keep in mind that when it's neccesary to execute multiple similar queries in one go use an object of PDOStatement and then set different parameters using the bindParam() method.

Related

Cannot connect to Snowflake Database [PHP]

The structure of the table I'm trying to reach is as such:
Database: INTERNAL_STUFF
Schema: INTERNAL_TEST
Table: TEST_TABLE_SIMPLE
I create a PDO as such:
$dbh = new PDO("snowflake:account=$this->account", $this->user, $this->password);
$dbh->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION );
If I use this query:
$result = $dbh->query("SELECT * FROM INTERNAL_STUFF.INTERNAL_TEST.TEST_TABLE_SIMPLE");
I end up getting this response - 'Schema 'INTERNAL_STUFF.INTERNAL_TEST' does not exist or not authorized.'. So it appears to be treating the database and the schema as just the schema.
If I use the same query but drop the database from the front:
$result = $dbh->query("SELECT * FROM INTERNAL_TEST.TEST_TABLE_SIMPLE");
I end up getting this response - 'SQLSTATE[22000]: Data exception: 90105 Cannot perform SELECT. This session does not have a current database. Call 'USE DATABASE', or use a qualified name.'
What am I doing wrong here? My user has access to the correct role to view the table, and that exact query (the longer of the two) works just fine in a Snowflake Worksheet.
You may also set the default namespace(database & Schema) for your user using Alter User statement.
Details: https://docs.snowflake.com/en/sql-reference/sql/alter-user.html#usage-notes
Can you execute query USE DATABASE INTERNAL_STUFF and next query USE SCHEMA INTERNAL_TEST before you execute your main Sql query.

what does sqlsrv_query return for ALTER INDEX all ON tbl REORGANIZE query and why does sqlsrv_num_rows return FALSE?

I've created a pull request to add a Database\ResultInterface::getNumRows() function to CodeIgniter 4. Some changes I've made have broken a unit test. Specifically, an SQLSRV table optimization test is barfing because this framework tries to fetch a result array from the SQLSRV response to this table optimization query:
ALTER INDEX all ON db_job REORGANIZE
It's also a bit tricky to trace all the code being called that leads to this, but here's an exception:
1) CodeIgniter\Database\Live\DbUtilsTest::testUtilsOptimizeDatabase
CodeIgniter\Database\Exceptions\DatabaseException: Error retrieving row count
/home/runner/work/CodeIgniter4/CodeIgniter4/system/Database/SQLSRV/Result.php:198
/home/runner/work/CodeIgniter4/CodeIgniter4/system/Database/BaseResult.php:193
/home/runner/work/CodeIgniter4/CodeIgniter4/system/Database/BaseUtils.php:177
/home/runner/work/CodeIgniter4/CodeIgniter4/tests/system/Database/Live/DbUtilsTest.php:105
Dissecting that, we see that the DBUtilsTest line 105 calls the BaseUtils::optimizeTable function with a table name, "db_job", and that uses the SQLSRV\Utils::optimizeTable var to construct the query I provided above. This sql is then fed to a db object that calls CodeIgniter\Database\SQLSRV\Connection::query() which, through inheritance and various function calls routes that SQL through BaseConnection::query() which feeds the sql to BaseConnection::simpleQuery which hands the sql to SQLSRV\Connection::execute which finally feeds the sql to sqlsrv_query and returns the result of that function back up through the call stack. So I guess this brings me to my first question:
Question 1: What is the value of $stmt if this ALTER command a) succeeds or b) fails?:
$stmt = sqlsrv_query($connID, 'ALTER INDEX all ON db_job REORGANIZE');
According to the sqlsrv_query documentation, this function:
Returns a statement resource on success and false if an error occurred.
Whatever that value is gets returned back up the call stack to line 625 of BaseConnection::query where, if successful, it is stored as $this->resultID and fed as the second parameter to the constructor at line 676 which effectively returns new SQLSRV\Result($this->connID, $this->resultID). To be clear, connID refers to the SQLSRV db connection and resultID refers to whatever value of $stmt was returned by the sqlsrv_query call above.
The resulting $query variable is an instance of system\Database\SQLSRV\Result in this function:
public function optimizeTable(string $tableName)
{
if ($this->optimizeTable === false)
{
if ($this->db->DBDebug)
{
throw new DatabaseException('Unsupported feature of the database platform you are using.');
}
return false;
}
$query = $this->db->query(sprintf($this->optimizeTable, $this->db->escapeIdentifiers($tableName)));
if ($query !== false)
{
$query = $query->getResultArray();
return current($query);
}
return false;
}
An instance of SQLSRV\Result will not be false so that code will attempt to call SQLSRV\Result::getResultArray which through inheritance calls BaseResult::getResultArray. This seems wrong to try to getResultArray from a query that optimizes a table or any sort of ALTER query, however an MySQL server will return a series of records in response to an OPTIMIZE query. Also, the sqlsrv_query function we just ran is just returning some of sqlserver statement or resource as its result.
I guess this brings me to my second question:
Question 2: How does one tell from the $stmt result of sqlsrv_query whether the ALTER statement above succeeded?
Curiously, none of these pecularities caused any problem before. The problem appears to have arisen because I've deprecated an unused BaseResult::numRows property that was never being set in the old getResultArray code and have replaced it and all references to it with a new getNumRows method. In my new BaseResult::getResultArray function we now check getNumRows instead of numRows. This works fine for MySQLi, SQLite3, and PostGreSQL, but barfs in SQLSRV because the aforementioned $stmt result of the sqlsrv_query ALTER statement gets fed to sqlsrv_num_rows and returns false, which signifies an error according to the docs. Here's the code for SQLSRV\Result::getNumRows function:
public function getNumRows() : int
{
// $this->resultID contains the sqlsrv_query result of our ALTER statement
// and $retval comes up false
$retval = sqlsrv_num_rows($this->resultID);
if ($retval === false)
{
throw new DatabaseException('Error retrieving row count');
}
return intval($retval);
}
This brings me to:
Question 3: would it ever make any sense to try and fetch results from or call sqlsrv_num_rows on the result of an ALTER INDEX query?

Call to mysqli::query and mysqli::execute don't return errors, but aren't executed in the database

I'm trying to execute a DROP TEMPORARY TABLE statement. I've run the statement using MySQL Workbench and it works there. I've made sure I'm successfully logged in as the same user in both PHP and MySQL Workbench.
I've tried using both mysqli::query() and mysqli::prepare then mysqli::execute() methods. Neither works when I try to execute the DROP TEMPORARY TABLE statement. However, both work when I execute either an INSERT or SELECT statement.
This code doesn't return error, but also doesn't execute in the database:
public function executeSQL()
{
// SQL statement using query method
if (!$this->mySQLi->query("DROP TEMPORARY TABLE IF EXISTS TIME_INTERVAL_DATA") )
{
$this->writeSQLErrorToLog("Query method returned an error.");
}
if(!$this->mySQLi->commit())
{
// Committing the transaction failed.
$this->writeSQLErrorToLog("Commit method returned an error.");
}
// SQL statement using prepare and execute methods
$stmt = $this->mySQLi->prepare("DROP TEMPORARY TABLE IF EXISTS TIME_INTERVAL_DATA");
if($stmt)
{
if(!$stmt->execute())
{
// Execute method returned an error.
$this->writeSQLErrorToLog("Execute method returned an error.");
$this->throwMySQLErrorAndCloseStatement($stmt);
}
} else {
// Prepare method returned an error.
$this->writeSQLErrorToLog("Prepare method returned an error.");
return;
}
// ...
}
However, the following code does execute in the database:
$stmt = $this->mySQLi->prepare("INSERT INTO TABLE1 (ID) VALUES (1)");
if(!$stmt->execute()) {
// Return the MySQL Error message and number
$this->throwMySQLErrorAndCloseStatement($stmt);
}
I've been reading as many of the MySQL and PHP docs as I can find. I've read and re-read the docs on all of the above mysqli methods. And I've read about and tried variations of using mysqli::escape_string(), but when I use it an error is returned. I've also tried many variations of different SQL statements.
In a nutshell, all of the INSERT and SELECT statements work. But, the DROP TEMPORARY TABLE statement never works and never returns an error. I'm really scratching my head on this one. Any help would be much appreciated!!

PHP PDO Transaction with nested PHP check

i have to do 2 MySql-queries:
SELECT id FROM X WHERE [...]
INSERT [...]
The second query should only be executed, if the first query returns an correct id.
Is it possible, to mix PHP conditions between both queries?
Eg.
try
{
$dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$dbh->beginTransaction();
$stmt = $dbh->prepare("SELECT id FROM [...]");
$stmt->bindParam(1, [...]);
if($stmt->execute())
{
if($row = $stmt->fetch())
{
$matchID = $row['id'];
$checkD = $this->checkId($matchID);
if($checkD)
{
return '-1';
}
else
{
$stmt = $dbh->prepare("INSERT INTO [...]");
$stmt->bindParam(1,[...]);
$stmt->execute();
stmt = $dbh->prepare("DELETE [...]");
$stmt->bindParam(1,[...]);
$stmt->execute();
$dbh->commit();
return $matchID;
}
}
else
{
return '-1';
}
}
else
{
return '-1';
}
} catch(Exception $e)
{
$dbh->rollBack();
return '-1';
}
Is this correct? (i get zero errors)
If not: how can i realize it?
I want to be sure, that no other user could reach the INSERT query, when annother is performing the 1. query.
Transactions are isolated from the current data. How they behave exactly is dependent on the isolation level they use. For example a transaction with serializable isolation level completely lives in the past, and it knows nothing of the data changes have been made since the beginning of the transaction.
If you want to prevent anybody to do changes on the database while your script is working on something, then you have to lock your database, tables or rows. This is usually not necessary, with the proper code.
In your case
you can use read committed transaction isolation level (default) call the DELETE after the SELECT, and check whether there are affected rows by the DELETE before the INSERT
if you don't want to change the order of your queries then you can
simply throw an exception if the DELETE does not affect any row, and so the INSERT will be rolled back
add a constraint which prevents multiple INSERTs with the same data, so the duplicated INSERT will violate a constraint, and it will be rolled back
lock the rows with a SELECT FOR UPDATE
This rather makes no sense. there is no use for transactions at all. What you want to roll back? a SELECT query result?
I want to be sure, that no other user could reach the INSERT query, when another is performing the 1. query.
This is achieved via table locking, not transactions. Or, rather by the simple key uniqueness. Without knowing your business logic it's impossible to answer more. Better ask another question, explaining user experience, and avoiding technical terms like "transaction" at all.

doctrine2 native query update statement

How to do a native sql query in Doctrine 2, executing an update statement?
The createNativeQuery method on EntityManager, requires a second parameter (ResultSetMapping) to be able to map the resultsets to Objects.
But when updating (or inserting, or set, or...) there is no resulset to map.
Passing null or just new ResultSetMapping(), gives an error.
Are only select queries supported for native sql?
Essentially ditto w/ faken,
this from the docs:
If you want to execute DELETE, UPDATE or INSERT statements the Native
SQL API cannot be used and will probably throw errors. Use
EntityManager#getConnection() to access the native database connection
and call the executeUpdate() method for these queries.
one note of use is the connection object can be retrieved from the EntityManager:
$conn = $entityManager->getConnection();
$rowsAffected = $conn->executeUpdate($sql, $params, $types);
Update statements are usually pretty simple, so you might as well use the normal Doctrine2 way (i.e. programmatically updating entities and calling EntityManager::flush() OR using DQL Updates).
Having said this, if you really want to use normal SQL, you could always do it like this:
Keep the db connection object you get when creating a connection with Doctrine2:
$connection = \Doctrine\DBAL\DriverManager::getConnection($dbConfig->toArray(),null,$evm);
Execute whatever SQL you want, using the available methods in the connection object, e.g.:
$connection->executeUpdate($sql, $params, $types);
$connection->exec($sql);
...

Categories