What is the best way in PHP to handle foreign key exceptions on a mysql database? Is there a mysql class that can be used to simplify any code?
Ideally, what I want to do, as an example, is to try to delete a record where it is the foreign key parent to any number of child tables. The foreign key throws the exception, so then I would like to be able to look at each foreign key table and test it, giving meaningful feedback on the tables and number of records causing the exception. This would then be returned as the error so the end user can reference and delete the offending records.
The way I handle this is to set up my database wrapper class to always throw an exception when you encounter a database error. So, for instance, I might have a class called MySQL with the following functions:
public function query($query_string)
{
$this->queryId = mysql_query($query_string,$this->connectionId);
if (! $this->queryId) {
$this->_throwException($query_string);
}
return $this->queryId;
}
private function _throwException($query = null)
{
$msg = mysql_error().". Query was:\n\n".$query.
"\n\nError number: ".mysql_errno();
throw new Exception($msg,mysql_errno());
}
Any time a query fails, a regular PHP exception is thrown. Note that I would throw these from within other places too, like a connect() function or a selectDb() function, depending on whether the operation succeeded or not.
With that set up, you're good to go. Any place you expect that you might need to be handling a database error, do something like the following:
//assume $db has been set up to be an instance of the MySQL class
try {
$db->query("DELETE FROM parent WHERE id=123");
} catch (Exception $e) {
//uh-oh, maybe a foreign key restraint failed?
if ($e->getCode() == 'mysql foreign key error code') {
//yep, it failed. Do some stuff.
}
}
Edit
In response to the poster's comment below, you have some limited information available to you to help diagnose a foreign key issue. The error text created by a failed foreign key restraint and returned by mysql_error() looks something like this:
Cannot delete or update a parent row:
a foreign key constraint fails
(`dbname`.`childtable`, CONSTRAINT `FK_name_1` FOREIGN KEY
(`fieldName`) REFERENCES `parenttable` (`fieldName`));
If your foreign keys are complex enough that you can't be sure what might cause a foreign key error for a given query, then you could probably parse this error text to help figure it out. The command SHOW ENGINE INNODB STATUS returns a more detailed result for the latest foreign key error as well.
Otherwise, you're probably going to have to do some digging yourself. The following query will give you a list of foreign keys on a given table, which you can examine for information:
select * from information_schema.table_constraints
WHERE table_schema=schema() AND table_name='table_name';
Unfortunately, I don't think there's a magic bullet to your solution other than examining the errors and constraints very carefully.
I think the best bet would be for you to do a transaction. That way, the insert will always be valid, or not done at all. That can return an error message that you can work with as well. This will prevent you from having to manually check every table - the db does it for you.
Related
I use doctrine ODM to work with MongoDB. I have documents to save which can duplicate time to time. I need only 1 copy of each event, so I use hashed uniq key to ensure event is only 1.
So I do several ->persist($document);
And when I do ->flush();
I'm getting an exception:
localhost:27017: E11000 duplicate key error index: dbname.event.$eventKey_1 dup key: { : "keyValue" }
And all data never persisted to MongoDB. So question is: is any way to persist uniq data and ignore existing without doing:
try {
->persist();
->flush();
} catch (\Exception $e) {}
for each document?
Thank you.
Updated:
thanks for your answers and your time, but I found exact solution :)
Mongo function insert has an option "ordered: "
https://docs.mongodb.org/manual/reference/method/db.collection.insert/
which allow continue insertion even after errors.
Doctrine use Pecl extension for mongo.
doctrine flush() use this method:
http://www.php.net/manual/en/mongocollection.batchinsert.php
which has option "continueOnError"
So if you do this way:
$documentManager->flush(null, ['continueOnError' => true]);
It will save all documents without errors and skip all with errors. Though it will throw "\MongoDuplicateKeyException". So all you need - catch this exception and handle or simply ignore it (depending on your needs).
Something like this :)
The native Doctrine methods do not support filtering unique values - you need to do this on your own.
To insert those data without any errors you have to do a few things, depending on your entity structure:
Find all existing entities with the unique keys you have
Find unique keys that are duplicated between the entities you are trying to persist
Replace the already existing entities with the entities you found
Persist and flush
There is absolutely no chance to do this without at least one additionally query. If you had the primary key of the existing entities, you could use those to get a reference object. But unfortunately, there is no support for getting references by unique keys according to the doctrine documentation:
http://doctrine-orm.readthedocs.org/en/latest/reference/limitations-and-known-issues.html#join-columns-with-non-primary-keys
It is not possible to use join columns pointing to non-primary keys. Doctrine will think these are the primary keys and create lazy-loading proxies with the data, which can lead to unexpected results. Doctrine can for performance reasons not validate the correctness of this settings at runtime but only through the Validate Schema command.
I was unable to get the ['continueOnError' => true] to work, so solved it differently:
$collection = $documentManager->getDocumentCollection(Content::class);
try {
$collection->insertMany($arrayData, ['ordered' => false]);
} catch (BulkWriteException $exception) {
// ignore duplicate key errors
if (false === strpos($exception->getMessage(), 'E11000 duplicate key error')) {
throw $exception;
}
}
You can just do: Search your existing id edit it and save..
With Doctrine + symfony:
$Document = $EntityManager->getRepository("ExpBundle:Document")->find(123);
$Document->setTitile("Doc");
$EntityManager->flush();
With Doctrine
$Document = $EntityManager->find('Document', 1234);
$Document->setTitle("edited");
$EntityManager->flush();
I have a spelling error in migrations:
Schema::create('business_category',function(Blueprint $table){
$table->integer('business_id')->unsinged();
$table->integer('category_id')->unsinged();
});
Schema::create('business_category',function(Blueprint $table){
$table->foreign('business_id')->references('id')->on('business');
$table->foreign('category_id')->references('id')->on('category');
});
and I run "php artisan migrate"
this error has been shown:
[Illuminate\Database\QueryException]
SQLSTATE[HY000]: General error: 1005 Can't create table
'brandecide.#sql-42 4_aa' (errno: 150) (SQL: alter table
business_category add constraint bus
iness_category_business_id_foreign foreign key (business_id)
references business (id))
this error caused by:
$table->integer('business_id')->**unsinged**();
and I should change this to
$table->integer('business_id')->**unsigned**();
to fix it.
How should I understand this from the error?
How should I understand this from the error?
You can't, unfortunately. I agree the error is quite misleading. The problem is that unique() is not a real method. (If so you'd get a method undefined exception)
Instead the call to unique() ends up being caught by Illuminate\Support\Fluent
public function __call($method, $parameters)
{
$this->attributes[$method] = count($parameters) > 0 ? $parameters[0] : true;
return $this;
}
And is then added to $this->attributes without doing any checking. This then results in no error but just an attribute (unsinged) that will never be used and a missing unsigned that causes the constraint to fail.
If you want you can create an issue (with type "proposal") on github. Maybe someone has a good idea how this can be prevented. (e.g. a whitelist of recognized methods)
You probably already have some entries in your business_category table. When running that migration, you're trying to add a column that references another table. As you didn't define any default value, MySQL tries to default the value to null, but that corresponds to nothing in that other table. When having a foreign key, the value in that column MUST correspond to a value in another table.
So to solve it, you could create a ->default(1), assuming that the id 1 exists in your business-table.
I have added a unique key over two columns in my database;
ALTER TABLE `foo`
ADD UNIQUE KEY `foo_uk` (`field1`, `field2`);
This is working correctly, I get an integrity constraint violation if I attempt to add a row where the values of these two fields already exist in another row.
However, I can't get Yii to pick up the restraint.
In my model I've added a rule;
public function rules()
{
return array(
...
array('field1', 'unique', 'criteria'=>array(
'condition'=>'`field2`=:field2',
'params'=>array(
':field2'=>$this->field2
)
),
...
);
}
I can see this rule is running when I save the model and is performing the following query during validation:
SELECT 1 FROM `foo` `t` WHERE (`field2`=:field2) AND (`t`.`field1`=:ycp0) LIMIT 1
I've checked by running this command in mysql and it's returning a correct result, When the table is empty, it returns 0 sets, and when I try to add an identical row, it's returning 1.
However, for some reason it's not triggering a fail on validation, instead it's throwing a CDbException:
CDbCommand failed to execute the SQL statement: SQLSTATE[23000]: Integrity
constraint violation: 1062 Duplicate entry 'field1TestData-field2TestData' for
key 'foo_uk'. The SQL statement executed was: INSERT INTO `foo` (`field1`,
`field2`...etc) VALUES (:yp0, :yp1...etc)
I would expect the validation before the save occurs to throw an error gracefully and take me back to the form with the correct error message ('field1' and 'field2' must be unique or whatever it would be)?
Please could someone point me in the right direction as to what's going on here. Do I need to create my own validation method, I am assuming here that Yii should handle the validation error as it's in the rules method for the model?
May be this extension will help you : unique-attributes-validator
From the extension page:
Yii comes with bucket of validators including a unique validator which validates whether an attribute value is unique in the corresponding database table. But what if your database unique constraint contains more than one attribute?
This is what I'm using on my projects.
here's an example
public function rules() {
return array(
array('firstKey', 'UniqueAttributesValidator', 'with'=>'secondKey'),
);
}
i got some stuck when accessing a yii's web application. I have configured as the same as the owner's setting, but while i tried to access, i got an error "Column must be either a string or an array". How could i solve it? Thanks in advance..
When reporting error messages, it helps to have the precise error message. The actual error message is: "Column name must be either a string or an array". With an exact string you can search the framework files to find where it is mentioned.
Looks like some method somewhere is passing an invalid column name to createInCondition method of CDbCommandBuilder.
See line 722: https://github.com/yiisoft/yii/blob/1.1.13/framework/db/schema/CDbCommandBuilder.php
Looking at a couple instances where that method is called, I would guess that you have a database table without a primary key somewhere. That is one possible explanation for the problem. Other explanations will require a lot more details on your part.
Provide the stack trace that the error page provides you with when in debug mode along with your table schema.
This happens when you don't have a primary key in your table and you try to do an update. I got this problem because I had a composite primary key in my table. I was being handled well on all operations until I wanted to update a model.
Just add an int primary key, call it 'id' to your table with auto increment. It should do the trick.
Be sure to disable schema caching (if you're using that) before you test this. The change wont take effect until your schema cache expires.
Maybe you do not have primary key in your table. If you use the method $model->save() to save or use method $model->update() ($model is CActiveRecord instance), you will get this error.
Because the method update in CActiveRecord using Primary key to update (Read more here
)
Source Code: framework/db/ar/CActiveRecord.php#1115
if($this->_pk===null)
$this->_pk=$this->getPrimaryKey();
$this->updateByPk($this->getOldPrimaryKey(),$this->getAttributes($attributes));
$this->_pk=$this->getPrimaryKey();
You can use method updateAll() instead of update() or updateByPk()
Take a look this link
http://www.yiiframework.com/forum/index.php/topic/3887-cdbexception-column-name-must-be-either-a-string-or-an-array/
It seems your table doesn't have a primary key or the primary key doesn't well restored which usually caused by corrupt back up file.
If you forgot add return value you will have error you showed. Simple example, your model with such method will return error on PK
...
public function relations()
{
}
...
Your have to add return value.
/**
* #return array
*/
public function relations()
{
return array();
}
If you are not using such methods you should delete them, or add 'default return values'. Otherwise it gives errors the same as it was primary key or other DB issues (because model read invalid data and didn't all things it should).
Lets say that I have a table with column called a. It has index UNIQUE KEY on it.
In ORM model, I try to insert into that table. This is a way to catch Database_Exception [ 1062 ] that occurs when user tries to insert something in column a that's not unique:
function save(Validation $validation = null) {
try {
parent::save($validation);
}
catch (Database_Exception $exception) {
if ($exception->getCode() === 1062) {
// PK?
}
}
}
Now I'm trying to get primary key of entry that already have that content what I tried to duplicate. Is it possible without any more SQL queries? I hope that primary key of that row is returned somewhere.
Sorry about non-sense, but it was kinda hard to explain. Thanks in an advice!
NO, this is not posible.
This is not expected also to have the primary key for that duplicate entry from a exception.
It is underlying database which throw this exception.
It could be possible if database could report that message somehow. But it will lead to so many similar cases where much more info is needed. And all the database vendor needs to confirm this which is impossible, I believe.