You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Question is though, should adding those unique: 'name_cat' options cause a unique validator to be automatically added to the model? Adding required: true automatically adds a required validator.
I think, if possible, we should avoid client side validators and rather rely on the database engine (since it will be a factor faster). When we add required: true we are also including that information in the database definition by appending NOT NULL to any columns.
I'd recommend that if the database is able to, we avoid adding the validator and instead work with appending unique index statements to the database instantiation SQL. (Obviously we'd need to mention this, since some people with pre-written databases without these indexes would not experience any difference between unique: false and unique: true or unique: 'name').
The question is though, do the supported database servers return parseable enough errors for that to be possible?
There's another problem; afterValidate and beforeSave will both fire before the unique validation error comes back. This is not what the user will expect.
I'm not sure about how parsable the errors would be, but I don't think we necessarily have to wrap them in anything special. Ideally the user shouldn't be running queries which violate the DB schema in the first place.
Maybe we can have both the validator and the unique property and allow them to fulfil different tasks. The validator can act as a stopgap between bad input into the user's application and the database queries, while the unique property can act as a database schema restriction. I think given the way the two would be used that it makes sense to do it that way, and the user would expect schema errors to be returned by the database engine post query.
It should then generate the following MySQL table creation query, using the model name and unique key if present, or the name of the field if unique = true to generate the index name.
CREATETABLEmodel (
id VARCHAR(255) NOT NULL,
first_name VARCHAR(255) NOT NULL,
last_name VARCHAR(255) NOT NULL,
email VARCHAR(255),
PRIMARY KEY(id),
UNIQUE INDEX IX_model_name (first_name, last_name),
UNIQUE INDEX IX_model_email (email)
);
We can then keep the enforce validator for unique and allow that to do pre-query validation on the fields if the user wishes.
I think the required option of a property, although it's validated in the database, it never gets there because there's code to check this. I think this is good if we can avoid the database (because the link can be a lot slower than an if statement). For consistency, we should remove that if and:
Add an automatic required validation to the property (user will then see an error he might expect, with detailed information);
Or rely on database (although not bad, I don't think this is the best option).
The unique option should then follow the same pattern.
I think the difference with the unique option is that it requires a query to the database to find any duplicate keys, which negates the efficiency bonus that we get with required. In the case of required we can avoid a query by implementing the logic in ORM whereas with unique we would be moving from a single insert query (with an error response) to two queries (one to determine whether there are any duplicate keys in the database and one to perform the insert/update) instead of the single update query which would be issued otherwise.
It also introduces the issue of handling NoSQL datastores, or databases which don't support whatever query we use to check for uniqueness - which then requires each database driver to define their own implementation of an isUnique function.
My vote would be to split database logic and application logic up into the properties and opts parameters for db.define. Thus, anything defined within properties will only affect how the database is generated and queried while opts.validations would allow application side validation of data. It makes it easy to comprehend where the validations will take place, and what error messages (and when) you can expect.
I agree with you that unique validator could be avoided by just trying to insert it. Perhaps we could just check what kind of error is returned for each driver and have a way for instance to know that. If we can do this, we might deprecate the unique validator since it might be inefficient on large databases. I have to check how to do this in NoSQL datastores.
Certainly is an option, maybe have a central error type wrapper within node-sql-query or ORM which translates, on a per driver basis, error codes into a number of standardized forms. Would allow us to easily interop for other features in the future, and allow the database engines to handle what they're best at while ORM acts as an interface layer (which it's best at). As for NoSQL, at the moment I've added a bit of code which disables the unique validator for NoSQL database engines, however from what I've gathered so far it would be extremely difficult to implement such a thing given the emphasis on horizontal scalability present in most of those engines (which attempts to do away with keys and relationships). I'm currently taking a course on MongoDB in Node.js and I'll see what (if any) of that I can apply to this project.
No worries, actually using this module in a project for one of my other courses - most of the functionality I end up adding is stuff I need there as well, so not really any extra effort :)
I believe the answer is no. Responses from sql servers don't enumerate all errors with the insert query; They return the first one whereas there should be two. In some cases, the error messages are very cryptic and as in the case of the mysql driver, are incomplete.
Wow, thanks for looking into that. I agree that the errors returned by the database certainly are lacking the details required to truly use them to generate validation messages for unique keys, but that takes us back to the issue of how to handle the validation in a performance friendly way - since in your demo case there would be 2 additional queries for each attempt to insert data (one for email and one for someId).
It does look like it may be possible to at least extract the first error from SQLite (assuming index refers to the index of the value in the query which caused the issue. Since we know the order that fields are used in ORM queries it should be possible to determine the field that caused the error and give some extra information.
varfield=Object.keys(Model.fields)[error.index];varvalue=Instance[field];returnnewError('Duplicate entry for unique field \''+field'\': \''+value.toString+'\'');
An alternative is to eagerly assume that the inserted data will pass validation, and if we get a validation error from the database we run the queries required to determine all the validation errors? That way we don't have the additional overhead for successful queries but are still able to get the information we need.
There is also the option of having validators which are executed only after a query has completed, so having unique: '...' would set the constraint on the database and the unique validator would (if unique: '...' was set on the property's column) only execute after the query, validating that no error occurred and generating meaningful information about the error if one did.
Person=db.define("person",{name: {type: 'text'},email: {type: 'text'},someId: {type: 'text',unique: true}},{validations: {// Executes pre-query, making a request to the DB to check for uniqueness (current behaviour)email: orm.unique(),// Executes post-query to check for a validation error, and if one is present - queries the DB for better infosomeId: orm.unique()}});
We should be able to check whether unique is set on a property by checking Model.fields[property].unique, from that we can define a property on the Validator (say, this.postQuery = true) and based on that handle validations.
Obviously I'm rather opposed to the idea of running additional queries against the database for every insert or update operation - for performance reasons - and in my case I'd be more than happy to settle for less information from a unique constraint error in exchange for better performance. That being said, there's definitely a place for a validator which gets that information, but I think it should be an opt-in alternative rather than the default (with warnings about the performance impact, especially with multiple unique keys).
Also keep in mind that mysql returns the errors differently in different versions.
Eg: duplicate entry '0' for key 1
And if we change the table to id: ['id1', 'id2'] and try insert duplicate primary key: ERROR 1062 (23000): Duplicate entry '5-6' for key 'PRIMARY'
and whilst postgres gives a relatively nice error, sqlite doesn't give one at all - which means you're gonna have to have some sort of lookup table, a whole bunch of regexs, and lots of custom logic to deal with all the different versions of all the various database servers.
I'm also unsure about the order in which hooks will be executed -
Wouldn't we have to trigger beforeSave & afterSave after the actual save operation has completed if it was successfull (but only for supported datastores)? What if the user wants to run a different query in a before filter before the data is saved? Do we revert to traditional validation behaviour if a before hook is detected?
Whilst I completely understand wanting to reduce the number of SQL queries, I'm not sure It's worth paying the price; significantly more complicated code.
Generally apps have frequent reads, and infrequent writes. And if I had a write-heavy table, I'd could manually optimize - by not adding the traditional 'slow' unique validator - and just handle the errors straight from the database.
I'm not sure if this is a problem that needs solving, especially given the increased complexity.
I'm not sure about how parsable the errors would be, but I don't think we necessarily have to wrap them in anything special. Ideally the user shouldn't be running queries which violate the DB schema in the first place.
Maybe we can have both the validator and the unique property and allow them to fulfil different tasks. The validator can act as a stopgap between bad input into the user's application and the database queries, while the unique property can act as a database schema restriction. I think given the way the two would be used that it makes sense to do it that way, and the user would expect schema errors to be returned by the database engine post query.
It should then generate the following MySQL table creation query, using the model name and unique key if present, or the name of the field if unique = true to generate the index name.
CREATETABLEmodel (
id VARCHAR(255) NOT NULL,
first_name VARCHAR(255) NOT NULL,
last_name VARCHAR(255) NOT NULL,
email VARCHAR(255),
PRIMARY KEY(id),
UNIQUE INDEX IX_model_name (first_name, last_name),
UNIQUE INDEX IX_model_email (email)
);
We can then keep the enforce validator for unique and allow that to do pre-query validation on the fields if the user wishes.
How does that sound to you guys?
good job.
index together: �set index same value ,like this: 'id_name_index';
unique together: set unique same value ,like this: 'id_name_unique';
Activity
notheotherben commentedon Aug 8, 2013
You can do this by specifying multiple primary keys, which will cause the database engine to treat their combination as a unique primary key.
dxg commentedon Aug 11, 2013
I've also just improved the unique validator to support scope. See the wiki.
We should also at some point add the following syntax:
dresende commentedon Aug 11, 2013
Thank you @dxg , that idea is exactly what I was thinking when I read this issue.
Fix unique validator (dresende#288)
notheotherben commentedon Aug 13, 2013
Surely it should be possible to use the suggested new syntax to generate something along the lines of
Thus ensuring the uniqueness on the database itself...
Disclaimer: I haven't actually checked the syntax there, just guessing at it from memory.
dxg commentedon Aug 13, 2013
Question is though, should adding those
unique: 'name_cat'options cause a unique validator to be automatically added to the model? Addingrequired: trueautomatically adds arequiredvalidator.notheotherben commentedon Aug 14, 2013
I think, if possible, we should avoid client side validators and rather rely on the database engine (since it will be a factor faster). When we add
required: truewe are also including that information in the database definition by appendingNOT NULLto any columns.I'd recommend that if the database is able to, we avoid adding the validator and instead work with appending unique index statements to the database instantiation SQL. (Obviously we'd need to mention this, since some people with pre-written databases without these indexes would not experience any difference between
unique: falseandunique: trueorunique: 'name').dxg commentedon Aug 14, 2013
The question is though, do the supported database servers return parseable enough errors for that to be possible?
There's another problem;
afterValidateandbeforeSavewill both fire before the unique validation error comes back. This is not what the user will expect.notheotherben commentedon Aug 14, 2013
I'm not sure about how parsable the errors would be, but I don't think we necessarily have to wrap them in anything special. Ideally the user shouldn't be running queries which violate the DB schema in the first place.
Maybe we can have both the validator and the
uniqueproperty and allow them to fulfil different tasks. The validator can act as a stopgap between bad input into the user's application and the database queries, while theuniqueproperty can act as a database schema restriction. I think given the way the two would be used that it makes sense to do it that way, and the user would expect schema errors to be returned by the database engine post query.Essentially we could have the following
It should then generate the following MySQL table creation query, using the model name and
uniquekey if present, or the name of the field ifunique = trueto generate the index name.We can then keep the enforce validator for
uniqueand allow that to do pre-query validation on the fields if the user wishes.How does that sound to you guys?
dxg commentedon Aug 14, 2013
So the idea is to stick with what we have, and add the
{ type: 'text', required: true, unique: 'name' }syntax.I'm happy with that.
dresende commentedon Aug 14, 2013
I think the required option of a property, although it's validated in the database, it never gets there because there's code to check this. I think this is good if we can avoid the database (because the link can be a lot slower than an
ifstatement). For consistency, we should remove thatifand:The unique option should then follow the same pattern.
notheotherben commentedon Aug 14, 2013
I think the difference with the
uniqueoption is that it requires a query to the database to find any duplicate keys, which negates the efficiency bonus that we get withrequired. In the case ofrequiredwe can avoid a query by implementing the logic in ORM whereas withuniquewe would be moving from a single insert query (with an error response) to two queries (one to determine whether there are any duplicate keys in the database and one to perform the insert/update) instead of the single update query which would be issued otherwise.It also introduces the issue of handling NoSQL datastores, or databases which don't support whatever query we use to check for uniqueness - which then requires each database driver to define their own implementation of an
isUniquefunction.My vote would be to split database logic and application logic up into the
propertiesandoptsparameters fordb.define. Thus, anything defined withinpropertieswill only affect how the database is generated and queried whileopts.validationswould allow application side validation of data. It makes it easy to comprehend where the validations will take place, and what error messages (and when) you can expect.dresende commentedon Aug 14, 2013
I agree with you that
uniquevalidator could be avoided by just trying to insert it. Perhaps we could just check what kind of error is returned for each driver and have a way for instance to know that. If we can do this, we might deprecate theuniquevalidator since it might be inefficient on large databases. I have to check how to do this in NoSQL datastores.notheotherben commentedon Aug 14, 2013
Certainly is an option, maybe have a central error type wrapper within node-sql-query or ORM which translates, on a per driver basis, error codes into a number of standardized forms. Would allow us to easily interop for other features in the future, and allow the database engines to handle what they're best at while ORM acts as an interface layer (which it's best at). As for NoSQL, at the moment I've added a bit of code which disables the
uniquevalidator for NoSQL database engines, however from what I've gathered so far it would be extremely difficult to implement such a thing given the emphasis on horizontal scalability present in most of those engines (which attempts to do away with keys and relationships). I'm currently taking a course on MongoDB in Node.js and I'll see what (if any) of that I can apply to this project.dresende commentedon Aug 14, 2013
Ok, thank you for your effort on this :)
notheotherben commentedon Aug 14, 2013
No worries, actually using this module in a project for one of my other courses - most of the functionality I end up adding is stuff I need there as well, so not really any extra effort :)
dxg commentedon Aug 14, 2013
That all sounds wonderful, however we need a reality check:
Now let's see if we have enough data to construct meaningful validation messages:
Postgres
Raw sql response:
Mysql
Raq sql response:
SQLite
I believe the answer is no. Responses from sql servers don't enumerate all errors with the insert query; They return the first one whereas there should be two. In some cases, the error messages are very cryptic and as in the case of the mysql driver, are incomplete.
notheotherben commentedon Aug 15, 2013
Wow, thanks for looking into that. I agree that the errors returned by the database certainly are lacking the details required to truly use them to generate validation messages for unique keys, but that takes us back to the issue of how to handle the validation in a performance friendly way - since in your demo case there would be 2 additional queries for each attempt to insert data (one for
emailand one forsomeId).It does look like it may be possible to at least extract the first error from SQLite (assuming
indexrefers to the index of the value in the query which caused the issue. Since we know the order that fields are used in ORM queries it should be possible to determine the field that caused the error and give some extra information.An alternative is to eagerly assume that the inserted data will pass validation, and if we get a validation error from the database we run the queries required to determine all the validation errors? That way we don't have the additional overhead for successful queries but are still able to get the information we need.
There is also the option of having validators which are executed only after a query has completed, so having
unique: '...'would set the constraint on the database and theuniquevalidator would (ifunique: '...'was set on the property's column) only execute after the query, validating that no error occurred and generating meaningful information about the error if one did.We should be able to check whether
uniqueis set on a property by checkingModel.fields[property].unique, from that we can define a property on the Validator (say,this.postQuery = true) and based on that handle validations.Obviously I'm rather opposed to the idea of running additional queries against the database for every insert or update operation - for performance reasons - and in my case I'd be more than happy to settle for less information from a
uniqueconstraint error in exchange for better performance. That being said, there's definitely a place for a validator which gets that information, but I think it should be an opt-in alternative rather than the default (with warnings about the performance impact, especially with multiple unique keys).dxg commentedon Aug 15, 2013
Also keep in mind that mysql returns the errors differently in different versions.
Eg:
duplicate entry '0' for key 1And if we change the table to
id: ['id1', 'id2']and try insert duplicate primary key:ERROR 1062 (23000): Duplicate entry '5-6' for key 'PRIMARY'and whilst postgres gives a relatively nice error, sqlite doesn't give one at all - which means you're gonna have to have some sort of lookup table, a whole bunch of regexs, and lots of custom logic to deal with all the different versions of all the various database servers.
I'm also unsure about the order in which hooks will be executed -
Wouldn't we have to trigger beforeSave & afterSave after the actual save operation has completed if it was successfull (but only for supported datastores)? What if the user wants to run a different query in a before filter before the data is saved? Do we revert to traditional validation behaviour if a before hook is detected?
Whilst I completely understand wanting to reduce the number of SQL queries, I'm not sure It's worth paying the price; significantly more complicated code.
Generally apps have frequent reads, and infrequent writes. And if I had a write-heavy table, I'd could manually optimize - by not adding the traditional 'slow' unique validator - and just handle the errors straight from the database.
I'm not sure if this is a problem that needs solving, especially given the increased complexity.
dresende commentedon Aug 19, 2013
I was thinking more about looking into error codes, not descriptions. Error 1062 (in
error.code) always meansDUP-ENTRY.http://dev.mysql.com/doc/refman/5.5/en/error-messages-server.html#error_er_dup_entry
anlebcoder commentedon Nov 7, 2018
good job.
index together: �set
indexsame value ,like this: 'id_name_index';unique together: set
uniquesame value ,like this: 'id_name_unique';