I have a strange problem with PDO not throwing an exception when a duplicate value is inserted. In this case I did expect an error.
The relevant code:
try
{
$db_conn = new PDO("mysql:host=".$config["database"]["hostname"].";charset=utf8", $config["database"]["username"], $config["database"]["password"], []);
$db_conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$db_conn->exec(file_get_contents("some_file_with_data.sql");
}
catch(Exception $e)
{
// PDOException extends RuntimeException extends Exception so exceptions should be catched here
// however for the duplicate key entry it will not throw an exception
}
The file with SQL data contains multiple inserts like this:
INSERT INTO `a` (`b`, `c`) VALUES
(1, 1),
(2, 2),
(3, 2);
INSERT INTO `a` (`b`, `c`) VALUES
(1, 1);
The field b in table a is set to being the primary key. When I insert the exact same data in the exact same structure using phpMyAdmin I get this error: #1062 - Duplicate entry '65533' for key 'PRIMARY'
Why does PDO not throw an error in this case? Even when I set the error mode to exception?
Edit:
This is the table structure used for this specific table
CREATE TABLE IF NOT EXISTS `a` (
`b` smallint(5) unsigned NOT NULL,
`c` smallint(5) unsigned NOT NULL,
PRIMARY KEY (`b`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
Update 2018: DEVs do not consider this a bug, but intended behaviour.
So, PHP-Users have to live with that, Report is closed for any future questions...
This has often been reported as bug with PDO: https://bugs.php.net/bug.php?id=61613
It will only throw an exception if the FIRST Statement is invalid. If the first statement runs smooth, you won't get any error - And your first statement is valid:
INSERT INTO `a` (`b`, `c`) VALUES
(1, 1),
(2, 2),
(3, 2);
as a workaround - or according to user deleted the right way of doing it - you need to process the rowsets one-by-one (taken from the bug reports comments):
$pdo->beginTransaction();
try {
$statement = $pdo->prepare($sql);
$statement->execute();
while ($statement->nextRowset()) {/* https://bugs.php.net/bug.php?id=61613 */};
$pdo->commit();
} catch (\PDOException $e) {
$pdo->rollBack();
throw $e;
}
Related
I'll use the example from SQLite help page about foreign key constrains:
Let's build 2 tables in SQLite:
CREATE TABLE artist(
artistid INTEGER PRIMARY KEY,
artistname TEXT
);
CREATE TABLE track(
trackid INTEGER PRIMARY KEY,
trackname TEXT,
trackartist INTEGER,
CONSTRAINT fk
FOREIGN KEY(trackartist) REFERENCES artist(artistid))
ON UPDATE CASCADE ON DELETE CASCADE
);
Let's add 2 records from SQLite command:
sqlite> pragma foreign_keys = ON;
sqlite> INSERT INTO artist(artistid, artistname) VALUES(null, 'Bing Crosby');
sqlite> INSERT INTO track(trackid, trackname, trackartist) VALUES(null, 'White Christmas', 1);
sqlite> SELECT * FROM artist;
1|Bing Crosby
sqlite> SELECT * FROM track;
1|White Christmas|1
Now, while the command tool does NOT allow (correctly) an insert of:
sqlite> INSERT INTO track(trackid, trackname, trackartist) VALUES(null, 'Another Track', 9);
Error: FOREIGN KEY constraint failed
php on the other hand DOES allow (incorrectly) this:
$dbh = new \PDO('sqlite:test.sq3');
$dbh->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
try {
if (!$dbh->beginTransaction())
throw new \Exception('Could not started a transaction!');
//$dbh->exec('PRAGMA foreign_keys = ON;');
$query = $dbh->prepare('PRAGMA foreign_keys = ON;');
$query->execute();
//$dbh->exec("INSERT INTO track(trackid, trackname, trackartist) VALUES(null, 'Another Track', 9);");
$query = $dbh->prepare("INSERT INTO track(trackid, trackname, trackartist) VALUES(null, 'White Christmas', 9);");
$query->execute();
$dbh->commit();
} catch (\Exception $e) {
$dbh->rollback();
}
See the violation:
sqlite> SELECT * FROM track;
1|White Christmas|1
2|Another Track|9
Any idea? Thank you!
The documentation says:
This pragma is a no-op within a transaction; foreign key constraint enforcement may only be enabled or disabled when there is no pending BEGIN or SAVEPOINT.
In my MySQL database I have a table "table1" with unique constraint set on column "name" - I want to prevent duplicate names.
If there's already name 'John' in table this code:
$db=new mysqli(...);
$sql="INSERT INTO table1 SET id=10,name='John'";
if(!$db->query($sql))
{
if($db->errno==1062)
{
throw new InsertNonUniqueException(...);
}
else
{
throw new InsertException(...);
}
}
should throw InsertNonUniqueException() (my own exception). Instead, it throws InsertException().
Execution of query returns false and execution enters the if() loop. Also $db->row_affected is -1 but problem is that $db->errno is always O (it should be 1062)!!! So I can't detect that my insert error was caused by violating unique key constraint on name column!
I don't know why mysqli does not return 1062 code when unique key constraint violation occurs!
I can't leave a comment, thus going to ask you here.
Please provide the result of SHOW CREATE TABLE table1;
I can't reproduce your problem using your code and next table:
CREATE TABLE `table1` (
`name` varchar(11) COLLATE utf8_unicode_ci NOT NULL,
UNIQUE KEY `name` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci
Am I the only one around here that thinks you have an error in your SQL syntax?.. There is no room for SET in INSERT INTO, because you can only use SET in UPDATE statements (assuming you habe MySQL in version 5.5 or below).
INSERT INTO syntax is like the following (as described in the docs):
INSERT [LOW_PRIORITY | HIGH_PRIORITY] [IGNORE]
[INTO] tbl_name [(col_name,...)]
SELECT ...
[ ON DUPLICATE KEY UPDATE col_name=expr, ... ]
OR
INSERT INTO tbl_temp2 (fld_id)
SELECT tbl_temp1.fld_order_id
FROM tbl_temp1 WHERE tbl_temp1.fld_order_id > 100;
Try it like this:
<?php
$sql="INSERT INTO table1 (id, name) VALUES ('10', 'John')";
...
step 1
make sure that the table has a unique key
SHOW CREATE TABLE table1
expected result
CREATE TABLE `table1` (
`id` INT(11) default NULL,
`name` varchar(11) COLLATE utf8_general_ci NOT NULL,
PRIMARY KEY (id),
UNIQUE KEY `name` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci
if there is UNIQUE KEY name (name) we have a unique key
step 2
try to change your code
$db = new mysqli(...);
// first insert
if( !$db->query("INSERT INTO table1 (id, name) VALUES (10, 'John')") ) {
throw new Exception($db->error);
}
// second insert (for me raise: Duplicate entry 'John' for key 'name')
if( !$db->query("INSERT INTO table1 (id, name) VALUES (11, 'John')") ) {
throw new Exception($db->error);
}
Please, try these two steps
Side note: if you have name and id as duplicates, only the first duplicate encountered will be returned in the message.
The only issue i have with your code is that:
having setup your table and columns.
I setup a unique index on the table. I did .. stuff on a two column table that ensure it works.
You missed the 'new'
keyword when you 'throw exceptions'.
this is the only error with your posted code that i could find.
i.e: throw new Exception('Division by zero.'); // example taken from PHP manual.
Okay, I'm using GLOBALS to set some settings within my whole site
$tmp = $GLOBALS['_ODB']->query("SELECT * FROM `options`");
$GLOBALS['options'] = NameToTop($tmp->fetchAll(PDO::FETCH_ASSOC));
I have this as my query, then I use this function to put the returned data in an array
So I can call it by using $GLOBALS['settings']['setting1']
function NameToTop($arr)
{
$output = array();
foreach ($arr as $val) {
$output[$val['name']] = $val['value'];
}
return $output;
}
Then here is the settings table, I don't see why this is going wrong; I really need some help.
CREATE TABLE IF NOT EXISTS `options` (
`ID` int(11) NOT NULL AUTO_INCREMENT,
`name` text NOT NULL,
`value` text NOT NULL,
PRIMARY KEY (`ID`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1 AUTO_INCREMENT=6 ;
--
-- Dumping data for table `options`
--
INSERT INTO `options` (`ID`, `name`, `value`) VALUES
(1, 'setting1', 'Name'),
(2, 'email', 'webmaster#gmail.com'),
(3, 'site_title', 'Title of Site'),
I'm getting
Call to a member function fetchAll() on a non-object
You're expecting $tmp to be a PDOStatement object in order to call fetchAll() on it but it isn't, hence the error message you're seeing.
PDO::query() returns false on failure, so this is most likely what is happening.
This comment from the PDO manual talks about the return value of query():
The handling of errors by this function is controlled by the
attribute PDO::ATTR_ERRMODE.
Use the following to make it throw an exception:
$dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
You need to read up on error handling in PDO. Most people do tend to go down the exceptions route.
That would make your example something like this:
try {
$tmp = $GLOBALS['_ODB']->query("SELECT * FROM `options`");
$GLOBALS['options'] = NameToTop($tmp->fetchAll(PDO::FETCH_ASSOC));
} catch (PDOException $e) {
// handle the error in some way
echo $e->getMessage();
}
I have the following kind of error that happens on my website from time to time.
2012/07/12 21:21:48 [error] [system.db.CDbCommand] Error in executing
SQL: INSERT INTO `seo_page` (`grouped`, `results_count`, `page`, `operation`, `subtype`, `type`, `state`, `zone`, `city`, `district`, `custom`, `property`, `date_add`, `date_upd`) VALUES (:yp0, :yp1, :yp2, :yp3, :yp4, :yp5, :yp6, :yp7, :yp8, :yp9, :yp10, :yp11, :yp12, :yp13)
Basically the code works, and it saves the model correctly, I think I only get this kind of errors when it is trying to do two inserts at the same time with the same primary key, or maybe it doesn't ensure the uniqueness of the keyword field before inserting.
The script it is soposed to insert or update a keyword in the database, the code is like this:
static public function quickAdd($keyword) {
if (strlen($keyword['name'])==0)
return;
$seo_keyword = SeoKeyword::model()->find("keyword=:keyword", array("keyword"=>$keyword['name']));
if ($seo_keyword) {
// return;
} else {
$seo_keyword=new SeoKeyword();
}
$seo_keyword->keyword=$keyword['name'];
if (is_numeric($keyword['position'])) {
$seo_keyword->position=$keyword['position'];
}
try {
$seo_keyword->save();
} catch (Exception $e) {
}
return $seo_keyword;
}
The mysql table looks like this:
CREATE TABLE IF NOT EXISTS `seo_keyword` (
`id_seo_keyword` int(11) NOT NULL AUTO_INCREMENT,
`keyword` varchar(255) NOT NULL,
`position` int(11) DEFAULT NULL,
`date_add` datetime NOT NULL,
`date_upd` datetime NOT NULL,
PRIMARY KEY (`id_seo_keyword`),
UNIQUE KEY `keyword` (`keyword`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 AUTO_INCREMENT=2 ;
I am also getting this kind of error with other tables, all of them are tables that have many rows and are mostly used for stathistical data.
The fact is that I can only see that it could not insert, but there's no error like: primary key problem, servery busy or anything.
Try
var_dump($seo_keyword->getErrors());
after
$seo_keyword->save();
and lookout what is the actually problem so that insert query fails.
Iam having some trouble with a PDO execute statement. My code looks like this:
try {
$stmt = $this->dbh->prepare("INSERT INTO smtp_servers (host, port, ssl, auth, username, password) VALUES(:host, :port, :ssl, :auth, :username, :password)");
$stmt->bindParam(':host', $serverOptions[0]);
$stmt->bindParam(':port', $serverOptions[1]);
$stmt->bindParam(':ssl', $serverOptions[2]);
$stmt->bindParam(':auth', $serverOptions[3]);
$stmt->bindParam(':username', $serverOptions[4]);
$stmt->bindParam(':password', $serverOptions[5]);
$stmt->execute();
} catch (PDOException $e) {
print("fail");
}
It dosen't print "fail" if i make a print before the $stmt->execute(); it prints the test, but if i make a print just after the execute line, it dosen't print the text.
Any ideas?
UPDATED:
I tried to throw the more generic Exception, without any luck, i implemented PDO::PARAM_INT where i am using integers. And also added the line:
$this->dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
Now my code looks like this:
try {
$stmt = $this->dbh->prepare("INSERT INTO smtp_servers (host, port, ssl, auth, username, password) VALUES(:host, :port, :ssl, :auth, :username, :password)");
$stmt->bindParam(':host', $serverOptions[0]);
$stmt->bindParam(':port', $serverOptions[1], PDO::PARAM_INT);
$stmt->bindParam(':ssl', $serverOptions[2], PDO::PARAM_INT);
$stmt->bindParam(':auth', $serverOptions[3], PDO::PARAM_INT);
$stmt->bindParam(':username', $serverOptions[4]);
$stmt->bindParam(':password', $serverOptions[5]);
$stmt->execute();
} catch (PDOException $e) {
debug("fail");
}
I quess it would help to see my db table design?
CREATE TABLE IF NOT EXISTS `smtp_servers` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`host` varchar(50) NOT NULL,
`port` int(11) DEFAULT NULL,
`ssl` smallint(11) DEFAULT NULL,
`auth` smallint(11) DEFAULT NULL,
`username` varchar(50) DEFAULT NULL,
`password` varchar(50) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1 AUTO_INCREMENT=1 ;
Now i am getting this error:
Fatal error: Uncaught exception 'PDOException' with message 'SQLSTATE[42000]: Syntax error or access violation: 1064 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 'ssl, auth, username, password FROM smtp_servers' at line 1' in /var/www/isms/php/communication/Mail.php on line 13 PDOException: SQLSTATE[42000]: Syntax error or access violation: 1064 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 'ssl, auth, username, password FROM smtp_servers' at line 1 in /var/www/isms/php/communication/Mail.php on line 13 Call Stack: 0.0002 649056 1. {main}() /var/www/isms/index.php:0 0.0023 867336 2. include('/var/www/isms/php/settings/ismsSettings.php') /var/www/isms/index.php:88 0.0065 1091712 3. require_once('/var/www/isms/php/settings/MailSettings.php') /var/www/isms/php/settings/ismsSettings.php:29 0.0147 2288960 4. require_once('/var/www/isms/php/forms/settings/EditSmtpServer.php') /var/www/isms/php/settings/MailSettings.php:4 0.0155 2290456 5. Mail->getServers() /var/www/isms/php/forms/settings/EditSmtpServer.php:41 0.0156 2291888 6. PDOStatement->execute() /var/www/isms/php/communication/Mail.php:13
With addition to andrewsi's comment about "MySQL reserved words"...
[ssl is indeed a MySQL reserved word!]
As it stands your create table statement also fails as is, but formatted like so; does not
CREATE TABLE `smtp_servers` (
`id` INT( 11 ) NOT NULL AUTO_INCREMENT PRIMARY KEY ,
`host` VARCHAR( 50 ) NOT NULL ,
`port` INT( 11 ) NOT NULL ,
`ssl` SMALLINT( 11 ) NOT NULL ,
`auth` SMALLINT( 11 ) NOT NULL ,
`username` VARCHAR( 50 ) NOT NULL ,
`password` VARCHAR( 50 ) NOT NULL
) ENGINE = MYISAM ;
Although, as I've been writing this I notice that StackOverflow uses the spacing grave for formatting! ( ` )(`)
So, in conclusion change your prepare statement line to the following:
$stmt = $this->dbh->prepare("INSERT INTO `smtp_servers`
(`host`, `port`, `ssl`, `auth`, `username`, `password`)
VALUES(:host, :port, :ssl, :auth, :username, :password)");
Note : only formatted this way for easier reading :)
In your code example, the fail print should only happen in case of a problem, when an Exception is thrown by the script inside the try block.
Assuming everything went smooth and the query succeeded, not seeing the line is a good sign.
If the query has not succeeded, and yet you are still not seeing the error message, try telling PDO to only throw PDOExceptions on errors:
$this->dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
Right after instantiating the PDO object.
Have you tried printing out the SQL of the query, replacing the named parameters with the actual parameters you're trying to insert, and running it from the command line?
The text of the exception implies that there's a problem with the SQL statement, rather than with the PDO connection, and running it from the command line will let you know if the SQL itself works.
It's probably because you are in a particular namespace right now. Try replacing } catch (PDOException $e) { with } catch (\PDOException $e) { and see if something changes.