Can I safely assume this INSERT succeeded? - php

I'm using MySQL with strict mode turned on. I am inserting data supplied by user through an HTML form. I've already done all my validation and this is the final step.
Here is an excerpt from my PHP code:
$sql = 'SET sql_mode = TRADITIONAL'; //last chance data validation
$mysqli->query($sql) or output_error('Database Error: Failed to set SQL mode');
$db_err_msg = 'Database Error: Failed to update profile summary';
$sql = "INSERT INTO {$tables['text']} (lngProfileId, memProfileText) VALUES(?, ?)
ON DUPLICATE KEY UPDATE memProfileText = VALUES(memProfileText)";
$stmt = $mysqli->prepare($sql) or output_error($db_err_msg);
$stmt->bind_param('is', $profile_id, $_POST['memProfileText'])
or output_error($db_err_msg);
$stmt->execute() or output_error($db_err_msg);
$stmt->close();
//if code reaches this point, can it assume insert/update succeeded?
Note: output_error is my error handler for fatal errors. It doesn't return; it exits.
Do I need to check for warnings? Or can I safely assume the insert/update succeeded? Is there some scenario that could cause a warning instead of an error even with strict mode turned on?
EDIT: I'm only concerned about things that should be considered fatal errors. If it was updated rather than inserted, that is of no concern to me. That's not an error.
Also, last_insert_id and affected_rows are meaningless in this case. If the user didn't make any changes, affected_rows will be 0 and the value of last_insert_id will not be updated.
My definition of succeeded in this case:
it inserted the record or updated the record or did nothing if no changes were made
it did not 'silently mangle my data'
Many newbie programmers make the mistake of assuming that a query succeeded without ever checking the return value or warnings. Their code works... under normal conditions.
My question is: "Considering my code, with strict mode turned on, and the error checking I've done, is it safe to assume the query succeeded? Is that a correct or incorrect assumption? Should I also check for warnings or would that be redundant?"

You should check the value of mysqli::info() function.
If it inserts data the string will look something like this,
Records: 1 Duplicates: 0 Warnings: 0
Another function is mysqli::affected_rows. it returns the number of rows affected for last insert/update/delete query.

Yes you can safely assume that, but just for a double check Fetch the ID of the latest record that is inserted. If there is an id, it was inserted then

While I think that it's probably a safe assumption, I figured better safe than sorry. I've added the following code directly after the execute statement before the close statement.
//I think this is probably redundant, but just in case
if ($mysqli->warning_count) {
$warnings = $stmt->get_warnings();
do {
trigger_error('Database Warning (' . $warnings->errno . '): '
. $warnings->message, E_USER_WARNING);
} while ( $warnings->next() );
}
I've added the comment so that I remember why I did that.

Related

PHP / mysqli: Prepared Statements with num_rows constantly returning nothing

In my test-surroundings there is a database containing some Person Information (Name, E-Mail, Adress etc.). These Informations can be inserted by anyone into the database via a form. In the background they are inserted with a parameterized INSERT into the database after submission.
What I now would like to do is to detect if some person tries to insert the same values into the database again, and if he does, not inserting the new values and instead showing an error message. (So every person name in the database is unique, there are no multiple rows linked to one name).
I had a numerous number of ideas on how to accomplish this. My first one was to use a query like REPLACE or INSERT IGNORE, but this method would not give me feedback so I can display the error message.
My second attempt was to first do a SELECT-query, checking if the row already exists, and if num_rows is greater than 0, exit with the error message (and else do the INSERT-part). For this to work I will have to use parameterized queries for the SELECT too, as I´m putting some user input into it. Figuring that parameterized queries need special functions for everything you could normally do with way less lines of code, I researched in the internet on how to get num_rows from my $statement parameterized-statement-object. This is what I had in the end:
$connection = new mysqli('x', 'x', 'x', 'x');
if (mysqli_connect_error()) {
die("Connect Error");
}
$connection->set_charset("UTF-8");
$statement = $connection->stmt_init();
$statement = $connection->prepare('SELECT Name FROM test WHERE Name LIKE ?');
flags = "s";
$statement->bind_param($flags, $_POST["person_name"]);
$statement->execute();
$statement->store_result();
$result = $statement->get_result(); //Produces error
if ($result->num_rows >= 1) {
$output = "Your already registered";
} else {
$output = "Registering you...";
}
exit($output);
After all, I can´t get why mysqli still won´t give me num_rows from my statement. Any help is appreciated, thanks in advance!
Oh, and if you guys could explain to me what I have to do to get affected_rows,that would be awesome!
EDIT: I know I could to this by using unique constraints. I also found out that I can find out if INSERT IGNORE skipped the INSERT or not. But that won´t answer my complete question: Why does the SELECT num_rows alternative not work?
ANOTHER EDIT: I changed the code snippet to what I now have. Although my mysql(i)-version seems to be 5.6.33 (I echo´d it via $connection->server_info) get_result() produces the following error message:
Fatal error: Call to undefined method mysqli_stmt::get_result() in X on line X (line of get_result)
The behaviour of mysqli_num_rows() depends on whether buffered or unbuffered result sets are being used. For unbuffered result sets, mysqli_num_rows() will not return the correct number of rows until all the rows in the result have been retrieved. Note that if the number of rows is greater than PHP_INT_MAX, the number will be returned as a string.
Also make sure that you declare ->store_result() first. Moreover the function doesn't work with LIMIT used jointly with SQL_CALC_FOUND_ROWS. If you want to obtain the total rows found you must do it manually.
EDIT:
If nothing from the suggestions does not work for you, then I would propose to rewrite your SQL query:
SELECT `Name`, (SELECT COUNT(*) FROM `Persons`) AS `num_rows` FROM `Persons` WHERE `Name` LIKE ?
This query will return the total number from your Persons table, as well as Name, if exist.

PHP PDO. error number '00000' when query is correct [duplicate]

This question already has answers here:
PDO SQL-state "00000" but still error? [duplicate]
(5 answers)
Closed 5 years ago.
I have the code below:
$sql3 = "update news set date='$time' where id='2'";
$sql3 = $connect->exec($sql3);
if(!$sql3)
{
print_r($connect->errorInfo());
$error = $connect->errorInfo();
die ("Error: (".$error[0].':'.$error[1].') '.$error[2]);
}
When I run the script, sometimes I get error number '00000'. I mean it goes intro the IF. and it is all random. output (sometimes):
Array ( [0] => 00000 [1] => [2] => )
What should I do to fix this problem ?
PS: The script executes correctly every time.
The PDO error code 00000 means that everything works fine. The reason you're hitting the error-checking code is that $sql3 is returning 0 (no rows were effected) and PHP evaluates that to false. Try explicitly checking for a return false;
if($sql3 === false)
If exec does not update any row, it will return 0. that makes if(!$sql3) evaluate to false, you should do this instead :
if($sql3 === false){
}
In my experience badly formed queries (syntax errors) and failed queries (for example an INSERT that didn't insert anything) also may WRONGLY return error code 00000. You should go ahead and try to run the complete query on your SQL console and see why it failed. I really don't know why the proper error message isn't returned though. Here's a snippet of the code we use
$r = $pdo->prepare($sql);
if (!$r->execute($input_parameters)) { # query failed
if ($bLogFailures) {
error_log('query failed: ERROR['.$pdo->errorCode().':'.print_r($pdo->errorInfo(), true).'] QUERY['.$sql.']');
}
return false;
}
I just got similar situation in my php project - it occured that PDO Exception with error code '00000' took place when I tried to insert row with a field set to NULL while column defining the field in the database was of type ENUM('0', '1') and restriction NOT NULL.
After modifying PHP script to place '0' instead of NULL, the error perished.
Further coding brought more light into the situation - I was performing more than one PDO statments within one DB transaction but checking errors (in Exception handling block) basing only on the first PDO statement executed while real error occured int the third PDO statement.
00000 means, it works fine. you should change your if to this: $sql3 === false.
I had the same problem. It also tortured me a lot, but finally figured it out.
Suppose you have 7 columns in your table.
You are inserting data into 4 of them.
If for remaining 3 columns the default value is not set (say NULL for alpha-numeric columns, CURRENT_TIMESTAMP for date-time related columns etc.) then the above stated problem occurs.
If you are inserting data into all of those 7 columns or at least in those columns for which default value is not set, you wont get any error and data will get inserted.
The PDO::exec statement returns an integer to indicate the number of rows that were affected. So in your particular case, as the SomeKittens indicates, if 0 rows were affected, then your error code would be triggered.
However, if you are concerned as to whether your query worked, your better action may be to use PDO::query (in terms of your code ($returnObj = $connect->query($sql3) instead of PDO::exec.
The $returnObj can then be checked to see if there was an error in the SQL execution, and you can then troubleshoot your SQL query because it will tell you what the error was and where it was located.
Your best bet to do this would be:
//set PDO to throw an error so you can wrap the query in a try / catch block.
$connect->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$sql3 = "update news set date='$time' where id='2'";
try {
$returnObj = $connect->query($sql3);
} catch (PDOException $e) {
print_r($returnOjb->errorInfo());
$error = $returnObj->errorInfo();
die ("Error: (".$error[0].':'.$error[1].') '.$error[2]);
}

PDO SQL-state "00000" but still error? [duplicate]

This question already has answers here:
Why does this PDO statement silently fail?
(2 answers)
Closed 5 years ago.
Can anybody explain why
$sql->execute($params);
returns FALSE, whereas
print $pdo->errorCode();
print_r($pdo->errorInfo());
both return SQLSTATE 00000, which means according to the documentation success? It is an INSERT and nothing is actually being inserted into the database... so, why do I get a success message from SQLSTATE?
In case it helps, this is the code...
$sql = $pdo->prepare("
INSERT INTO user (
username, fname, pass, salt, email,
loc_id_home, country_id_home, region_id_home,
cont_id_home, timestamp_reg, timestamp_upd, timestamp_lastonline,
online_status, gender, birthdate
)
VALUES (
:username,:fname,:pass,:random_salt,:email,
:loc_id_home,:country_id_home,:region_id_home,
:cont_id_home,'".time()."','".time()."','".time()."',
1,:gender,:birthdate)
");
$params=array(
':username'=>$username,
':fname'=>$fname,
':pass'=>$pass,
':random_salt'=>$random_salt,
':email'=>$email,
':loc_id_home'=>$loc_id_home,
':country_id_home'=>$country,
':region_id_home'=>$region,
':cont_id_home'=>$continent,
':gender'=>$gender,
':birthdate'=>$birthdate
);
$sql->execute($params);
print $pdo->errorCode();
print_r($pdo->errorInfo());
It is because $pdo->errorInfo() refers to the last statement that was successfully executed. Since $sql->execute() returns false, then it cannot refer to that statement (either to nothing or to the query before).
As to why $sql->execute() returns false, I don't know... either there is a problem with your $params array or with your database connection.
PDO::errorCode — Fetch the SQLSTATE associated with the last operation on the database handle
Note: The PHP manual (http://php.net/manual/en/pdo.errorinfo.php) does not define exactly what "last operation on the database handle" means, but if there was an issue with binding parameters, that error would have occurred inside PDO and without any interaction with the database. It is safe to say that if $pdo->execute() returns true, that $pdo->errorInfo() is valid. If $pdo->execute() returns false, the behavior of $pdo->errorInfo() is not explicitly clear from the documentation. If I recall correctly from my experience, execute returns true, even if MySQL returned an error, returns false if no operation was done. Since the documentation is not specific, it might be db driver specific.
This answer reflects practical experience as of when it was written in September 2012. As a user has pointed out, the documentation does not explicitly reaffirm this interpretation. It also may only reflect the particular database driver implementation, but it should always be true that if $pdo->execute() returns true, that $pdo->errorInfo() is valid.
You might also want to set PDO::ERRMODE_EXCEPTION in your connect sequence. Exception handling makes it unnecessary to check and query the error.
$dbh->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION );
I Faced the similar problem ,
This occurs manly due to error in query, try to run your query in php-myadmin or any other query runner and confirm that your query is working fine.Even if our query syntax is correct other simple errors like leaving null or not mentioan a column that set as not null in table structure will cause this error.(This was the errror made by me)
As user1122069 explained the reason for $pdo->errorInfo() says nothing is wrong may be due to
$pdo->errorInfo() refers to the last statement that was successfully
executed. Since $sql->execute() returns false, then it cannot refer to
that statement (either to nothing or to the query before)
Hopes this helps :)
From the php manual:
PDO::ERR_NONE (string)
Corresponds to SQLSTATE '00000', meaning that the SQL statement was successfully issued with no errors or warnings. This constant is for your convenience when checking PDO::errorCode() or PDOStatement::errorCode() to determine if an error occurred. You will usually know if this is the case by examining the return code from the method that raised the error condition anyway.
So it sounds like it did insert the record. Check the last record id in your table... maybe you just missed it?
I was getting this error at one time. I only got it on one server for all failures. A different server would report the error correctly for the same errors. That led me to believe it was a MySQL client configuration error. I never solved the specific error, but check your configurations.
Try to check $sql by print_r() and copy your query then try resultant query in phpMyadmin. Hope will get the reason. There would be chance of irrelevant value.

php mysql_query returns nothing after insert (and nothing is inserted either)

I've got the following code:
<?php
if(!empty($error_msg))
print("$error_msg");
else
{
require_once("../include/db.php");
$link = mysql_connect($host,$user,$pass);
if (!$link)
print('Could not connect: ' . mysql_error());
else
{
$sql = "insert into languages values(NULL,'$_POST[language]','$_POST[country_code]');";
$res = mysql_query($sql);
print("$sql<br>\n");
print_r("RES: $res");
mysql_close($link);
}
}
?>
In one word: it does not work. mysql_query doesn't return anything. If I try the same
query within php_myadmin, it works. It does not insert anything either. Also tried it as
user root, nothing either. Never had this before. Using mysql 5.1 and PHP 5.2.
Any ideas?
mysql_query will return a boolean for INSERT queries. If you var_dump $res you should see a boolean value being printed. It will return TRUE for a successful query, or FALSE on error. In no cases it ever returns NULL.
In addition, never pass input data (e.g.: $_POST) directly to an SQL query. This is a recipe for SQL injection. Use mysql_real_escape_string on it first:
$language = mysql_real_escape_string($_POST['language']);
$sql = "INSERT INTO language SET language='$language'";
And don't forget to quote your array indices (e.g.: $_POST['language'] instead of $_POST[language]) to prevent E_NOTICE errors.
You need to specify a database so the system knows which database to run the query on...
http://php.net/manual/en/function.mysql-select-db.php
Without selecting a database, your data will not be inserted
mysql_query returns a boolean for INSERT queries. If used in string context, such as echo "$res", true will be displayed as 1 and false as an empty string. A query error has possibly occured. Use mysql_error() to find out why the query has failed.
$sql = "insert into languages values(NULL,'$_POST[language]','$_POST[country_code]');";
This is very bad practise, as a malicious user can send crafted messages to your server (see SQL Injection).
You should at least escape the input. Assuming your column names are named 'language' and 'country_code', this is a better replacement for the above code:
$sql = sprintf('INSERT INTO LANGUAGES (language, country_code) VALUES ("%s","%s")',
mysql_real_escape_string($_POST['language']),
mysql_real_escape_string($_POST['country_code'])
);
For a description of the mysql_real_escape_string function, see the PHP Manual. For beginners and experienced programmers, this is still the best resource for getting information about PHP functions.
Instead of using $_POST directly, I suggest using the filter_input() function instead. It's available as of PHP 5.2.
With an INSERT query, mysql_query returns true or false according as the query succeeded or not. Here it is most likely returning false. Change the line print_r("RES: $res"); to print_r("RES: ".(int)$res); and most likely you will see it print RES: 0.
The problem may be that MySQL expects a list of column names before the VALUES keyword.
Also, you appear to be inserting POST variables directly into SQL - you should read up on SQL injection to see why this is a bad idea.
--I retract the quote comment, but still not good to directly insert $_POST values.--
Second, I don't think i've seen print_r quite used like that, try just using an echo.
And mysql_query is only expected a boolean back on an INSERT, what are you expecting?
Now ive got this:
$language = mysql_real_escape_string($_POST['language']);
$country_code = mysql_real_escape_string($_POST['country_code']);
$sql = "insert into shared_content.languages (id,language,country_code) values(NULL,$language,$country_code);";
$res = mysql_query($sql);
print("$sql<br>\n");
var_dump($res);
print(mysql_error());
mysql_close($link);
And the output:
insert into shared_content.languages (id,language,country_code) values(NULL,NETHERLANDS,NL);
bool(false) Unknown column 'NETHERLANDS' in 'field list'

Can I detect and handle MySQL Warnings with PHP?

I'm dealing with a MySQL table that defines the JobName column as UNIQUE. If somebody tries to save a new Job to the database using a JobName that is already in the database, MySQL throws a warning.
I would like to be able to detect this warning, just like an error, in my PHP script and deal with it appropriately. Ideally I would like to know what kind of warning MySQL has thrown so that I can branch the code to handle it.
Is this possible? If not, is it because MySQL doesn't have this ability, PHP doesn't have this ability, or both?
For warnings to be "flagged" to PHP natively would require changes to the mysql/mysqli driver, which is obviously beyond the scope of this question. Instead you're going to have to basically check every query you make on the database for warnings:
$warningCountResult = mysql_query("SELECT ##warning_count");
if ($warningCountResult) {
$warningCount = mysql_fetch_row($warningCountResult );
if ($warningCount[0] > 0) {
//Have warnings
$warningDetailResult = mysql_query("SHOW WARNINGS");
if ($warningDetailResult ) {
while ($warning = mysql_fetch_assoc($warningDetailResult) {
//Process it
}
}
}//Else no warnings
}
Obviously this is going to be hideously expensive to apply en-mass, so you might need to carefully think about when and how warnings may arise (which may lead you to refactor to eliminate them).
For reference, MySQL SHOW WARNINGS
Of course, you could dispense with the initial query for the SELECT ##warning_count, which would save you a query per execution, but I included it for pedantic completeness.
First, you should turn warnings off so that your visitors don't see your MySQL errors. Second, when you call mysql_query(), you should check to see if it returned false. If it did, call mysql_errno() to find out what went wrong. Match the number returned to the error codes on this page.
It looks like this is the error number you're looking for:
Error: 1169 SQLSTATE: 23000 (ER_DUP_UNIQUE)
Message: Can't write, because of unique constraint, to table '%s'
ini_set('mysql.trace_mode', 1)
may be what you are looking for.
The PHP errors can then be handled with a custom PHP error handler, but you can also just turn off displaying php errors as they are usually logged into a log file (depends on your php configuration).
depending on what (if any) framework you're using, I suggest you do a query to check for the jobname yourself and create the proper information to user in with the rest of the validations for the form.
Depending on the number of jobnames, you could send the names to the view that contains the form and use javascript to tell use which is taken.
If this doesnt make sense to you, then to sum my view it's this: dont design your program and / or user to try to do illegal things and catch the errors when they do and handle it then. It is much better, imho, to design your system to not create errors. Keep the errors to actual bugs :)
Updated to remove the stuff about errno functions which I now realize don't apply in your situation...
One thing in MySQL to be wary of for UPDATE statements: mysqli_affected_rows() will return zero even if the WHERE clause matched rows, but the SET clause didn't actually change the data values. I only mention this because that behaviour caused a bug in a system I once looked at--the programmer used that return value to check for errors after an update, assuming a zero meant that some error had occurred. It just meant that the user didn't change any existing values before clicking the update button.
So I guess using mysqli_affected_rows() can't be relied upon to find such warnings either, unless you have something like an update_time column in your table that will always be assigned a new timestamp value when updated. That sort of workaround seems kinda kludgey though.
You can detect Unique key violations using mysqli statement error no. The mysqli statement returns error 1062 , that is ER_DUP_ENTRY. You can look for error 1062 and print a suitable error message. If you want to print your column (jobName) also as part of your error message then you should parse the statement error string.
if($stmt = $mysqli->prepare($sql)){
$stmt->bind_param("sss",
$name,
$identKey,
$domain);
$stmt->execute();
if($mysqli->affected_rows != 1) {
//This will return errorno 1062
trigger_error('mysql error >> '.$stmt->errno .'::' .$stmt->error, E_USER_ERROR);
exit(1);
}
$stmt->close();
} else {
trigger_error('mysql error >> '. $mysqli->errno.'::'.$mysqli->error,E_USER_ERROR);
}
It is possible to get the warnings, and in a more efficient way with mysqli than with mysql.
Here is the code suggested on the manual page on php.net for the property mysqli->warning_count:
$mysqli->query($query);
if ($mysqli->warning_count) {
if ($result = $mysqli->query("SHOW WARNINGS")) {
$row = $result->fetch_row();
printf("%s (%d): %s\n", $row[0], $row[1], $row[2]);
$result->close();
}
}
Note on suppressing warnings: Generally, it is not a good idea to prevent warnings from being displayed since you might be missing something important. If you absolutely must hide warnings for some reason, you can do it on an individual basis by placing an # sign in front of the statement. That way you don't have to turn off all warning reporting and can limit it to a specific instance.
Example:
// this suppresses warnings that might result if there is no field titled "field" in the result
$field_value = #mysql_result($result, 0, "field");

Categories