I've got some code like this:
$this->validate(new \Phalcon\Mvc\Model\Validator\Uniqueness(['field' => $field]));
if (true == $this->validationHasFailed()) {
throw new SpecialInternalUniqueException();
}
This works for all columns except for natural Primary Keys. That is, Primary Keys that are not surrogate keys (auto-incrementing integers). For example, in the job_titles table, the natural key column is "job_title" - which, in our case, refers to the name of the job title. This name should be unique, and I want to be able to check for that in the code prior to saving. However, Phalcon happily ignores it, somehow.
I'm actually setting up a unit test for this right now and doing something similar to the following:
$job_title = new JobTitles();
$job_title->job_title = 'Unique Test';
$job_title->description = 'Desc A';
$job_title->save();
$job_title2 = new JobTitles();
$job_title2->job_title = 'Unique Test';
$job_title->description = 'Desc B';
$job_title->save();
The exception never gets thrown. What ends up in the database is a single column for the first Unique test with Desc A, and no record for the second one. But I don't get a thrown exception.
Any thoughts?
EDIT:
Also, I've tried with the ->create() function in place of the save() function.
First you should be aware that in the default behavior those validations are created from the actual database schema right after the model class is initialized; you're not supposed to add them manually in that case.
In other words, the default meta-data strategy for models is the Database Introspection Strategy
So a exception will only be raised if the job_title field is already indexed for uniqueness checking in the database scheme. If you aren't able to actually create this PK in the database, you may change the default meta-data strategy for your models and them set the metadata manually (sigh).
Related
I have a table in Postgres with DDL like this one:
CREATE TABLE robots(
robot_id INTEGER NOT NULL CONSTRAINT robot_id_pkey PRIMARY KEY,
name TEXT
);
I know I can insert a record with following SQL statement:
INSERT INTO robots (robot_id, name) VALUES (nextval('robots_seq'), 'WALL-E');
I need to make CRUD operations in Phalcon for this table. Also I want to use ORM features.
So I do:
$robot = new Robots();
$robot->setRobotId(new \Phalcon\Db\RawValue("nextval('robots_seq')"));
$robot->setName('WALL-E');
$robot->save();
And get the following exception:
Uncaught PDOException: SQLSTATE[22P02]: Invalid text representation:
7 ERROR: invalid input syntax for integer: 'nextval('robots_seq')';
Is there any way to accomplish this ?
To tell Phalcon what is name of your model sequence use function getSequenceName:
public function getSequenceName() {
return 'category_id_seq';
}
Phalcon assumes that your models with serial (auto increment) columns will have a sequence named [table_name]_[column_name]_seq.
In your case, in order to make Phalcon take care of handling auto increment columns you should have a sequence named robots_robot_id_seq instead of robots_seq for the column robot_id (which I'd call just "id", by the way).
This way you do not have to worry about setting the id when creating an object and after saving Phalcon will fill that field automatically for you:
CREATE TABLE robots(
robot_id SERIAL PRIMARY KEY NOT NULL,
name TEXT
);
$robot = new Robots();
$robot->setName('WALL-E');
$robot->save();
$robot->getRobotId(); // Should return last "nextval()" sequence
Hope it helps.
I am using the RedBeanPHP, I have a string of numbers, is it possible to store it like the string, not a double type? My example is next, and it doesn't work:
$participant = R::dispense('participants');
$participant->setMeta("participants.number","string");
$participant->number = $number;
R::store($participant);
Redbean check attribute given value to set the right type for the column, I would recommend you to work with freeze option off every time, but, when you need to change something, you just turn it on. I mean, just turn freeze on when you really need to perform some change on you table, for ex:
// the false param will disable db changes
R::setup('dns', 'user', 'pass', false);changes (freeze option)
//...
//... let's imagine you have some code here
//...
R::freeze(false);
$participant = R::dispense('participants');
$participant->number = 'intert any string'; // need to set field to string type
R::store($participant);
R::freeze(true);
$participant->number = '99.99';
R::store($participant);
I know it is not the best thing ever, but you just have to turn it on when you need to change something on DB structure. Essentially, in production environment you should always turn it off
RedBean will automatically try to guess the right column type for the data you provide. However, it will never shrink a column, (for example from TEXT to INTEGER), only widen (for example from INTEGER to TEXT).
If it's important for you that the database column is TEXT during development, you could therefore insert a string and delete it again to "trick" RedBean into making the column type TEXT.
For example, put this code snippet into some type of initialization script:
$participant = R::dispense('participants');
$participant->number = 'not a number';
R::store($participant);
R::trash($participant);
// Column 'participants.number' is now of type TEXT
As I mentioned earlier, RedBean will never shrink the column to INTEGER even if you never insert anything else than number strings again.
On the other hand, if it's not critical to you during development, you could just freeze the database before deploying to production and manually change the column type to TEXT in your database manager.
I'm having a table whith 'id' as pk of type int.
When I'm doing the folowing mysql query in php activerecord
Location_cat::find_by_sql("select concat('#',id) as 'id', text FROM location_cat");
it returns 'id' as 0, instead of '#142'for example.
Does anyone knows this behavior?
You are trying to overwrite the 'id' field, but this will give you issues. An activerecord object knows it's own primary key (i'd assume the id-field), and uses it for all sorts of stuff. If you try to select something else into that field, this will not work.
For instance, you get the object with id 1. This is renamed to #1 by you. now AR thinks it is an object with a primary key that is #1. Which is not true
Bottomline is, you should not put something else in the id field if you want to work with an actual activerecord object.
What you might want to do is just get the data, but on returning the $object->id field, you can add the #. For instance, make a magic getter for id (I believe AR allows you to name this getId(), but otherwise, fix it in __get().
Lets Cast the id into a string before concatenating
SELECT CONCAT('#', CAST(id AS CHAR(15))) AS nid
FROM location_cat
I have a strange situation.
Suppose I have a very simple function in php (I used Yii but the problem is general) which is called inside a transaction statement:
public function checkAndInsert($someKey)
{
$data = MyModel::model()->find(array('someKey'=>$someKey)); // search a record in the DB.If it does not exist, insert
if ( $data == null)
{
$data->someCol = 'newOne';
$data->save();
}
else
{
$data->someCol = 'test';
$data->save();
}
}
...
// $db is the instance variable used for operation on the DB
$db->transaction();
$this->checkAdnInsert();
$db->commit();
That said, if I run the script containing this function by staring many processes, I will have duplicate values in the DB. For example, if I have $someKey='pippo', and I run the script by starting 2 processes, I will have two (or more) records with column "someCol" = "newOne". This happens randomly, not always.
Is the code wrong? Should I put some constraint in DB in form of KEYs?
I also read this post about adding UNIQUE indexes to TokuDB which says that UNIQUE KEY "kills" write performance...
The approach you have is wrong. It's wrong because you delegate the authority for integrity/uniqueness check to PHP, but it's the database that's responsible for that.
In other words, you don't have to check whether something exists and then insert. That's bad because there's always some slight ping involved between PHP and MySQL and as you already saw - you can get false results for your checks.
If you need unique values for certain column or combination of columns, you add a UNIQUE constraint. After that you simply insert. If the record exists, insert fails and you can deal with it via Exception. Not only is it faster, it's also easier for you because your code can become a one-liner which is much easier to maintain or understand.
I'd like RedBean to create unique keys/indexes when generating the schema. The following code does- opposed to how I understand the documentation- not do this:
R::setup('sqlite:rss_loader.db3');
$bean = R::findOne(IMG);
if (!$bean->id) {
$bean = R::dispense(IMG);
$bean->setMeta("buildcommand.unique.0", array('url'));
$bean->url = 'text';
R::store($bean);
$bean->wipe();
R::freeze(); //no more schema changes!
}
What is happening in sqlite ist this:
create table img (id integer primary key autoincrement, url)
What I was expecting was this:
create table img (id integer primary key autoincrement, url text unique)
Can this be achieved without write SQL against RedBean?
What version of Redbean are you using? It looks like they updated the buildcommand in the latest version. This is what the manual says:
$bean->setMeta("buildcommand.unique" , array(array($property1, $property2)));
Plugging in what you have:
$bean->setMeta("buildcommand.unique" , array(array('url')));
If that doesn't work, you may have to read the actual code under the setMeta function and see what is actually going on.
To do this on an existing table it is sufficient to "store" an empty bean like this- no data needs to be added to the DB:
$bean = R::dispense(IMG);
$bean->setMeta("buildcommand.unique", array(array(...)));
R::store($bean);
(Word of warning, if you freeze after doing this, you're not guaranteed to have all your columns)