I have always struggled with a fairly basic concept in my PHP INSERT/UPDATE code. Should I always be checking for the number of affected rows after every INSERT/UPDATE because in the vast majority of times I am only ever performing one INSERT/UPDATE and it seems to cause more problems than it fixes by checking that only one row was affected.
Below is my standard code to perform the INSERT/UPDATE and this code fails if the user is updating a record without changing anything because the affected rows will be 0. I could write code to check that at least one field has changed but on large forms this seems very clunky and was wondering if it is really worth it because I have never really ever caught any errors by checking this number anyway.
<?php
$whereSql = '';
$groupSql = 'INSERT INTO';
if(isset($_POST['id']) && is_numeric($_POST['id'])){
$groupSql = 'UPDATE';
$whereSql = 'WHERE id = ' . $_POST['id'];
}
$groupSql .= ' sometable SET name="' . $name . '" ' . $whereSql;
$groupDb = mysqli_query($groupSql, $dbObject) or die("Login DB error:".mysql_error());
if(mysqli_affected_rows($dbObject) == 1){
//redirect
}else{
die('System Error');
}
You should be checking return values on queries. A select/update query which affects/returns no rows is NOT an error condition, it's simply an empty result set, or an update which happened to affect nothing.
$result = mysql_query($sql) or die(mysql_error());
^^^^^^^^^^^^^^^^^^^^^
Consider a user signup system which checks for the existence of a matching username:
SELECT id FROM users WHERE username='foo';
if the user foo does not exist, your system will barf and claim an error occurred. But an empty result set is NOT an error. It simply means the username is available for user.
Same for a system that handles grades:
UPDATE students SET failed=true WHERE score < 50;
getting 0 affected rows is NOT a failure. It just means all the students passed.
I would recommend checking if the query has failed, and if not, then if there was more than one row affected.
$groupDb = mysql_query($groupSql, $dbObject);
if (false === $groupDb) {
die("Login DB error:".mysql_error())
if (mysql_affected_rows($dbObject) > 1) {
die('System Error: failed to ' . $action . ' a document Group');
} else {
//redirect
}
This way you will redirect only in case of successful queries and if there was less than 2 rows affected (if that is important to you).
Related
This is a simple one, maybe I'm having a long brain fart or something, but whats happening is the form is used to set a record only if the name and their cpukey matches then it will continue, as this is a public page with no login, it would be rather annoying for people to be changing other peoples things without knowing 2 sets in information. So the problem here is, the function itself actually works flawlessly, the Message produced which states SUCCESS or FAILURE always produces SUCCESS, even if the function failed (due to no match on one or more rows)
Here is the code used.
if(isset($_POST['upduserNotify'])){
$ccurrentname = mysqli_real_escape_string($con, $_POST['ccnameVal']);
$cclientnotify = mysqli_real_escape_string($con, $_POST['cnotifyVal']);
$cclientcpuk = mysqli_real_escape_string($con, $_POST['ccpukeyVal']);
$changenot = "UPDATE clients SET notify = '$cclientnotify' WHERE cpukey = '$cclientcpuk' AND name = '".$_POST['ccnameVal']."'";
if (mysqli_query($con, $changenot)) {
echo'<div align ="center" style="color:#000000">Your Custom notification was updated successfully</div><br />';
} else {
echo'<div align ="center">You entered an incorrect CPUKEY/Name or used an invalid character</div><br />';
}
}
An UPDATE query that runs, but finds no rows to update, is still a successful one - it ran to completion without encountering any errors, so mysqli_query will return TRUE, per the docs. (If it were a SELECT sort of query, it'd return a mysqli_result object.)
If you want to do something different when it didn't find any rows to update, you'll want to look at the number of affected rows and act accordingly.
if(isset($_POST['upduserNotify'])){
$ccurrentname = mysqli_real_escape_string($con, $_POST['ccnameVal']);
$cclientnotify = mysqli_real_escape_string($con, $_POST['cnotifyVal']);
$cclientcpuk = mysqli_real_escape_string($con, $_POST['ccpukeyVal']);
$changenot = "UPDATE `clients` SET `notify` = '$cclientnotify' WHERE `cpukey` = '$cclientcpuk' AND `name` = '$ccurrentname'";
if (mysqli_query($con, $changenot) && mysqli_affected_rows($con) == 1 )
{
echo'<div align ="center" style="color:#000000">Your Custom Xnotify was updated successfully</div><br />';
}
else if (mysqli_query($con, $changenot) && mysqli_affected_rows($con) == 0 )
{
echo'<div align ="center">You entered an incorrect CPUKEY/Name or used an invalid character</div><br />';
}
}
After some hours I have to post this question even if the answer maybe obvious to someone else.
The problem is that I want to test for the tokens, but even when I hardcode this, I still get INVALID. And I know it has to be right, because I tested it in PHPADMIN directly. What's odd is that it always passes the first time (without being hardcoded), but after that it is useless?
The tokens are retrieved from a cookie.
public function findTriplet($credential, $token, $persistentToken) {
$token = "459078a3b05ce938ed58f9678ac78f1agcgfsewe4";
$persistentToken = "24d317b742da89ddf5b8ed50993d0f3cgcgfsewe4";
$credential ="34";
$q = "SELECT IF(SHA1(?) = {$this->tokenColumn}, 1, -1) AS token_match " .
"FROM {$this->tableName} WHERE {$this->credentialColumn} = ? " .
"AND {$this->persistentTokenColumn} = SHA1(?) LIMIT 1 ";
$query = $this->db->prepare($q);
$query->execute(array($token, $credential, $persistentToken));
$result = $query->fetchColumn();
if (!$result) {
return self::TRIPLET_NOT_FOUND;
} else if ($result == 1) {
return self::TRIPLET_FOUND;
} else {
return self::TRIPLET_INVALID; }
}
EDIT
The limit clause always catches the first row it finds, therefore I
always get a mismatch Now I have to fix this.
The solution was simple. Delete the entry that was just validated before inserting a new row with the newly generated token. The new row should contain the SAME persistenceToken you just validated against. REMEMBER, this will still be UNSECURE, so set a FLAG on the serverside that this was a cookielogin, and require a REAL LOGIN for handling important data.
I think your if checks are in the wrong order:
if(!$result) { return self::TRIPLET_NOT_FOUND;}
elseif ($result == 1) { return self::TRIPLET_FOUND;}
else { return self::TRIPLET_INVALID;}
In the SQL, 1 means found, -1 means not found, and anything else would be invalid. But in the PHP, a -1 would fall into the else clause, and return self::TRIPLET_INVALID, whereas an invalid result would fall into if(!$result) and return self::TRIPLET_NOT_FOUND.
Reason: I was assigned to run some script that advances a website,it's a fantasy football site and there are several instants of the site located into different domains. Some has more than 80k users and each users supposed to have a team that consists of 15 players. Hence some tables have No.users x No.players rows.
However Sometimes the script fails and the result gets corrupted, therefore I must backup 10 tables in question before i execute the script. Nevertheless, I still need to backup the tables to keep historical record of users action. Because football matches may last for 50+ game weeks.
Task: To duplicate db tables using php script. When i started i used to backup the tables using sqlyog. it's works but it's time consuming since I have to wait for each table to be duplicated. Besides, for large tables the sqlyog application crashes during the duplicating of large tables which may be very annoying.
Current solution: I have created a simple application with interface that does the job and it works great. It consist of three files, one for db connection, 2nd for db manipulation, 3rd for user interface and to use the 2nd file's code.
The thing is, sometimes it get stuck at the middle of duplicating tables process.
Objective: To create an application to be used by admin to facilitate database backing up using mysql+php.
My Question: How to ensure that the duplicating script will definitely backup the table completely without hanging the server or interrupting the script.
Down here I will include my code for duplicating function, but basically these are the two crucial lines that i think the problem is located in them:
//duplicate tables structure
$query = "CREATE TABLE $this->dbName.`$newTableName` LIKE $this->dbName.`$oldTable`";
//duplicate tables data
$query = "INSERT INTO $this->dbName.`$newTableName` SELECT * FROM $this->dbName.`$oldTable`";
The rest of the code is solely for validation in case error occur. If you wish to take a look at the whole code, be my guest. Here's the function:
private function duplicateTable($oldTable, $newTableName) {
if ($this->isExistingTable($oldTable))
{
$this->printLogger("Original Table is valid -table exists- : $oldTable ");
}
else
{
$this->printrR("Original Table is invalid -table does not exist- : $oldTable ");
return false;
}
if (!$this->isExistingTable($newTableName))// make sure new table does not exist alrady
{
$this->printLogger("Distination Table name is valid -no table with this name- : $newTableName");
$query = "CREATE TABLE $this->dbName.`$newTableName` LIKE $this->dbName.`$oldTable`";
$result = mysql_query($query) or $this->printrR("Error in query. Query:\n $query\n Error: " . mysql_error());
}
else
{
$this->printrR("Distination Table is invalid. -table already exists- $newTableName");
$this->printr("Now checking if tables actually match,: $oldTable => $newTableName \n");
$varifyStatus = $this->varifyDuplicatedTables($oldTable, $newTableName);
if ($varifyStatus >= 0)
{
$this->printrG("Tables match, it seems they were duplicated before $oldTable => $newTableName");
}
else
{
$this->printrR("The duplicate table exists, yet, doesn't match the original! $oldTable => $newTableName");
}
return false;
}
if ($result)
{
$this->printLogger("Query executed 1/2");
}
else
{
$this->printrR("Something went wrong duplicateTable\nQuery: $query\n\n\nMySql_Error: " . mysql_error());
return false;
}
if (!$this->isExistingTable($newTableName))//validate table has been created
{
$this->printrR("Attemp to duplicate table structure failed $newTableName table was not found after creating!");
return false;
}
else
{
$this->printLogger("Table created successfully: $newTableName");
//Now checking table structure
$this->printLogger("Now comparing indexes ... ");
$autoInc = $this->checkAutoInc($oldTable, $newTableName);
if ($autoInc == 1)
{
$this->printLogger("Auto inc seems ok");
}
elseif ($autoInc == 0)
{
$this->printLogger("No inc key for both tables. Continue anyways");
}
elseif ($autoInc == -1)
{
$this->printLogger("No match inc key!");
}
$time = $oldTable == 'team_details' ? 5 : 2;
$msg = $oldTable == 'team_details' ? "This may take a while for team_details. Please wait." : "Please wait.";
$this->printLogger("Sleep for $time ...\n");
sleep($time);
$this->printLogger("Preparing for copying data ...\n");
$query = "INSERT INTO $this->dbName.`$newTableName` SELECT * FROM $this->dbName.`$oldTable`";
$this->printLogger("Processing copyign data query.$msg...\n\n\n");
$result = mysql_query($query) or $this->printrR("Error in query. Query:\n $query\n Error: " . mysql_error());
// ERROR usually happens here if large tables
sleep($time); //to make db process current requeste.
$this->printLogger("Query executed 2/2");
sleep($time); //to make db process current requeste.
if ($result)
{
$this->printLogger("Table created ($newTableName) and data has been copied!");
$this->printLogger("Confirming number of rows ... ");
/////////////////////////////////
// start checking count
$numRows = $this->checkCountRows($oldTable, $newTableName);
if ($numRows)
{
$this->printLogger("Table duplicated successfully ");
return true;
}
else
{
$this->printLogger("Table duplicated, but, please check num rows $newTableName");
return -3;
}
// end of checking count
/////////////////////////////////
}//end of if(!$result) query 2/2
else
{
$this->printrR("Something went wrong duplicate Table\nINSERT INTO $oldTable -> $newTableName\n\n$query\n mysql_error() \n " . mysql_error());
return false;
}
}
}
AS you noticed the function is only to duplicate one table, that's why there is another function that that takes an array of tables from the user and pass the tables names array one by one to duplicateTable().
Any other function should be included for this question, please let me know.
One solution pops into my mind, would duplicating tables by part by part add any improvement, I'm not sure how Insert into works, but maybe if I could insert let's say 25% at a time it may help?
However Sometimes the script fails and the result gets corrupted,
therefore I must backup 10 tables in question before i execute the
script.
Probably you need to use another solution here: transactions. You need to wrap up all queries you are using in failing script into transaction. If transaction fails all data will be the same as in the beginning of the operation. If queries got executed correctly - you are OK.
why are you every time duplicating the table..
CLUSTERS are good option which can make duplicate copies of your table in distributed manner and is much more reliable and secure.
When running my PHP script It keeps giving me the error
You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '1' at line 1
This is my sql code I have other than selecting from the table. I have commented out all of this and not gotten an error, so I'm assuming its occuring in this block of code.
if($status === 1){
$sqlQ = mysql_query("UPDATE tablename SET status=1 WHERE steam64='$id'");
if(!mysql_query($sqlQ, $con)){
die('Error: ' . mysql_error());
}
}else if($status !== 1){
$sqlQ = mysql_query("UPDATE tablename SET status=2 WHERE steam64='$id'");
if(!mysql_query($sqlQ, $con)){
die('Error: ' . mysql_error());
}
}
What is really confusing me is the line 1 part.
You're violating the DRY principle big time. Why not something like...
$statusValue = ($status === 1) ? 1 : 2;
$sqlQuery = mysql_query("UPDATE `14d2_group` SET `status` = $statusValue WHERE `steam64` = '$id'"):
UPDATE 2: It looks like there's a need for additional clarification.
mysql_query function doesn't only create a query: it actually sends in to MySQL - and returns the result. In case of UPDATE it will return FALSE if query has failed. That's why you shouldn't call mysql_query twice, as you did in the original example.
You can check how many lines were actually updated with mysql_affected_rows function.
UPDATE 3: Finally get it. ) That was the reason error appeared: you tried to call mysql_query with result of the last update query. Which was, as TRUE converted to String, just '1'. )
You're using the result from one query as a query itself.
What you probably wanted to do is:
if($status === 1){
$sqlQ = mysql_query("UPDATE tablename SET status=1 WHERE steam64='$id'");
if (!$sqlQ) {
die('Error: ' . mysql_error());
}
}
else {// no need for your if-statement here because it would always be true
$sqlQ = mysql_query("UPDATE tablename SET status=2 WHERE steam64='$id'");
if(!$sqlQ){
die('Error: ' . mysql_error());
}
}
"Line 1" corresponds to line 1 of the query, not the script invoking it. To add the line of the script invoking it, use:
die('Error: ' . mysql_error() . ' in ' . $_SERVER['PHP_SELF'] . ' on line ' . __LINE__ );
As for the query, I don't really see anything jumping out at me. The only suggestion I have right now is to always enclose field names in backticks, just in case they're keywords (it also makes them clearer to read)
Also, your else if is redundant. If $status === 1 doesn't run, then clearly $status !== 1 must be true.
Because of type casting, status=1 is not a problem. I'm assuming $id has some probrem. Once change $id to other safe value (1, 'foo'...) then check it works or not.
The 'line 1' part is SQL saying that hte message it recieved had an error on line 1 -- the first line of the command that SQL tried to process.
If I had to make a guess, status isn't set to a number type, so you need to put quotes around it so that SQL knows it's being passed a variable.
Edit: OK, the other solution might be right too. We both made different assumptions about your data structure, and I think his is better. Try it first.
I have php application which gets information from a SAML POST and creates a record in the MySQL database, if the record is already present it just updates it
Here is the code
//getMemberRecord returns true for successful insertion.
$row = $this->getMemberRecord($data);
if ($row) {
//if the row already exists
$this->updateMemberRecord($data)
} else {
// creates a new record
$this->setMemberRecord($data);
}
This code is causing double inserts in the database, we don't have a unique key for the table due to some poor design constraints, but I see two HTTP posts in the access logs happening at the same time.
The create date column is same or differs by a second for the duplicate record.
This issue is happening for only select few, it works for most of them.
The table is innoDB table and we can not use sessions on our architecture.
Any ideas of why this would happen
You said:
I see two HTTP posts in the access logs
You should try avoiding this and have just one http POST invocation
May be it is a problem related to concurrency and mutual exclusion. The provided code must be executed in a mutually exclusion zone, so you must use some semaphore / mutex to prevent simultaneous execution.
If you have two HTTP POST happening your problem is not on the PHP/MYSQL side.
One thing is allowing a second 'transparent' HTTP POST in the HTTP protocol. It's the empty url. If you have an empty GET url in the page most browsers will replay the request which rendered the page. Some recent browser are not doing it, but most of them are still doing it (and it's the official way of HTTP). An empty GET url on a page is for example <img src=""> or < script url=""> but also an url() in a css file.
The fact you have one second between the two posts make me think it's what's happening for you. The POST response page is quite certainly containing an empty Get that the browser fill by replaying the POST... I hate this behaviour.
I found that the double inserts were happening becuase double submits and our application doesnot handle double submits efficiently, I read up on some articles on this, here are some of the solutions
it always best to handle double posts at the server side
best solution is to set a UNIQUE KEY on the table or do a INSERT ON DUPLICATE KEY UPDATE
if you have sessions then use the unique token , one of the technique in this article
http://www.freeopenbook.com/php-hacks/phphks-CHP-6-SECT-6.html
or use can use the Post/Redirect/Get technique which will handle most double submit problems
http://en.wikipedia.org/wiki/Post/Redirect/Get
note: the Double submit problem only happens on a POST request, GET request is immune
public function setMemberRecord($data, $brand_id, $organization_id, $context = null)
{
global $gRegDbManager;
$sql = "insert into member ......"
$gRegDbManager->DbQuery($sql);
// Popuplate the iid from the insert
$params['iid'] = $gRegDbManager->DbLastInsertId();
$data = some operations
return (int)$data;
}
public function getMemberRecord($field, $id, $brand_id, $organization_id, $organization_level_account = null)
{
global $gRegDbManager;
$field = mysql_escape_string($field);
$id = mysql_escape_string($id);
$sql = "SELECT * FROM " . DB_REGISTRATION_DATABASE . ".member WHERE $field = '$id' ";
if($organization_level_account) {
$sql .= "AND organization_fk = " . $organization_id;
} else {
$sql .= "AND brand_fk = " . $brand_id;
}
$sql .= " LIMIT 1";
$results = $gRegDbManager->DbGetAll($sql);
if(count($results) > 0) {
return $results[0];
}
return;
}
/* * ******************************************************************************************************
* Updates member record in the member table
* *******************************************************************************************************
*/
public function updateMemberRecord($id, $changes)
{
global $gRegDbManager;
$id = mysql_escape_string($id);
if(!empty($changes)) {
$sql = "UPDATE " . DB_REGISTRATION_DATABASE . ".member SET ";
foreach($changes as $field => $value) {
$sql .= mysql_escape_string($field) . " = '" . mysql_escape_string($value) . "', ";
}
$sql = rtrim($sql, ", ");
$sql .= " WHERE iid = '$id'";
$gRegDbManager->DbQuery($sql);
} else {
return false;
}
}