CakePHP 3: Model Unit Test fails - "duplicate key value" - postgresql

I'm using Postgres (which I think is related to the problem), and CakePHP 3.
I have the following unit test to just check to make sure that a valid dataset can get saved by the model. When I run the following test, with a standard "bake'd" Model unit test, I get the error below.
I think this is the problem:
We are using fixtures to add some base data. This is the only place that I think might be causing a problem. To add credence to this, while the unit tests were running I ran the following command to get the next auto-incrementing id value and it returned 1, even though it returned the proper number in non-test DB. Select nextval(pg_get_serial_sequence('agencies', 'id')) as new_id;
Unit Test:
public function testValidationDefault()
{
$agencyData = [
'full_name' => 'Agency Full Name',
'mode' => 'transit',
'request_api_class' => 'Rest\Get\Json',
'response_api_class' => 'NextBus\Generic',
'realtime_url_pattern' => 'http://api.example.com',
'routes' => '{"123": {"full_route": "123 Full Route", "route_color": "#123456"}}'
];
$agency = $this->Agencies->newEntity($agencyData);
$saved = $this->Agencies->save($agency);
$this->assertInstanceOf('App\Model\Entity\Agency', $saved);
}
Error:
PDOException: SQLSTATE[23505]: Unique violation: 7 ERROR: duplicate key value violates unique constraint "agencies_pkey"
DETAIL: Key (id)=(1) already exists.
Things I've tried
Copied that same code into a controller, and it successfully added the entity in the table.
Adding an id of 200. Same error appears.
Update 1
The fixture for this does have the ID field set each record. Deleting them from the fixture does work, but it breaks other unit tests that rely on some relational data.

I don't like this solution, but adding the following before saving the entity does work.
$this->Agencies->deleteAll('1=1');

[UPDATE: My other answer is the real solution to this problem.! You don't have to do this anymore...]
Here is a less dirty workaround that doesn't require deleting all the records:
use Cake\Datasource\ConnectionManager;
...
$connection = ConnectionManager::get('test');
$results = $connection->execute('ALTER SEQUENCE <tablename>_id_seq RESTART WITH 999999');
//TEST WHICH INSERTS RECORD(s)...
It appears that the auto-incrementing doesn't get properly set/reset during the setUp() or tearDown()... so manually setting it to something really high (greater than the number of existing records) prevents the "duplicate key..." error.
The benefit of this hack (over deleteAll('1=1')) is that you can still subsequently run tests that reference existing DB data.

It might be a problem in your fixture definition. The Cake PHP documentation uses a _constraints field specifying that the id field is a primary key:
'_constraints' => [
'primary' => ['type' => 'primary', 'columns' => ['id']],
]

I believe I've finally figured out the REAL solution to this problem!
I believe this issue stems from a default fixture setting that results from using the bake command to generate fixtures.
When you bake a model it creates the boilerplate for it's fixtures. Notice the autoIncrement for the ID property in the code below? Contrary to what you might think, this should not but true. When I set it to null and remove the ids from the items in the $records array I no longer get uniqueness errors.
public $fields = [
'id' => ['type' => 'integer', 'length' => 10, 'autoIncrement' => true, 'default' => null, 'null' => false, 'comment' => null, 'precision' => null, 'unsigned' => null],
'nickname' => ['type' => 'text', 'length' => null, 'default' => null, 'null' => false, 'comment' => null, 'precision' => null],
...
public $records = [
[
// 'id' => 1,
'nickname' => 'Foo bar',
'width' => 800,
...
The ninja wizards on the CakePHP project are the heroes: source
CakePHP ticket

If id fields are removed from fixture records then they will utilize auto-incrementing when inserted, leaving the table's ID sequence in the right place for inserts that happen during tests. I believe that is why it works for #emersonthis as described above.
That solution has another problem, though: you can't create dependable relationships between fixture records because you don't know what IDs they will get. What do you put in the foreign ID field of a related table? This has led me back to his original solution of just altering the table sequence after records with hard-coded IDs have been inserted. I do it like this in affected TestCases now:
public $fixtures = [
'app.articles',
'app.authors',
];
...
public function setUp()
{
$connection = \Cake\Datasource\ConnectionManager::get('test');
foreach ($this->fixtures as $fixture) {
$tableName = explode('.', $fixture)[1];
$connection->execute("
SELECT setval(
pg_get_serial_sequence('$tableName', 'id'),
(SELECT MAX(id) FROM $tableName)
)");
}
}
This moves the auto-increment sequence to the highest previously-used ID. The next time an ID is generated from the sequence it will be one higher, resolving the problem in all cases.
Including one of these solutions in an upcoming CakePHP release is being discussed here.

Related

Get created object with all fields

I use DBIx::class and postgreSQL.
Imagine table 'Company'. It has fields:
id => {
data_type => 'integer',
is_auto_increment => 1,
},
name => {
data_type => 'text',
},
is_client => {
data_type => 'boolean',
default_value => 'true',
},
When I create new company passing the company name only, create method returns primery key and field I passed: id, name.
How can I get all fields with default values without additional request to DB?
In other words, I want the create method to return all fields with defaults to me.
The DBIx::Class::ResultSet create method returns a DBIx::Class::Result object which has all column values filled.
On databases that support it, DBIx::Class uses RETURNING to get back server populated column values.
See https://metacpan.org/pod/DBIx::Class::ResultSource#retrieve_on_insert how to use that feature.

get_entry_list() method with query parameter on sugar crm 7 does not work

My code is as below..
Please note that I am trying to retrive all the accounts whose where the Accounts.name='bhagya'.
The same query workes for me in SugarCRM Version 6.5 (Community edition) but in case of SugarCRM 7 it is not working when I mention query parameter. If I mention 'query'=>'' then I get all the records from the sugarcrm 7 server. It fails when I specify any filter for query parameter. I am using RestAPI - 4.1
$get_entry_list_parameters = array(
//session id
'session' => $session_id,
//The name of the module from which to retrieve records
'module_name' => 'Accounts',
//The SQL WHERE clause without the word "where".
//'query' => "Accounts.billing_address_postalcode='60329'",
// 'query' => "Accounts.name='Ingrid Rofalsky'",
'query'=>'',
//The SQL ORDER BY clause without the phrase "order by".
'order_by' => "",
//The record offset from which to start.
'offset' => 0,
//A list of fields to include in the results.
'select_fields' => array(
'id',
'name',
),
//A list of link names and the fields to be returned for each link name.
'link_name_to_fields_array' => array(),
//The maximum number of results to return.
'max_results' => 10,
//If deleted records should be included in results.
'deleted' => 0,
//If only records marked as favorites should be returned.
'favorites' => false,
);
print_r($get_entry_list_parameters);
$get_entry_list_result = call('get_entry_list', $get_entry_list_parameters, $url);
echo '<pre>';
print_r($get_entry_list_result);
echo '</pre>';
Can some one help me on this..
Thank you.
Regards
- Bhagya
The Rest API has changed a lot in Sugar7.
The URL for the invokation should look like
http://servname.com/pro720/rest/v10/Accounts?filter[0][name][$starts]=B&filter[0][email_addresses.email_address]=burgers#example.com&fields=name,account_type,description,email
Where you are filtering the accounts with a name that starts with B, the mail address is burgers#example.com and where you retrieve only the fields name,account_type,description and email
Changing the module name to lowercase in query option solved this problem.
I got the answer for my query from below link.
https://community.sugarcrm.com/sugarcrm/topics/get_entry_list_method_with_query_parameter_on_sugar_crm_7_does_not_work?topic-reply-list%5Bsettings%5D%5Bfilter_by%5D=all&topic-reply-list%5Bsettings%5D%5Bpage%5D=1#reply_14588280
Thanks again
Regards
- Bhagya

FuelPHP ORM Primary Key on model cannot be changed

I've been banging my head with this ORM error:
Fuel\Core\FuelException [ Error ]: Primary key on model Model_CustomValue cannot be changed.
Here are relevant info from my models I'm having issues with:
<?php
use Orm\Model;
class Model_Purchase extends Model
{
protected static $_has_many = array(
'customvalues' => array(
'model_to' => 'Model_CustomValue',
'key_to' => 'purchase_id',
'cascade_delete' => true,
)
);
protected static $_properties = array(
'id',
'customer_id',
'payment_id',
'audit_id',
'created_at',
'updated_at',
);
<?php
use Orm\Model;
class Model_CustomValue extends Model
{
protected static $_table_name = 'customvalues';
protected static $_primary_key = array('purchase_id', 'customfield_id');
protected static $_belongs_to = array(
'purchase' => array(
'key_from' => 'purchase_id',
'model_to' => 'Model_Purchase',
'key_to' => 'id',
),
);
When trying to save the Model_Purchase with an array of Model_CustomValue objects as a property named 'customvalues' on the $purchase object, I get the "Primary key on model Model_CustomValue cannot be changed."
I've tried swapping the key_from/to in the "belongs_to" on the Model_CustomValue, but to no avail.
I'm using Fuel 1.6 (hash: 6e6d764)
Please let me know if more information would be helpful, and I'll provide.
From the FuelPHP forum thread, Harro answered:
You can not have a column which is at the same time FK and PK. Which
you have on your Model_CustomValue.
The reason for that is that when you disconnect a relation, the FK
will be set to NULL, which should not happen with a PK.
I then clarified, for those of us who may need specific examples from the original example, I confirmed the following:
So just re-stating why that's not allowed:
Model_CustomValue uses the "purchase_id" as part of its PK as well as the FK to Model_Purchase. And if the two Models were to be unlinked, that would lead to a null portion of the PK for Model_CustomValue -- which obviously isn't allowed.

CakePHP 2.2 with PostgreSQL Failed new row insert - Database Error: Undefined table: 7 ERROR: relation "table_id_seq" does not exist

My problem is as follows.
After deleting multiple rows from table, inserting new record into same table results in error.
Database Error
Error: SQLSTATE[42P01]:
Undefined table: 7 ERROR: relation "order_details_id_seq" does not exist
Table
CREATE TABLE schema.order_details (
id serial NOT NULL,
order_id integer NOT NULL,
field_1 integer,
field_2 real,
field_3 character varying(15),
CONSTRAINT order_details_pkey PRIMARY KEY (id )
)
WITH (
OIDS=FALSE
);
Insert is
INSERT INTO "schema"."order_details" ("order_id", "field_1", "field_2", "field_3")
VALUES (37, 1, 2, 'value');
Sequence "schema"."order_details_id_seq" in used schema exists.
CREATE SEQUENCE schema.order_details_id_seq
INCREMENT 1
MINVALUE 1
MAXVALUE 9223372036854775807
START 37
CACHE 1;
Models.
// Model
class Order extends AppModel {
public $useDbConfig = 'other_data';
public $hasMany = array(
'OrderDetail' => array(
'className' => 'OrderDetail',
'foreignKey' => 'order_id',
'dependent' => true,
'order' => array(
'OrderDetail.order_id',
'OrderDetail.field_1'
))
);
class OrderDetail extends AppModel {
public $useDbConfig = 'other_data';
public $belongsTo = array(
'Order' => array(
'className' => 'Order',
'foreignKey' => 'order_id',
'dependent' => true
),
// model Order save code on recreation of order
$this->OrderDetail->deleteAll(array('OrderDetail.order_id' => $this->id));
At this point tried to insert $this->OrderDetail->query('VACUUM FULL ANALYZE order_details'); with no effect
foreach ($details as $d) {
$this->OrderDetail->create();
$this->OrderDetail->save($d /*array(
'order_id' => $this->id,
'field_1' => 1,
'field_2' => 2,
'field_3' => 'value'
)*/);
}
I get error on first foreach loop.
Weirdest thing is that problem appears and disappears after some time randomly.
Any suggestions on what it could be and how to get rid of it?
Currently solved problem using code.
$this->Order->id = $id;
$this->Order->delete();
It fires 2 queries for each row (100 extra in my case!) of delete statements instead of two in case of
$this->OrderDetail->deleteAll(array('OrderDetail.order_id' => $id));
So for this time it has space for improvement.
EDIT: Currently code works as it should with tweaked DboSource.
It seems that cake was looking in public schema for sequence where it is not located.
Fixed it by tweaking to include schema name in last insert getter inf file Model/Datasource/DboSource.php create method with this diff
## -1006,7 +1006,7 ##
if ($this->execute($this->renderStatement('create', $query))) {
if (empty($id)) {
- $id = $this->lastInsertId($this->fullTableName($model, false, false), $model->primaryKey);
+ $id = $this->lastInsertId($this->fullTableName($model, false, true), $model->primaryKey);
}
$model->setInsertID($id);
$model->id = $id;
I know that modifying core is not the way to go, but as long as it is working it is fine with me.
This happened to me because I modified the name of the table, but PostgreSQL did not change the name of the sequences. Knowing this, I changed the name of the sequences that affected this table and it was resolved.
To prevent this error, use this convention to name your sequence when using cakephp: table_name_id_seq. For example:
table name: user
sequence name should be: user_id_seq
If you alredy have sequences, you can rename it in posgres like this
alter sequence user_seq rename to user_id_seq
I'm not a fun of this way to name sequence but it prenvent this kind of errors in my case

How do I make a DBIx::Class relationship with a fixed join condition?

We have a link table that can handle multiple types of object on one side, and I can't work out how to get from one of these objects to the link table using has_many.
Example: link table contains:
id link_id link_table resource_id
1 1 page 3
2 1 page 5
3 2 page 3
4 1 not_page 1
Building the relationship from the resource side is easy enough:
Resource->has_many(links => 'Link', 'resource_id');
but I haven't been able to get the corresponding relationship from the page side:
Page->has_many(links => 'Link', 'link_id');
would get the not_page link
Page->has_many(links => 'Link', {'foreign.link_id' => 'self.id', 'foreign.link_table' => 'page'});
gives an 'Invalid rel cond val page' error (which was not that surprising to me).
Page->has_many(links => 'Link', {'foreign.link_id' => 'self.id', 'foreign.link_table' => '"page"'});
gives an 'Invalid rel cond val "page"' error. Throwing backslashes in didn't help.
DBIx::Class::Relationship::Base says:
The condition needs to be an SQL::Abstract-style representation of the join between the tables
and I have tried various different options from there, such as:
Page->has_many(links => 'Link', {'foreign.link_id' => 'self.id', 'foreign.link_table' => {'=', 'page'}});
but without any success at all.
If I added another field to the page table which always contains the value 'page' I could do
Page->has_many(links => 'Link', {'foreign.link_id' => 'self.id', 'foreign.link_table' => 'self.what_table_am_i'});
but that's hardly an optimal solution.
Splitting the link table into a separate one for each type may be a possibility, but this is an existing project that is being considered for adaptation to DBIx::Class, and there may be other places where splitting a table into multiple other tables is more hassle than it's worth.
You should just make a wrapper method that calls the relationship with the required arguments:
Page->has_many(__all_links => 'Link', 'link_id');
sub links {
shift->__all_links({link_table => 'page'});
}
This would be pretty easy to turn into a DBIx::Class component if you have multiple tables that need to have this kind of join logic.
It can be specified in the has_many call like so:
Page->has_many(links => 'Link', 'link_id',
{ where => { link_table => 'page'} });
See: DBIx::Class Cookbook