不要怂,就是干,撸起袖子干!

Commit 1350aa91 by Sascha Depold

Merge branch 'master' of github.com:sequelize/sequelize

2 parents 30ad8c19 c5fee254
...@@ -30,7 +30,7 @@ The Sequelize library provides easy access to MySQL, SQLite or PostgreSQL databa ...@@ -30,7 +30,7 @@ The Sequelize library provides easy access to MySQL, SQLite or PostgreSQL databa
- Associations - Associations
- Importing definitions from single files - Importing definitions from single files
## Documentation, Examples and Updates ## ## Documentation and Updates ##
You can find the documentation and announcements of updates on the [project's website](http://www.sequelizejs.com). You can find the documentation and announcements of updates on the [project's website](http://www.sequelizejs.com).
If you want to know about latest development and releases, follow me on [Twitter](http://twitter.com/sdepold). If you want to know about latest development and releases, follow me on [Twitter](http://twitter.com/sdepold).
...@@ -42,6 +42,11 @@ Also make sure to take a look at the examples in the repository. The website wil ...@@ -42,6 +42,11 @@ Also make sure to take a look at the examples in the repository. The website wil
- [Google Groups](https://groups.google.com/forum/#!forum/sequelize) - [Google Groups](https://groups.google.com/forum/#!forum/sequelize)
- [XING](https://www.xing.com/net/priec1b5cx/sequelize) (pretty much inactive, but you might want to name it on your profile) - [XING](https://www.xing.com/net/priec1b5cx/sequelize) (pretty much inactive, but you might want to name it on your profile)
## Running Examples
Instructions for running samples are located in the [example directory](https://github.com/sequelize/sequelize/tree/master/examples). Try these samples in a live sandbox environment:
<a href="https://runnable.com/sequelize" target="_blank"><img src="https://runnable.com/external/styles/assets/runnablebtn.png"></a>
## Roadmap ## Roadmap
A very basic roadmap. Chances aren't too bad, that not mentioned things are implemented as well. Don't panic :) A very basic roadmap. Chances aren't too bad, that not mentioned things are implemented as well. Don't panic :)
...@@ -70,8 +75,12 @@ A very basic roadmap. Chances aren't too bad, that not mentioned things are impl ...@@ -70,8 +75,12 @@ A very basic roadmap. Chances aren't too bad, that not mentioned things are impl
### 2.0.0 ### 2.0.0
- ~~save datetimes in UTC~~ - ~~save datetimes in UTC~~
- encapsulate attributes if a dao inside the attributes property + add getters and setters - encapsulate attributes if a dao inside the attributes property
- ~~add getters and setters for dao~~ Implemented in [#538](https://github.com/sequelize/sequelize/pull/538), thanks to iamjochem
- add proper error message everywhere - add proper error message everywhere
- refactor validate() output data structure, separating field-specific errors
from general model validator errors (i.e.
`{fields: {field1: ['field1error1']}, model: ['modelError1']}` or similar)
## Collaboration 2.0 ## ## Collaboration 2.0 ##
......
# v1.7.0 # # v1.7.0 #
- [DEPENDENCIES] Upgraded validator for IPv6 support. [#603](https://github.com/sequelize/sequelize/pull/603). thanks to durango - [DEPENDENCIES] Upgraded validator for IPv6 support. [#603](https://github.com/sequelize/sequelize/pull/603). thanks to durango
- [FEATURE] Validate a model before it gets saved. [#601](https://github.com/sequelize/sequelize/pull/601). thanks to durango
- [DEPENDENCIES] replaced underscore by lodash. [#954](https://github.com/sequelize/sequelize/pull/594). thanks to durango - [DEPENDENCIES] replaced underscore by lodash. [#954](https://github.com/sequelize/sequelize/pull/594). thanks to durango
- [BUG] Fix string escape with postgresql on raw SQL queries. [#586](https://github.com/sequelize/sequelize/pull/586). thanks to zanamixx - [BUG] Fix string escape with postgresql on raw SQL queries. [#586](https://github.com/sequelize/sequelize/pull/586). thanks to zanamixx
- [BUG] "order by" is now after "group by". [#585](https://github.com/sequelize/sequelize/pull/585). thanks to mekanics - [BUG] "order by" is now after "group by". [#585](https://github.com/sequelize/sequelize/pull/585). thanks to mekanics
- [BUG] Added decimal support for min/max. [#583](https://github.com/sequelize/sequelize/pull/583). thanks to durango - [BUG] Added decimal support for min/max. [#583](https://github.com/sequelize/sequelize/pull/583). thanks to durango
- [BUG] Null dates don't break SQLite anymore. [#572](https://github.com/sequelize/sequelize/pull/572). thanks to mweibel - [BUG] Null dates don't break SQLite anymore. [#572](https://github.com/sequelize/sequelize/pull/572). thanks to mweibel
- [BUG] Correctly handle booleans in MySQL. [#608](https://github.com/sequelize/sequelize/pull/608). Thanks to terraflubb - [BUG] Correctly handle booleans in MySQL. [#608](https://github.com/sequelize/sequelize/pull/608). Thanks to terraflubb
- [BUG] Fixed empty where conditions in MySQL. [#619](https://github.com/sequelize/sequelize/pull/619). Thanks to terraflubb
- [BUG] Allow overriding of default columns. [#635](https://github.com/sequelize/sequelize/pull/635). Thanks to sevastos
- [FEATURE] Validate a model before it gets saved. [#601](https://github.com/sequelize/sequelize/pull/601). thanks to durango
- [FEATURE] Schematics. [#564](https://github.com/sequelize/sequelize/pull/564). thanks to durango - [FEATURE] Schematics. [#564](https://github.com/sequelize/sequelize/pull/564). thanks to durango
- [FEATURE] Foreign key constraints. [#595](https://github.com/sequelize/sequelize/pull/595). thanks to optilude - [FEATURE] Foreign key constraints. [#595](https://github.com/sequelize/sequelize/pull/595). thanks to optilude
- [FEATURE] Support for bulk insert (`<DAOFactory>.bulkCreate()`, update (`<DAOFactory>.update()`) and delete (`<DAOFactory>.destroy()`) [#569](https://github.com/sequelize/sequelize/pull/569). thanks to optilude - [FEATURE] Support for bulk insert (`<DAOFactory>.bulkCreate()`, update (`<DAOFactory>.update()`) and delete (`<DAOFactory>.destroy()`) [#569](https://github.com/sequelize/sequelize/pull/569). thanks to optilude
- [FEATURE] Add an extra `queryOptions` parameter to `DAOFactory.find` and `DAOFactory.findAll`. This allows a user to specify `{ raw: true }`, meaning that the raw result should be returned, instead of built DAOs. Usefull for queries returning large datasets, see [#611](https://github.com/sequelize/sequelize/pull/611) janmeier - [FEATURE] Add an extra `queryOptions` parameter to `DAOFactory.find` and `DAOFactory.findAll`. This allows a user to specify `{ raw: true }`, meaning that the raw result should be returned, instead of built DAOs. Usefull for queries returning large datasets, see [#611](https://github.com/sequelize/sequelize/pull/611) janmeier
- [FEATURE] Added convenient data types. [#616](https://github.com/sequelize/sequelize/pull/616). Thanks to Costent - [FEATURE] Added convenient data types. [#616](https://github.com/sequelize/sequelize/pull/616). Thanks to Costent
- [FEATURE] Binary is more verbose now. [#612](https://github.com/sequelize/sequelize/pull/612). Thanks to terraflubb - [FEATURE] Binary is more verbose now. [#612](https://github.com/sequelize/sequelize/pull/612). Thanks to terraflubb
- [FEATURE] Promises/A support. [#626](https://github.com/sequelize/sequelize/pull/626). Thanks to kevinbeaty
- [FEATURE] Added Getters/Setters method for DAO. [#538](https://github.com/sequelize/sequelize/pull/538). Thanks to iamjochem
- [FEATURE] Added model wide validations. [#640](https://github.com/sequelize/sequelize/pull/640). Thanks to tremby
# v1.6.0 # # v1.6.0 #
- [DEPENDENCIES] upgrade mysql to alpha7. You *MUST* use this version or newer for DATETIMEs to work - [DEPENDENCIES] upgrade mysql to alpha7. You *MUST* use this version or newer for DATETIMEs to work
......
...@@ -5,11 +5,11 @@ ...@@ -5,11 +5,11 @@
First of all, Person is getting associated via many-to-many with other Person objects (e.g. Person.hasMany('brothers')). First of all, Person is getting associated via many-to-many with other Person objects (e.g. Person.hasMany('brothers')).
Afterwards a Person becomes associated with a 'father' and a mother using a one-to-one association created by hasOneAndBelongsTo. Afterwards a Person becomes associated with a 'father' and a mother using a one-to-one association created by hasOneAndBelongsTo.
The last association has the type many-to-one and is defined by the function hasManyAndBelongsTo. The last association has the type many-to-one and is defined by the function hasManyAndBelongsTo.
The rest of the example is about setting and getting the associated data. The rest of the example is about setting and getting the associated data.
*/ */
var Sequelize = require(__dirname + "/../../index") var Sequelize = require(__dirname + "/../../index")
, config = require(__dirname + "/../../test/config") , config = require(__dirname + "/../../spec/config/config")
, sequelize = new Sequelize(config.database, config.username, config.password, {logging: false}) , sequelize = new Sequelize(config.database, config.username, config.password, {logging: false})
, Person = sequelize.define('Person', { name: Sequelize.STRING }) , Person = sequelize.define('Person', { name: Sequelize.STRING })
, Pet = sequelize.define('Pet', { name: Sequelize.STRING }) , Pet = sequelize.define('Pet', { name: Sequelize.STRING })
...@@ -36,13 +36,13 @@ sequelize.sync({force:true}).on('success', function() { ...@@ -36,13 +36,13 @@ sequelize.sync({force:true}).on('success', function() {
.add(brother.save()) .add(brother.save())
.add(sister.save()) .add(sister.save())
.add(pet.save()) .add(pet.save())
chainer.run().on('success', function() { chainer.run().on('success', function() {
person.setMother(mother).on('success', function() { person.getMother().on('success', function(mom) { person.setMother(mother).on('success', function() { person.getMother().on('success', function(mom) {
console.log('my mom: ', mom.name) console.log('my mom: ', mom.name)
})}) })})
person.setFather(father).on('success', function() { person.getFather().on('success', function(dad) { person.setFather(father).on('success', function() { person.getFather().on('success', function(dad) {
console.log('my dad: ', dad.name) console.log('my dad: ', dad.name)
})}) })})
person.setBrothers([brother]).on('success', function() { person.getBrothers().on('success', function(brothers) { person.setBrothers([brother]).on('success', function() { person.getBrothers().on('success', function(brothers) {
console.log("my brothers: " + brothers.map(function(b) { return b.name })) console.log("my brothers: " + brothers.map(function(b) { return b.name }))
......
var Sequelize = require(__dirname + "/../../index") var Sequelize = require(__dirname + "/../../index")
, config = require(__dirname + "/../../test/config") , config = require(__dirname + "/../../spec/config/config")
, sequelize = new Sequelize(config.database, config.username, config.password, {logging: false}) , sequelize = new Sequelize(config.database, config.username, config.password, {logging: false})
var Person = sequelize.define('Person', { name: Sequelize.STRING }) var Person = sequelize.define('Person', { name: Sequelize.STRING })
...@@ -8,12 +8,12 @@ var Person = sequelize.define('Person', { name: Sequelize.STRING }) ...@@ -8,12 +8,12 @@ var Person = sequelize.define('Person', { name: Sequelize.STRING })
sequelize.sync({force: true}).on('success', function() { sequelize.sync({force: true}).on('success', function() {
var count = 10, var count = 10,
queries = [] queries = []
for(var i = 0; i < count; i++) for(var i = 0; i < count; i++)
chainer.add(Person.create({name: 'someone' + (i % 3)})) chainer.add(Person.create({name: 'someone' + (i % 3)}))
console.log("Begin to save " + count + " items!") console.log("Begin to save " + count + " items!")
chainer.run().on('success', function() { chainer.run().on('success', function() {
console.log("finished") console.log("finished")
Person.count().on('success', function(count) { Person.count().on('success', function(count) {
......
var Sequelize = require(__dirname + "/../../index") var Sequelize = require(__dirname + "/../../index")
, config = require(__dirname + "/../../test/config") , config = require(__dirname + "/../../spec/config/config")
, sequelize = new Sequelize(config.database, config.username, config.password, {logging: false}) , sequelize = new Sequelize(config.database, config.username, config.password, {logging: false})
var Person = sequelize.define('Person', var Person = sequelize.define('Person',
{ name: Sequelize.STRING, { name: Sequelize.STRING,
age : Sequelize.INTEGER age : Sequelize.INTEGER
...@@ -12,12 +12,12 @@ var Person = sequelize.define('Person', ...@@ -12,12 +12,12 @@ var Person = sequelize.define('Person',
sequelize.sync({force: true}).on('success', function() { sequelize.sync({force: true}).on('success', function() {
var count = 10, var count = 10,
queries = [] queries = []
for(var i = 0; i < count; i++) for(var i = 0; i < count; i++)
chainer.add(Person.create({name: 'someone' + (i % 3), age : i+5})) chainer.add(Person.create({name: 'someone' + (i % 3), age : i+5}))
console.log("Begin to save " + count + " items!") console.log("Begin to save " + count + " items!")
chainer.run().on('success', function() { chainer.run().on('success', function() {
console.log("finished") console.log("finished")
Person.max('age').on('success', function(max) { Person.max('age').on('success', function(max) {
......
var Sequelize = require(__dirname + "/../../index") var Sequelize = require(__dirname + "/../../index")
, config = require("../../test/config") , config = require(__dirname + "/../../spec/config/config")
, sequelize = new Sequelize(config.database, config.username, config.password, {logging: false, host: config.host}) , sequelize = new Sequelize(config.database, config.username, config.password, {logging: false, host: config.host})
, QueryChainer = Sequelize.Utils.QueryChainer , QueryChainer = Sequelize.Utils.QueryChainer
, sys = require("sys") , sys = require("sys")
...@@ -10,16 +10,16 @@ Person.sync({force: true}).on('success', function() { ...@@ -10,16 +10,16 @@ Person.sync({force: true}).on('success', function() {
var start = Date.now() var start = Date.now()
, count = 10000 , count = 10000
, done = 0 , done = 0
var createPerson = function() { var createPerson = function() {
Person.create({name: 'someone'}).on('success', function() { Person.create({name: 'someone'}).on('success', function() {
if(++done == count) { if(++done == count) {
var duration = (Date.now() - start) var duration = (Date.now() - start)
console.log("\nFinished creation of " + count + " people. Took: " + duration + "ms (avg: " + (duration/count) + "ms)") console.log("\nFinished creation of " + count + " people. Took: " + duration + "ms (avg: " + (duration/count) + "ms)")
start = Date.now() start = Date.now()
console.log("Will now read them from the database:") console.log("Will now read them from the database:")
Person.findAll().on('success', function(people) { Person.findAll().on('success', function(people) {
console.log("Reading " + people.length + " items took: " + (Date.now() - start) + "ms") console.log("Reading " + people.length + " items took: " + (Date.now() - start) + "ms")
}) })
...@@ -35,7 +35,7 @@ Person.sync({force: true}).on('success', function() { ...@@ -35,7 +35,7 @@ Person.sync({force: true}).on('success', function() {
for(var i = 0; i < count; i++) { for(var i = 0; i < count; i++) {
createPerson() createPerson()
} }
}).on('failure', function(err) { }).on('failure', function(err) {
console.log(err) console.log(err)
}) })
\ No newline at end of file
var fs = require("fs") /*
, Sequelize = require("sequelize") Title: Default values
, sequelize = new Sequelize('sequelize_test', 'root', null, {logging: false})
, Image = sequelize.define('Image', { data: Sequelize.TEXT })
Image.sync({force: true}).on('success', function() { This example demonstrates the use of default values for defined model fields. Instead of just specifying the datatype,
console.log("reading image") you have to pass a hash with a type and a default. You also might want to specify either an attribute can be null or not!
var image = fs.readFileSync(__dirname + '/source.png').toString("base64") */
console.log("done\n")
var Sequelize = require(__dirname + "/../../index")
console.log("creating database entry") , config = require(__dirname + "/../../spec/config/config")
Image.create({data: image}).on('success', function(img) { , sequelize = new Sequelize(config.database, config.username, config.password, {logging: false})
console.log("done\n")
var User = sequelize.define('User', {
console.log("writing file") name: { type: Sequelize.STRING, allowNull: false},
fs.writeFileSync(__dirname + '/target.png', img.data, "base64") isAdmin: { type: Sequelize.BOOLEAN, allowNull: false, defaultValue: false }
console.log("done\n") })
, user = User.build({ name: 'Someone' })
console.log("you might open the file ./target.png")
sequelize.sync({force: true}).on('success', function() {
user.save().on('success', function(user) {
console.log("user.isAdmin should be the default value (false): ", user.isAdmin)
user.updateAttributes({ isAdmin: true }).on('success', function(user) {
console.log("user.isAdmin was overwritten to true: " + user.isAdmin)
})
}) })
}).on('failure', function(err) {
console.log(err)
}) })
\ No newline at end of file
/* /*
Title: Defining class and instance methods Title: Defining class and instance methods
This example shows the usage of the classMethods and instanceMethods option for Models. This example shows the usage of the classMethods and instanceMethods option for Models.
*/ */
var Sequelize = require(__dirname + "/../../index") var Sequelize = require(__dirname + "/../../index")
, config = require(__dirname + "/../../test/config") , config = require(__dirname + "/../../spec/config/config")
, sequelize = new Sequelize(config.database, config.username, config.password, {logging: false}) , sequelize = new Sequelize(config.database, config.username, config.password, {logging: false})
// model definition // model definition
var Task = sequelize.define("Task", { var Task = sequelize.define("Task", {
name: Sequelize.STRING, name: Sequelize.STRING,
deadline: Sequelize.DATE, deadline: Sequelize.DATE,
...@@ -51,7 +51,7 @@ Task.sync({force: true}).on('success', function() { ...@@ -51,7 +51,7 @@ Task.sync({force: true}).on('success', function() {
console.log("should be false: " + task1.passedDeadline()) console.log("should be false: " + task1.passedDeadline())
console.log("should be true: " + task2.passedDeadline()) console.log("should be true: " + task2.passedDeadline())
console.log("should be 10: " + task1.importance) console.log("should be 10: " + task1.importance)
Task.setImportance(30, function() { Task.setImportance(30, function() {
Task.findAll().on('success', function(tasks) { Task.findAll().on('success', function(tasks) {
tasks.forEach(function(task) { tasks.forEach(function(task) {
......
var Sequelize = require(__dirname + "/../../index") var Sequelize = require(__dirname + "/../../index")
, config = require(__dirname + "/../../test/config") , config = require(__dirname + "/../../spec/config/config")
, sequelize = new Sequelize(config.database, config.username, config.password, { , sequelize = new Sequelize(config.database, config.username, config.password, {
// use other database server or port // use other database server or port
host: 'my.srv.tld', host: 'my.srv.tld',
port: 12345, port: 12345,
// disable logging // disable logging
logging: false logging: false
}) })
......
var Sequelize = require(__dirname + "/../../index") var Sequelize = require(__dirname + "/../../index")
, config = require(__dirname + "/../../test/config") , config = require(__dirname + "/../../spec/config/config")
, sequelize = new Sequelize(config.database, config.username, config.password, {logging: false}) , sequelize = new Sequelize(config.database, config.username, config.password, {logging: false})
, Project = sequelize.import(__dirname + "/Project") , Project = sequelize.import(__dirname + "/Project")
, Task = sequelize.import(__dirname + "/Task") , Task = sequelize.import(__dirname + "/Task")
Project.hasMany(Task) Project.hasMany(Task)
Task.belongsTo(Project) Task.belongsTo(Project)
sequelize.sync({force: true}).on('success', function() { sequelize.sync({force: true}).on('success', function() {
Project Project
.create({ name: 'Sequelize', description: 'A nice MySQL ORM for NodeJS' }) .create({ name: 'Sequelize', description: 'A nice MySQL ORM for NodeJS' })
......
...@@ -19,7 +19,15 @@ module.exports = (function() { ...@@ -19,7 +19,15 @@ module.exports = (function() {
whereCollection: null, whereCollection: null,
schema: null, schema: null,
schemaDelimiter: '' schemaDelimiter: ''
}, options || {}) }, options || {})
// error check options
Utils._.each(options.validate, function(validator, validatorType) {
if (Utils._.contains(Utils._.keys(attributes), validatorType))
throw new Error("A model validator function must not have the same name as a field. Model: " + name + ", field/validation name: " + validatorType)
if (!Utils._.isFunction(validator))
throw new Error("Members of the validate option must be functions. Model: " + name + ", error with validate member " + validatorType)
})
this.name = name this.name = name
if (!this.options.tableName) { if (!this.options.tableName) {
...@@ -30,9 +38,6 @@ module.exports = (function() { ...@@ -30,9 +38,6 @@ module.exports = (function() {
this.rawAttributes = attributes this.rawAttributes = attributes
this.daoFactoryManager = null // defined in init function this.daoFactoryManager = null // defined in init function
this.associations = {} this.associations = {}
// extract validation
this.validate = this.options.validate || {}
} }
Object.defineProperty(DAOFactory.prototype, 'attributes', { Object.defineProperty(DAOFactory.prototype, 'attributes', {
...@@ -75,11 +80,33 @@ module.exports = (function() { ...@@ -75,11 +80,33 @@ module.exports = (function() {
Util.inherits(this.DAO, DAO); Util.inherits(this.DAO, DAO);
this.DAO.prototype.rawAttributes = this.rawAttributes; this.DAO.prototype.rawAttributes = this.rawAttributes;
if (this.options.instanceMethods) { if (this.options.instanceMethods) {
Utils._.each(this.options.instanceMethods, function(fct, name) { Utils._.each(this.options.instanceMethods, function(fct, name) {
self.DAO.prototype[name] = fct self.DAO.prototype[name] = fct
}) })
} }
Utils._.each(['Get', 'Set'], function(type) {
var prop = type.toLowerCase(),
opt = prop + 'terMethods',
meth = '__define' + type + 'ter__',
funcs = Utils._.isObject(self.options[opt]) ? self.options[opt] : {}
;
Utils._.each(self.rawAttributes, function(attr, name) {
if (attr.hasOwnProperty(prop))
funcs[name] = attr[prop]
});
Utils._.each(funcs, function(fct, name) {
if (!Utils._.isFunction(fct))
throw new Error(type + 'ter for "' + name + '" is not a function.')
self.DAO.prototype[meth](name, fct);
})
})
this.DAO.prototype.attributes = Object.keys(this.DAO.prototype.rawAttributes); this.DAO.prototype.attributes = Object.keys(this.DAO.prototype.rawAttributes);
this.DAO.prototype.booleanValues = []; this.DAO.prototype.booleanValues = [];
...@@ -481,7 +508,9 @@ module.exports = (function() { ...@@ -481,7 +508,9 @@ module.exports = (function() {
} }
Utils._.each(defaultAttributes, function(value, attr) { Utils._.each(defaultAttributes, function(value, attr) {
self.rawAttributes[attr] = value if (Utils._.isUndefined(self.rawAttributes[attr])) {
self.rawAttributes[attr] = value
}
}) })
} }
......
...@@ -7,28 +7,15 @@ module.exports = (function() { ...@@ -7,28 +7,15 @@ module.exports = (function() {
var DAO = function(values, options, isNewRecord) { var DAO = function(values, options, isNewRecord) {
var self = this var self = this
this.dataValues = {}
this.__options = options this.__options = options
this.hasPrimaryKeys = options.hasPrimaryKeys this.hasPrimaryKeys = options.hasPrimaryKeys
this.selectedValues = values this.selectedValues = values
this.__eagerlyLoadedAssociations = [] this.__eagerlyLoadedAssociations = []
initAttributes.call(this, values, isNewRecord) initAttributes.call(this, values, isNewRecord)
if (this.hasDefaultValues) {
Utils._.each(this.defaultValues, function (value, name) {
if (typeof self[name] === 'undefined') {
self.addAttribute(name, value());
}
})
}
if (this.booleanValues.length) {
this.booleanValues.forEach(function (name) {
//transform integer 0,1 into boolean
self[name] = !!self[name];
});
}
} }
Utils._.extend(DAO.prototype, Mixin.prototype) Utils._.extend(DAO.prototype, Mixin.prototype)
Object.defineProperty(DAO.prototype, 'sequelize', { Object.defineProperty(DAO.prototype, 'sequelize', {
...@@ -42,7 +29,7 @@ module.exports = (function() { ...@@ -42,7 +29,7 @@ module.exports = (function() {
Object.defineProperty(DAO.prototype, 'isDeleted', { Object.defineProperty(DAO.prototype, 'isDeleted', {
get: function() { get: function() {
var result = this.__options.timestamps && this.__options.paranoid var result = this.__options.timestamps && this.__options.paranoid
result = result && this[this.__options.underscored ? 'deleted_at' : 'deletedAt'] !== null result = result && this.dataValues[this.__options.underscored ? 'deleted_at' : 'deletedAt'] !== null
return result return result
} }
...@@ -54,7 +41,10 @@ module.exports = (function() { ...@@ -54,7 +41,10 @@ module.exports = (function() {
, self = this , self = this
this.attributes.concat(this.__eagerlyLoadedAssociations).forEach(function(attr) { this.attributes.concat(this.__eagerlyLoadedAssociations).forEach(function(attr) {
result[attr] = self[attr] result[attr] = self.dataValues.hasOwnProperty(attr)
? self.dataValues[attr]
: self[attr]
;
}) })
return result return result
...@@ -67,7 +57,7 @@ module.exports = (function() { ...@@ -67,7 +57,7 @@ module.exports = (function() {
, self = this , self = this
Utils._.each(this.__factory.primaryKeys, function(_, attr) { Utils._.each(this.__factory.primaryKeys, function(_, attr) {
result[attr] = self[attr] result[attr] = self.dataValues[attr]
}) })
return result return result
...@@ -85,13 +75,21 @@ module.exports = (function() { ...@@ -85,13 +75,21 @@ module.exports = (function() {
} }
primaryKeys.forEach(function(identifier) { primaryKeys.forEach(function(identifier) {
result[identifier] = self[identifier] result[identifier] = self.dataValues[identifier]
}) })
return result return result
} }
}) })
DAO.prototype.getDataValue = function(name) {
return this.dataValues && this.dataValues.hasOwnProperty(name) ? this.dataValues[name] : this[name]
}
DAO.prototype.setDataValue = function(name, value) {
this.dataValues[name] = value
}
// if an array with field names is passed to save() // if an array with field names is passed to save()
// only those fields will be updated // only those fields will be updated
DAO.prototype.save = function(fields) { DAO.prototype.save = function(fields) {
...@@ -111,9 +109,11 @@ module.exports = (function() { ...@@ -111,9 +109,11 @@ module.exports = (function() {
} }
} }
var tmpVals = self.values
fields.forEach(function(field) { fields.forEach(function(field) {
if (self.values[field] !== undefined) { if (tmpVals[field] !== undefined) {
values[field] = self.values[field] values[field] = tmpVals[field]
} }
}) })
} }
...@@ -146,8 +146,8 @@ module.exports = (function() { ...@@ -146,8 +146,8 @@ module.exports = (function() {
} }
} }
if (this.__options.timestamps && this.hasOwnProperty(updatedAtAttr)) { if (this.__options.timestamps && this.dataValues.hasOwnProperty(updatedAtAttr)) {
this[updatedAtAttr] = values[updatedAtAttr] = Utils.now() this.dataValues[updatedAtAttr] = values[updatedAtAttr] = Utils.now()
} }
var errors = this.validate() var errors = this.validate()
...@@ -269,6 +269,15 @@ module.exports = (function() { ...@@ -269,6 +269,15 @@ module.exports = (function() {
} // if field has validator set } // if field has validator set
}) // for each field }) // for each field
// for each model validator for this DAO
Utils._.each(self.__options.validate, function(validator, validatorType) {
try {
validator.apply(self)
} catch (err) {
failures[validatorType] = [err.message] // TODO: data structure needs to change for 2.0
}
})
return (Utils._.isEmpty(failures) ? null : failures) return (Utils._.isEmpty(failures) ? null : failures)
} }
...@@ -301,7 +310,7 @@ module.exports = (function() { ...@@ -301,7 +310,7 @@ module.exports = (function() {
DAO.prototype.destroy = function() { DAO.prototype.destroy = function() {
if (this.__options.timestamps && this.__options.paranoid) { if (this.__options.timestamps && this.__options.paranoid) {
var attr = this.__options.underscored ? 'deleted_at' : 'deletedAt' var attr = this.__options.underscored ? 'deleted_at' : 'deletedAt'
this[attr] = new Date() this.dataValues[attr] = new Date()
return this.save() return this.save()
} else { } else {
var identifier = this.__options.hasPrimaryKeys ? this.primaryKeyValues : this.id var identifier = this.__options.hasPrimaryKeys ? this.primaryKeyValues : this.id
...@@ -362,7 +371,37 @@ module.exports = (function() { ...@@ -362,7 +371,37 @@ module.exports = (function() {
} }
DAO.prototype.addAttribute = function(attribute, value) { DAO.prototype.addAttribute = function(attribute, value) {
this[attribute] = value if (typeof this.dataValues[attribute] !== 'undefined')
return;
if (this.booleanValues.length && this.booleanValues.indexOf(attribute) !== -1) // transform integer 0,1 into boolean
value = !!value;
var has = (function(o) {
var predef = Object.getOwnPropertyDescriptor(o, attribute);
if (predef && predef.hasOwnProperty('value'))
return true; // true here means 'this property exist as a simple value property, do not place setters or getters at all'
return {
get: (predef && predef.hasOwnProperty('get') ? predef.get : null) || o.__lookupGetter__(attribute),
set: (predef && predef.hasOwnProperty('set') ? predef.set : null) || o.__lookupSetter__(attribute)
};
})(this);
// @ node-v0.8.19:
// calling __defineGetter__ destroys any previously defined setters for the attribute in
// question *if* that property setter was defined on the object's prototype (which is what
// we do in dao-factory) ... therefore we need to [re]define both the setter and getter
// here with either the function that already existed OR the default/automatic definition
//
// (the same is true for __defineSetter and 'prototype' getters)
if (has !== true) {
this.__defineGetter__(attribute, has.get || function() { return this.dataValues[attribute]; });
this.__defineSetter__(attribute, has.set || function(v) { this.dataValues[attribute] = v; });
}
this[attribute] = value;
} }
DAO.prototype.setValidators = function(attribute, validators) { DAO.prototype.setValidators = function(attribute, validators) {
...@@ -376,8 +415,13 @@ module.exports = (function() { ...@@ -376,8 +415,13 @@ module.exports = (function() {
// private // private
var initAttributes = function(values, isNewRecord) { var initAttributes = function(values, isNewRecord) {
// set id to null if not passed as value, a newly created dao has no id
var defaults = this.hasPrimaryKeys ? {} : { id: null },
attrs = {},
key;
// add all passed values to the dao and store the attribute names in this.attributes // add all passed values to the dao and store the attribute names in this.attributes
for (var key in values) { for (key in values) {
if (values.hasOwnProperty(key)) { if (values.hasOwnProperty(key)) {
if (typeof values[key] === "string" && !!this.__factory && !!this.__factory.rawAttributes[key] && !!this.__factory.rawAttributes[key].type && !!this.__factory.rawAttributes[key].type.type && this.__factory.rawAttributes[key].type.type === DataTypes.HSTORE.type) { if (typeof values[key] === "string" && !!this.__factory && !!this.__factory.rawAttributes[key] && !!this.__factory.rawAttributes[key].type && !!this.__factory.rawAttributes[key].type.type && this.__factory.rawAttributes[key].type.type === DataTypes.HSTORE.type) {
values[key] = this.QueryInterface.QueryGenerator.toHstore(values[key]) values[key] = this.QueryInterface.QueryGenerator.toHstore(values[key])
...@@ -387,10 +431,6 @@ module.exports = (function() { ...@@ -387,10 +431,6 @@ module.exports = (function() {
} }
} }
// set id to null if not passed as value
// a newly created dao has no id
var defaults = this.hasPrimaryKeys ? {} : { id: null }
if (this.__options.timestamps && isNewRecord) { if (this.__options.timestamps && isNewRecord) {
defaults[this.__options.underscored ? 'created_at' : 'createdAt'] = Utils.now() defaults[this.__options.underscored ? 'created_at' : 'createdAt'] = Utils.now()
defaults[this.__options.underscored ? 'updated_at' : 'updatedAt'] = Utils.now() defaults[this.__options.underscored ? 'updated_at' : 'updatedAt'] = Utils.now()
...@@ -398,17 +438,38 @@ module.exports = (function() { ...@@ -398,17 +438,38 @@ module.exports = (function() {
if (this.__options.paranoid) { if (this.__options.paranoid) {
defaults[this.__options.underscored ? 'deleted_at' : 'deletedAt'] = null defaults[this.__options.underscored ? 'deleted_at' : 'deletedAt'] = null
} }
if (this.hasDefaultValues) {
Utils._.each(this.defaultValues, function(valueFn, key) {
if (!defaults.hasOwnProperty(key))
defaults[key] = valueFn()
})
}
} }
if (Utils._.size(defaults)) { if (Utils._.size(defaults)) {
for (var attr in defaults) { for (key in defaults) {
var value = defaults[attr] attrs[key] = Utils.toDefaultValue(defaults[key])
}
}
if (!this.hasOwnProperty(attr)) { Utils._.each(this.attributes, function(key) {
this.addAttribute(attr, Utils.toDefaultValue(value)) if (!attrs.hasOwnProperty(key)) {
attrs[key] = undefined
}
})
if (values) {
for (key in values) {
if (values.hasOwnProperty(key)) {
attrs[key] = values[key]
} }
} }
} }
for (key in attrs) {
this.addAttribute(key, attrs[key])
}
} }
return DAO return DAO
......
...@@ -19,7 +19,9 @@ module.exports = (function() { ...@@ -19,7 +19,9 @@ module.exports = (function() {
this.poolCfg = Utils._.defaults(this.config.pool, { this.poolCfg = Utils._.defaults(this.config.pool, {
maxConnections: 10, maxConnections: 10,
minConnections: 0, minConnections: 0,
maxIdleTime: 1000 maxIdleTime: 1000,
handleDisconnects: false,
validate: validateConnection
}); });
this.pendingQueries = 0; this.pendingQueries = 0;
this.useReplicaton = !!config.replication; this.useReplicaton = !!config.replication;
...@@ -83,6 +85,7 @@ module.exports = (function() { ...@@ -83,6 +85,7 @@ module.exports = (function() {
destroy: function(client) { destroy: function(client) {
disconnect.call(self, client) disconnect.call(self, client)
}, },
validate: self.poolCfg.validate,
max: self.poolCfg.maxConnections, max: self.poolCfg.maxConnections,
min: self.poolCfg.minConnections, min: self.poolCfg.minConnections,
idleTimeoutMillis: self.poolCfg.maxIdleTime idleTimeoutMillis: self.poolCfg.maxIdleTime
...@@ -98,6 +101,7 @@ module.exports = (function() { ...@@ -98,6 +101,7 @@ module.exports = (function() {
destroy: function(client) { destroy: function(client) {
disconnect.call(self, client) disconnect.call(self, client)
}, },
validate: self.poolCfg.validate,
max: self.poolCfg.maxConnections, max: self.poolCfg.maxConnections,
min: self.poolCfg.minConnections, min: self.poolCfg.minConnections,
idleTimeoutMillis: self.poolCfg.maxIdleTime idleTimeoutMillis: self.poolCfg.maxIdleTime
...@@ -115,6 +119,7 @@ module.exports = (function() { ...@@ -115,6 +119,7 @@ module.exports = (function() {
}, },
max: self.poolCfg.maxConnections, max: self.poolCfg.maxConnections,
min: self.poolCfg.minConnections, min: self.poolCfg.minConnections,
validate: self.poolCfg.validate,
idleTimeoutMillis: self.poolCfg.maxIdleTime idleTimeoutMillis: self.poolCfg.maxIdleTime
}) })
} }
...@@ -247,10 +252,25 @@ module.exports = (function() { ...@@ -247,10 +252,25 @@ module.exports = (function() {
connection.query("SET time_zone = '+0:00'"); connection.query("SET time_zone = '+0:00'");
// client.setMaxListeners(self.maxConcurrentQueries) // client.setMaxListeners(self.maxConcurrentQueries)
this.isConnecting = false this.isConnecting = false
if (config.pool.handleDisconnects) {
handleDisconnect(this.pool, connection)
}
done(null, connection) done(null, connection)
} }
var handleDisconnect = function(pool, client) {
client.on('error', function(err) {
if (err.code !== 'PROTOCOL_CONNECTION_LOST') {
throw err
}
pool.destroy(client)
})
}
var validateConnection = function(client) {
return client && client.state != 'disconnected'
}
var enqueue = function(queueItem, options) { var enqueue = function(queueItem, options) {
options = options || {} options = options || {}
if (this.activeQueue.length < this.maxConcurrentQueries) { if (this.activeQueue.length < this.maxConcurrentQueries) {
...@@ -336,4 +356,4 @@ module.exports = (function() { ...@@ -336,4 +356,4 @@ module.exports = (function() {
} }
return ConnectorManager return ConnectorManager
})() })()
\ No newline at end of file
...@@ -186,7 +186,7 @@ module.exports = (function() { ...@@ -186,7 +186,7 @@ module.exports = (function() {
options.attributes = options.attributes || '*' options.attributes = options.attributes || '*'
if (options.include) { if (options.include) {
var optAttributes = [options.table + '.*'] var optAttributes = options.attributes === '*' ? [options.table + '.*'] : [options.attributes]
options.include.forEach(function(include) { options.include.forEach(function(include) {
var attributes = Object.keys(include.daoFactory.attributes).map(function(attr) { var attributes = Object.keys(include.daoFactory.attributes).map(function(attr) {
......
...@@ -193,7 +193,7 @@ module.exports = (function() { ...@@ -193,7 +193,7 @@ module.exports = (function() {
for (var attributeName in attributes) { for (var attributeName in attributes) {
attrString.push(Utils._.template('<%= before %> TO <%= after %>')({ attrString.push(Utils._.template('<%= before %> TO <%= after %>')({
before: QueryGenerator.addQuotes(attrBefore), before: QueryGenerator.addQuotes(attrBefore),
after: QueryGenerator.addQuotes(attributeName), after: QueryGenerator.addQuotes(attributeName)
})) }))
} }
...@@ -233,7 +233,7 @@ module.exports = (function() { ...@@ -233,7 +233,7 @@ module.exports = (function() {
options.attributes = options.attributes || '*' options.attributes = options.attributes || '*'
if (options.include) { if (options.include) {
var optAttributes = [options.table + '.*'] var optAttributes = options.attributes === '*' ? [options.table + '.*'] : [options.attributes]
options.include.forEach(function(include) { options.include.forEach(function(include) {
var attributes = Object.keys(include.daoFactory.attributes).map(function(attr) { var attributes = Object.keys(include.daoFactory.attributes).map(function(attr) {
...@@ -259,7 +259,7 @@ module.exports = (function() { ...@@ -259,7 +259,7 @@ module.exports = (function() {
} }
if(options.hasOwnProperty('where')) { if(options.hasOwnProperty('where')) {
options.where = QueryGenerator.getWhereConditions(options.where) options.where = QueryGenerator.getWhereConditions(options.where, tableName)
query += " WHERE <%= where %>" query += " WHERE <%= where %>"
} }
...@@ -474,14 +474,16 @@ module.exports = (function() { ...@@ -474,14 +474,16 @@ module.exports = (function() {
}) })
}, },
getWhereConditions: function(smth) { getWhereConditions: function(smth, tableName) {
var result = null var result = null
if (Utils.isHash(smth)) { if (Utils.isHash(smth)) {
smth = Utils.prependTableNameToHash(tableName, smth)
result = QueryGenerator.hashToWhereConditions(smth) result = QueryGenerator.hashToWhereConditions(smth)
} }
else if (typeof smth === "number") { else if (typeof smth === "number") {
result = '\"id\"' + "=" + QueryGenerator.pgEscape(smth) smth = Utils.prependTableNameToHash(tableName, { id: smth })
result = QueryGenerator.hashToWhereConditions(smth)
} }
else if (typeof smth === "string") { else if (typeof smth === "string") {
result = smth result = smth
...@@ -724,9 +726,15 @@ module.exports = (function() { ...@@ -724,9 +726,15 @@ module.exports = (function() {
} }
if (Utils._.includes(dataType, 'SERIAL')) { if (Utils._.includes(dataType, 'SERIAL')) {
dataType = dataType.replace(/INTEGER/, '') if (Utils._.includes(dataType, 'BIGINT')) {
dataType = dataType.replace(/SERIAL/, 'BIGSERIAL')
dataType = dataType.replace(/BIGINT/, '')
tables[tableName][attr] = 'bigserial'
} else {
dataType = dataType.replace(/INTEGER/, '')
tables[tableName][attr] = 'serial'
}
dataType = dataType.replace(/NOT NULL/, '') dataType = dataType.replace(/NOT NULL/, '')
tables[tableName][attr] = 'serial'
} }
if (dataType.match(/^ENUM\(/)) { if (dataType.match(/^ENUM\(/)) {
......
...@@ -24,8 +24,17 @@ module.exports = (function() { ...@@ -24,8 +24,17 @@ module.exports = (function() {
var QueryGenerator = { var QueryGenerator = {
options: {}, options: {},
addQuotes: function(s, quoteChar) { removeQuotes: function (s, quoteChar) {
return Utils.addTicks(s, quoteChar) quoteChar = quoteChar || '`'
return s.replace(new RegExp(quoteChar, 'g'), '')
},
addQuotes: function (s, quoteChar) {
quoteChar = quoteChar || '`'
return QueryGenerator.removeQuotes(s, quoteChar)
.split('.')
.map(function(e) { return quoteChar + String(e) + quoteChar })
.join('.')
}, },
addSchema: function(opts) { addSchema: function(opts) {
...@@ -76,6 +85,10 @@ module.exports = (function() { ...@@ -76,6 +85,10 @@ module.exports = (function() {
if (attributes.hasOwnProperty(attr)) { if (attributes.hasOwnProperty(attr)) {
var dataType = attributes[attr] var dataType = attributes[attr]
if (Utils._.includes(dataType, 'AUTOINCREMENT')) {
dataType = dataType.replace(/BIGINT/, 'INTEGER')
}
if (Utils._.includes(dataType, 'PRIMARY KEY') && needsMultiplePrimaryKeys) { if (Utils._.includes(dataType, 'PRIMARY KEY') && needsMultiplePrimaryKeys) {
primaryKeys.push(attr) primaryKeys.push(attr)
attrStr.push(Utils.addTicks(attr) + " " + dataType.replace(/PRIMARY KEY/, 'NOT NULL')) attrStr.push(Utils.addTicks(attr) + " " + dataType.replace(/PRIMARY KEY/, 'NOT NULL'))
...@@ -145,6 +158,74 @@ module.exports = (function() { ...@@ -145,6 +158,74 @@ module.exports = (function() {
return Utils._.template(query)(replacements) return Utils._.template(query)(replacements)
}, },
selectQuery: function(tableName, options) {
var table = null,
joinQuery = ""
options = options || {}
options.table = table = Array.isArray(tableName) ? tableName.map(function(tbl){ return QueryGenerator.addQuotes(tbl) }).join(", ") : QueryGenerator.addQuotes(tableName)
options.attributes = options.attributes && options.attributes.map(function(attr){
if(Array.isArray(attr) && attr.length == 2) {
return [attr[0], QueryGenerator.addQuotes(attr[1])].join(' as ')
} else {
return attr.indexOf(Utils.TICK_CHAR) < 0 ? QueryGenerator.addQuotes(attr) : attr
}
}).join(", ")
options.attributes = options.attributes || '*'
if (options.include) {
var optAttributes = options.attributes === '*' ? [options.table + '.*'] : [options.attributes]
options.include.forEach(function(include) {
var attributes = Object.keys(include.daoFactory.attributes).map(function(attr) {
return "`" + include.as + "`.`" + attr + "` AS `" + include.as + "." + attr + "`"
})
optAttributes = optAttributes.concat(attributes)
var table = include.daoFactory.tableName
var as = include.as
var tableLeft = ((include.association.associationType === 'BelongsTo') ? include.as : tableName)
var attrLeft = 'id'
var tableRight = ((include.association.associationType === 'BelongsTo') ? tableName : include.as)
var attrRight = include.association.identifier
joinQuery += " LEFT OUTER JOIN `" + table + "` AS `" + as + "` ON `" + tableLeft + "`.`" + attrLeft + "` = `" + tableRight + "`.`" + attrRight + "`"
})
options.attributes = optAttributes.join(', ')
}
var query = "SELECT " + options.attributes + " FROM " + options.table
query += joinQuery
if (options.hasOwnProperty('where')) {
options.where = this.getWhereConditions(options.where, tableName)
query += " WHERE " + options.where
}
if (options.group) {
options.group = Array.isArray(options.group) ? options.group.map(function(grp){return QueryGenerator.addQuotes(grp)}).join(', ') : QueryGenerator.addQuotes(options.group)
query += " GROUP BY " + options.group
}
if (options.order) {
query += " ORDER BY " + options.order
}
if (options.limit && !(options.include && (options.limit === 1))) {
if (options.offset) {
query += " LIMIT " + options.offset + ", " + options.limit
} else {
query += " LIMIT " + options.limit
}
}
query += ";"
return query
},
updateQuery: function(tableName, attrValueHash, where) { updateQuery: function(tableName, attrValueHash, where) {
attrValueHash = Utils.removeNullValuesFromHash(attrValueHash, this.options.omitNull) attrValueHash = Utils.removeNullValuesFromHash(attrValueHash, this.options.omitNull)
......
var util = require("util") var util = require("util")
, EventEmitter = require("events").EventEmitter , EventEmitter = require("events").EventEmitter
, Promise = require("promise")
, proxyEventKeys = ['success', 'error', 'sql'] , proxyEventKeys = ['success', 'error', 'sql']
var bindToProcess = function(fct) {
if (fct) {
if(process.domain) {
return process.domain.bind(fct);
}
}
return fct;
};
module.exports = (function() { module.exports = (function() {
var CustomEventEmitter = function(fct) { var CustomEventEmitter = function(fct) {
this.fct = fct this.fct = bindToProcess(fct);
} }
util.inherits(CustomEventEmitter, EventEmitter) util.inherits(CustomEventEmitter, EventEmitter)
...@@ -14,14 +26,14 @@ module.exports = (function() { ...@@ -14,14 +26,14 @@ module.exports = (function() {
this.fct.call(this, this) this.fct.call(this, this)
} }
}.bind(this)) }.bind(this))
return this return this
} }
CustomEventEmitter.prototype.success = CustomEventEmitter.prototype.success =
CustomEventEmitter.prototype.ok = CustomEventEmitter.prototype.ok =
function(fct) { function(fct) {
this.on('success', fct) this.on('success', bindToProcess(fct))
return this return this
} }
...@@ -29,13 +41,14 @@ module.exports = (function() { ...@@ -29,13 +41,14 @@ module.exports = (function() {
CustomEventEmitter.prototype.fail = CustomEventEmitter.prototype.fail =
CustomEventEmitter.prototype.error = CustomEventEmitter.prototype.error =
function(fct) { function(fct) {
this.on('error', fct) this.on('error', bindToProcess(fct))
return this; return this;
} }
CustomEventEmitter.prototype.done = CustomEventEmitter.prototype.done =
CustomEventEmitter.prototype.complete = CustomEventEmitter.prototype.complete =
function(fct) { function(fct) {
fct = bindToProcess(fct);
this.on('error', function(err) { fct(err, null) }) this.on('error', function(err) { fct(err, null) })
.on('success', function(result) { fct(null, result) }) .on('success', function(result) { fct(null, result) })
return this return this
...@@ -49,6 +62,18 @@ module.exports = (function() { ...@@ -49,6 +62,18 @@ module.exports = (function() {
}.bind(this)) }.bind(this))
} }
CustomEventEmitter.prototype.then =
function (onFulfilled, onRejected) {
var self = this
onFulfilled = bindToProcess(onFulfilled)
onRejected = bindToProcess(onRejected)
return new Promise(function (resolve, reject) {
self.on('error', reject)
.on('success', resolve);
}).then(onFulfilled, onRejected)
}
return CustomEventEmitter; return CustomEventEmitter;
})() })()
...@@ -47,7 +47,8 @@ module.exports = (function() { ...@@ -47,7 +47,8 @@ module.exports = (function() {
var urlParts var urlParts
options = options || {} options = options || {}
if (arguments.length === 1) { if (arguments.length === 1 || (arguments.length === 2 && typeof username === 'object')) {
options = username || {}
urlParts = url.parse(arguments[0]) urlParts = url.parse(arguments[0])
database = urlParts.path.replace(/^\//, '') database = urlParts.path.replace(/^\//, '')
dialect = urlParts.protocol dialect = urlParts.protocol
......
...@@ -35,16 +35,17 @@ ...@@ -35,16 +35,17 @@
"validator": "1.1.1", "validator": "1.1.1",
"moment": "~1.7.0", "moment": "~1.7.0",
"commander": "~0.6.0", "commander": "~0.6.0",
"generic-pool": "1.0.9",
"dottie": "0.0.6-1", "dottie": "0.0.6-1",
"toposort-class": "0.1.4" "toposort-class": "0.1.4",
"generic-pool": "2.0.3",
"promise": "~3.0.0"
}, },
"devDependencies": { "devDependencies": {
"jasmine-node": "1.5.0", "jasmine-node": "1.5.0",
"sqlite3": "~2.1.5", "sqlite3": "~2.1.5",
"mysql": "~2.0.0-alpha7", "mysql": "~2.0.0-alpha7",
"pg": "~0.10.2", "pg": "~0.10.2",
"buster": "~0.6.0", "buster": "~0.6.3",
"watchr": "~2.2.0", "watchr": "~2.2.0",
"yuidocjs": "~0.3.36" "yuidocjs": "~0.3.36"
}, },
......
...@@ -121,16 +121,16 @@ describe('QueryGenerator', function() { ...@@ -121,16 +121,16 @@ describe('QueryGenerator', function() {
expectation: "SELECT \"id\", \"name\" FROM \"myTable\";" expectation: "SELECT \"id\", \"name\" FROM \"myTable\";"
}, { }, {
arguments: ['myTable', {where: {id: 2}}], arguments: ['myTable', {where: {id: 2}}],
expectation: "SELECT * FROM \"myTable\" WHERE \"id\"=2;" expectation: "SELECT * FROM \"myTable\" WHERE \"myTable\".\"id\"=2;"
}, { }, {
arguments: ['myTable', {where: {name: 'foo'}}], arguments: ['myTable', {where: {name: 'foo'}}],
expectation: "SELECT * FROM \"myTable\" WHERE \"name\"='foo';" expectation: "SELECT * FROM \"myTable\" WHERE \"myTable\".\"name\"='foo';"
}, { }, {
arguments: ['myTable', {where: {name: "foo';DROP TABLE myTable;"}}], arguments: ['myTable', {where: {name: "foo';DROP TABLE myTable;"}}],
expectation: "SELECT * FROM \"myTable\" WHERE \"name\"='foo'';DROP TABLE myTable;';" expectation: "SELECT * FROM \"myTable\" WHERE \"myTable\".\"name\"='foo'';DROP TABLE myTable;';"
}, { }, {
arguments: ['myTable', {where: 2}], arguments: ['myTable', {where: 2}],
expectation: "SELECT * FROM \"myTable\" WHERE \"id\"=2;" expectation: "SELECT * FROM \"myTable\" WHERE \"myTable\".\"id\"=2;"
}, { }, {
arguments: ['foo', { attributes: [['count(*)', 'count']] }], arguments: ['foo', { attributes: [['count(*)', 'count']] }],
expectation: 'SELECT count(*) as \"count\" FROM \"foo\";' expectation: 'SELECT count(*) as \"count\" FROM \"foo\";'
...@@ -164,7 +164,7 @@ describe('QueryGenerator', function() { ...@@ -164,7 +164,7 @@ describe('QueryGenerator', function() {
expectation: "SELECT * FROM \"mySchema\".\"myTable\";" expectation: "SELECT * FROM \"mySchema\".\"myTable\";"
}, { }, {
arguments: ['mySchema.myTable', {where: {name: "foo';DROP TABLE mySchema.myTable;"}}], arguments: ['mySchema.myTable', {where: {name: "foo';DROP TABLE mySchema.myTable;"}}],
expectation: "SELECT * FROM \"mySchema\".\"myTable\" WHERE \"name\"='foo'';DROP TABLE mySchema.myTable;';" expectation: "SELECT * FROM \"mySchema\".\"myTable\" WHERE \"mySchema\".\"myTable\".\"name\"='foo'';DROP TABLE mySchema.myTable;';"
} }
], ],
......
...@@ -70,6 +70,34 @@ describe(Helpers.getTestDialectTeaser("DAOFactory"), function() { ...@@ -70,6 +70,34 @@ describe(Helpers.getTestDialectTeaser("DAOFactory"), function() {
}) })
}.bind(this), 'Invalid DAO definition. Only one autoincrement field allowed.') }.bind(this), 'Invalid DAO definition. Only one autoincrement field allowed.')
}) })
it('throws an error if a custom model-wide validation is not a function', function() {
Helpers.assertException(function() {
this.sequelize.define('Foo', {
field: {
type: Sequelize.INTEGER
}
}, {
validate: {
notFunction: 33
}
})
}.bind(this), 'Members of the validate option must be functions. Model: Foo, error with validate member notFunction')
})
it('throws an error if a custom model-wide validation has the same name as a field', function() {
Helpers.assertException(function() {
this.sequelize.define('Foo', {
field: {
type: Sequelize.INTEGER
}
}, {
validate: {
field: function() {}
}
})
}.bind(this), 'A model validator function must not have the same name as a field. Model: Foo, field/validation name: field')
})
}) })
describe('build', function() { describe('build', function() {
...@@ -100,6 +128,82 @@ describe(Helpers.getTestDialectTeaser("DAOFactory"), function() { ...@@ -100,6 +128,82 @@ describe(Helpers.getTestDialectTeaser("DAOFactory"), function() {
var user = this.User.build({ username: 'John Wayne' }) var user = this.User.build({ username: 'John Wayne' })
expect(user.selectedValues).toEqual({ username: 'John Wayne' }) expect(user.selectedValues).toEqual({ username: 'John Wayne' })
}) })
it("attaches getter and setter methods from attribute definition", function() {
var Product = this.sequelize.define('ProductWithSettersAndGetters1', {
price: {
type: Sequelize.INTEGER,
get : function() {
return 'answer = ' + this.getDataValue('price');
},
set : function(v) {
return this.setDataValue('price', v + 42);
}
}
},{
});
expect(Product.build({price: 42}).price).toEqual('answer = 84');
var p = Product.build({price: 1});
expect(p.price).toEqual('answer = 43');
p.price = 0;
expect(p.price).toEqual('answer = 42'); // ah finally the right answer :-)
})
it("attaches getter and setter methods from options", function() {
var Product = this.sequelize.define('ProductWithSettersAndGetters2', {
priceInCents: {
type: Sequelize.INTEGER
}
},{
setterMethods: {
price: function(value) {
this.dataValues.priceInCents = value * 100;
}
},
getterMethods: {
price: function() {
return '$' + (this.getDataValue('priceInCents') / 100);
},
priceInCents: function() {
return this.dataValues.priceInCents;
}
}
});
expect(Product.build({price: 20}).priceInCents).toEqual(20 * 100);
expect(Product.build({priceInCents: 30 * 100}).price).toEqual('$' + 30);
})
it("attaches getter and setter methods from options only if not defined in attribute", function() {
var Product = this.sequelize.define('ProductWithSettersAndGetters3', {
price1: {
type: Sequelize.INTEGER,
set : function(v) { this.setDataValue('price1', v * 10); }
},
price2: {
type: Sequelize.INTEGER,
get : function(v) { return this.getDataValue('price2') * 10; }
}
},{
setterMethods: {
price1: function(v) { this.setDataValue('price1', v * 100); }
},
getterMethods: {
price2: function() { return '$' + this.getDataValue('price2'); }
}
});
var p = Product.build({ price1: 1, price2: 2 });
expect(p.price1).toEqual(10);
expect(p.price2).toEqual(20);
})
}) })
describe('findOrCreate', function () { describe('findOrCreate', function () {
...@@ -763,6 +867,37 @@ describe(Helpers.getTestDialectTeaser("DAOFactory"), function() { ...@@ -763,6 +867,37 @@ describe(Helpers.getTestDialectTeaser("DAOFactory"), function() {
}.bind(this)) }.bind(this))
}) })
it('returns the selected fields and all fields of the included table as instance.selectedValues', function(done) {
this.Mission = this.sequelize.define('Mission', {
title: {type: Sequelize.STRING, defaultValue: 'a mission!!'},
foo: {type: Sequelize.INTEGER, defaultValue: 2},
})
this.Mission.belongsTo(this.User)
this.User.hasMany(this.Mission)
this.sequelize.sync({ force: true }).complete(function() {
this.Mission.create()
.success(function(mission) {
this.User.create({
username: 'John DOE'
}).success(function(user) {
mission.setUser(user)
.success(function() {
this.User.find({
where: { username: 'John DOE' },
attributes: ['username'],
include: [this.Mission]
}).success(function(user) {
expect(user.selectedValues).toEqual({ username: 'John DOE' })
done()
})
}.bind(this))
}.bind(this))
}.bind(this))
}.bind(this))
})
it('always honors ZERO as primary key', function(_done) { it('always honors ZERO as primary key', function(_done) {
var permutations = [ var permutations = [
0, 0,
...@@ -1085,7 +1220,7 @@ describe(Helpers.getTestDialectTeaser("DAOFactory"), function() { ...@@ -1085,7 +1220,7 @@ describe(Helpers.getTestDialectTeaser("DAOFactory"), function() {
done(); done();
}.bind(this)) }.bind(this))
}) })
it("should return raw data when raw is true", function (done) { it("should return raw data when raw is true", function (done) {
this.User.find({ where: { username: 'barfooz'}}, { raw: true }).done(function (err, user) { this.User.find({ where: { username: 'barfooz'}}, { raw: true }).done(function (err, user) {
...@@ -1342,9 +1477,9 @@ describe(Helpers.getTestDialectTeaser("DAOFactory"), function() { ...@@ -1342,9 +1477,9 @@ describe(Helpers.getTestDialectTeaser("DAOFactory"), function() {
it("should return a DAO when queryOptions are not set", function (done) { it("should return a DAO when queryOptions are not set", function (done) {
this.User.findAll({ where: { username: 'barfooz'}}).done(function (err, users) { this.User.findAll({ where: { username: 'barfooz'}}).done(function (err, users) {
users.forEach(function (user) { users.forEach(function (user) {
expect(user).toHavePrototype(this.User.DAO.prototype) expect(user).toHavePrototype(this.User.DAO.prototype)
}, this) }, this)
done(); done();
}.bind(this)) }.bind(this))
...@@ -1353,17 +1488,17 @@ describe(Helpers.getTestDialectTeaser("DAOFactory"), function() { ...@@ -1353,17 +1488,17 @@ describe(Helpers.getTestDialectTeaser("DAOFactory"), function() {
it("should return a DAO when raw is false", function (done) { it("should return a DAO when raw is false", function (done) {
this.User.findAll({ where: { username: 'barfooz'}}, { raw: false }).done(function (err, users) { this.User.findAll({ where: { username: 'barfooz'}}, { raw: false }).done(function (err, users) {
users.forEach(function (user) { users.forEach(function (user) {
expect(user).toHavePrototype(this.User.DAO.prototype) expect(user).toHavePrototype(this.User.DAO.prototype)
}, this) }, this)
done(); done();
}.bind(this)) }.bind(this))
}) })
it("should return raw data when raw is true", function (done) { it("should return raw data when raw is true", function (done) {
this.User.findAll({ where: { username: 'barfooz'}}, { raw: true }).done(function (err, users) { this.User.findAll({ where: { username: 'barfooz'}}, { raw: true }).done(function (err, users) {
users.forEach(function (user) { users.forEach(function (user) {
expect(user).not.toHavePrototype(this.User.DAO.prototype) expect(user).not.toHavePrototype(this.User.DAO.prototype)
expect(users[0]).toBeObject() expect(users[0]).toBeObject()
}, this) }, this)
...@@ -1532,6 +1667,10 @@ describe(Helpers.getTestDialectTeaser("DAOFactory"), function() { ...@@ -1532,6 +1667,10 @@ describe(Helpers.getTestDialectTeaser("DAOFactory"), function() {
expect(self.UserSpecialSync.getTableName()).toEqual('"special"."UserSpecials"'); expect(self.UserSpecialSync.getTableName()).toEqual('"special"."UserSpecials"');
expect(UserSpecial.indexOf('INSERT INTO "special"."UserSpecials"')).toBeGreaterThan(-1) expect(UserSpecial.indexOf('INSERT INTO "special"."UserSpecials"')).toBeGreaterThan(-1)
expect(UserPublic.indexOf('INSERT INTO "UserPublics"')).toBeGreaterThan(-1) expect(UserPublic.indexOf('INSERT INTO "UserPublics"')).toBeGreaterThan(-1)
} else if (dialect === "sqlite") {
expect(self.UserSpecialSync.getTableName()).toEqual('`special`.`UserSpecials`');
expect(UserSpecial.indexOf('INSERT INTO `special.UserSpecials`')).toBeGreaterThan(-1)
expect(UserPublic.indexOf('INSERT INTO `UserPublics`')).toBeGreaterThan(-1)
} else { } else {
expect(self.UserSpecialSync.getTableName()).toEqual('`special.UserSpecials`'); expect(self.UserSpecialSync.getTableName()).toEqual('`special.UserSpecials`');
expect(UserSpecial.indexOf('INSERT INTO `special.UserSpecials`')).toBeGreaterThan(-1) expect(UserSpecial.indexOf('INSERT INTO `special.UserSpecials`')).toBeGreaterThan(-1)
......
...@@ -282,5 +282,58 @@ describe(Helpers.getTestDialectTeaser("DAO"), function() { ...@@ -282,5 +282,58 @@ describe(Helpers.getTestDialectTeaser("DAO"), function() {
var successfulUser = User.build({ name : "2" }) var successfulUser = User.build({ name : "2" })
expect(successfulUser.validate()).toBeNull() expect(successfulUser.validate()).toBeNull()
}) })
it('skips other validations if allowNull is true and the value is null', function() {
var User = this.sequelize.define('User' + Math.random(), {
age: {
type: Sequelize.INTEGER,
allowNull: true,
validate: {
min: { args: 0, msg: 'must be positive' }
}
}
})
var failingUser = User.build({ age: -1 })
, errors = failingUser.validate()
expect(errors).not.toBeNull(null)
expect(errors).toEqual({ age: ['must be positive'] })
var successfulUser1 = User.build({ age: null })
expect(successfulUser1.validate()).toBeNull()
var successfulUser2 = User.build({ age: 1 })
expect(successfulUser2.validate()).toBeNull()
})
it('validates a model with custom model-wide validation methods', function() {
var Foo = this.sequelize.define('Foo' + Math.random(), {
field1: {
type: Sequelize.INTEGER,
allowNull: true
},
field2: {
type: Sequelize.INTEGER,
allowNull: true
}
}, {
validate: {
xnor: function() {
if ((this.field1 === null) === (this.field2 === null)) {
throw new Error('xnor failed');
}
}
}
})
var failingFoo = Foo.build({ field1: null, field2: null })
, errors = failingFoo.validate()
expect(errors).not.toBeNull()
expect(errors).toEqual({ 'xnor': ['xnor failed'] })
var successfulFoo = Foo.build({ field1: 33, field2: null })
expect(successfulFoo.validate()).toBeNull()
})
}) })
}) })
if (typeof require === 'function') {
const buster = require("buster")
, Helpers = require('./buster-helpers')
, dialect = Helpers.getTestDialect()
, _ = require('lodash')
}
buster.spec.expose()
describe(Helpers.getTestDialectTeaser("Promise"), function () {
before(function (done) {
var self = this
Helpers.initTests({
dialect: dialect,
beforeComplete: function (sequelize, DataTypes) {
self.sequelize = sequelize
self.User = sequelize.define('User', {
username: { type: DataTypes.STRING },
touchedAt: { type: DataTypes.DATE, defaultValue: DataTypes.NOW },
aNumber: { type: DataTypes.INTEGER },
bNumber: { type: DataTypes.INTEGER },
validateTest: {
type: DataTypes.INTEGER,
allowNull: true,
validate: {isInt: true}
},
validateCustom: {
type: DataTypes.STRING,
allowNull: true,
validate: {len: {msg: 'Length failed.', args: [1, 20]}}
},
dateAllowNullTrue: {
type: DataTypes.DATE,
allowNull: true
}
})
self.HistoryLog = sequelize.define('HistoryLog', {
someText: { type: DataTypes.STRING },
aNumber: { type: DataTypes.INTEGER },
aRandomId: { type: DataTypes.INTEGER }
})
self.ParanoidUser = sequelize.define('ParanoidUser', {
username: { type: DataTypes.STRING }
}, {
paranoid: true
})
self.ParanoidUser.hasOne(self.ParanoidUser)
},
onComplete: function () {
self.User.sync({ force: true }).then(function () {
return self.HistoryLog.sync({ force: true })
}).then(function () {
return self.ParanoidUser.sync({force: true })
})
.then(function () {done()}, done)
}
})
})
describe('increment', function () {
before(function (done) {
this.User.create({ id: 1, aNumber: 0, bNumber: 0 }).done(done)
});
it('with array', function (done) {
var self = this;
// Select something
this.User.find(1).then(function (user1) {
return user1.increment(['aNumber'], 2)
}).then(function (user2) {
return self.User.find(1)
}).then(function (user3) {
expect(user3.aNumber).toBe(2);
done();
}, done);
});
it('should still work right with other concurrent updates', function (done) {
var self = this;
// Select something
this.User.find(1).then(function (user1) {
// Select the user again (simulating a concurrent query)
return self.User.find(1).then(function (user2) {
return user2.updateAttributes({
aNumber: user2.aNumber + 1
}).then(function (user3) {
return user1.increment(['aNumber'], 2)
}).then(function (user4) {
return self.User.find(1)
}).then(function (user5) {
expect(user5.aNumber).toBe(3);
done();
}, done)
});
});
});
it('with key value pair', function (done) {
var self = this;
// Select something
this.User.find(1).then(function (user1) {
return user1.increment({ 'aNumber': 1, 'bNumber': 2})
}).then(function () {
return self.User.find(1)
}).then(function (user3) {
expect(user3.aNumber).toBe(1);
expect(user3.bNumber).toBe(2);
done();
}, done);
});
});
describe('decrement', function () {
before(function (done) {
this.User.create({ id: 1, aNumber: 0, bNumber: 0 }).done(done)
});
it('with array', function (done) {
var self = this;
// Select something
this.User.find(1).then(function (user1) {
return user1.decrement(['aNumber'], 2)
}).then(function () {
return self.User.find(1);
}).then(function (user3) {
expect(user3.aNumber).toBe(-2);
done();
}, done);
});
it('with single field', function (done) {
var self = this;
// Select something
this.User.find(1).then(function (user1) {
return user1.decrement(['aNumber'], 2)
}).then(function () {
return self.User.find(1);
}).then(function (user3) {
expect(user3.aNumber).toBe(-2);
done();
}, done);
});
it('should still work right with other concurrent decrements', function (done) {
var self = this;
// Select something
this.User.find(1).then(function (user1) {
var _done = _.after(3, function () {
self.User.find(1).then(function (user2) {
expect(user2.aNumber).toEqual(-6);
done();
})
});
user1.decrement(['aNumber'], 2).done(_done);
user1.decrement(['aNumber'], 2).done(_done);
user1.decrement(['aNumber'], 2).done(_done);
});
});
});
describe('reload', function () {
it("should return a reference to the same DAO instead of creating a new one", function (done) {
this.User.create({ username: 'John Doe' }).then(function (originalUser) {
return originalUser.updateAttributes({ username: 'Doe John' }).then(function () {
return originalUser.reload()
}).then(function (updatedUser) {
expect(originalUser === updatedUser).toBeTrue()
done();
}, done)
})
})
it("should update the values on all references to the DAO", function (done) {
var self = this
this.User.create({ username: 'John Doe' }).then(function (originalUser) {
return self.User.find(originalUser.id).then(function (updater) {
return updater.updateAttributes({ username: 'Doe John' })
}).then(function () {
// We used a different reference when calling updateAttributes, so originalUser is now out of sync
expect(originalUser.username).toEqual('John Doe')
return originalUser.reload()
}).then(function (updatedUser) {
expect(originalUser.username).toEqual('Doe John')
expect(updatedUser.username).toEqual('Doe John')
done();
}, done)
})
})
it("should update the associations as well", function (done) {
var Book = this.sequelize.define('Book', { title: Helpers.Sequelize.STRING })
, Page = this.sequelize.define('Page', { content: Helpers.Sequelize.TEXT })
Book.hasMany(Page)
Page.belongsTo(Book)
this.sequelize.sync({ force: true }).then(function () {
return Book.create({ title: 'A very old book' })
}).then(function (book) {
return Page.create({ content: 'om nom nom' }).then(function (page) {
return book.setPages([ page ]).then(function () {
return Book.find({
where: (dialect === 'postgres' ? '"Books"."id"=' : '`Books`.`id`=') + book.id,
include: [Page]
}).then(function (leBook) {
return page.updateAttributes({ content: 'something totally different' }).then(function (page) {
expect(leBook.pages[0].content).toEqual('om nom nom')
expect(page.content).toEqual('something totally different')
return leBook.reload().then(function (leBook) {
expect(leBook.pages[0].content).toEqual('something totally different')
expect(page.content).toEqual('something totally different')
done()
})
})
})
})
})
}, done)
})
});
describe('complete', function () {
it("gets triggered if an error occurs", function (done) {
this.User.find({ where: "asdasdasd" }).then(null, function (err) {
expect(err).toBeDefined()
expect(err.message).toBeDefined()
done()
})
})
it("gets triggered if everything was ok", function (done) {
this.User.count().then(function (result) {
expect(result).toBeDefined()
done()
})
})
})
describe('save', function () {
it('should fail a validation upon creating', function (done) {
this.User.create({aNumber: 0, validateTest: 'hello'}).then(null, function (err) {
expect(err).toBeDefined()
expect(err).toBeObject()
expect(err.validateTest).toBeArray()
expect(err.validateTest[0]).toBeDefined()
expect(err.validateTest[0].indexOf('Invalid integer')).toBeGreaterThan(-1);
done();
});
})
it('should fail a validation upon building', function (done) {
this.User.build({aNumber: 0, validateCustom: 'aaaaaaaaaaaaaaaaaaaaaaaaaa'}).save()
.then(null, function (err) {
expect(err).toBeDefined()
expect(err).toBeObject()
expect(err.validateCustom).toBeDefined()
expect(err.validateCustom).toBeArray()
expect(err.validateCustom[0]).toBeDefined()
expect(err.validateCustom[0]).toEqual('Length failed.')
done()
})
})
it('should fail a validation when updating', function (done) {
this.User.create({aNumber: 0}).then(function (user) {
return user.updateAttributes({validateTest: 'hello'})
}).then(null, function (err) {
expect(err).toBeDefined()
expect(err).toBeObject()
expect(err.validateTest).toBeDefined()
expect(err.validateTest).toBeArray()
expect(err.validateTest[0]).toBeDefined()
expect(err.validateTest[0].indexOf('Invalid integer:')).toBeGreaterThan(-1)
done()
})
})
})
})
...@@ -194,5 +194,29 @@ describe(Helpers.getTestDialectTeaser("Sequelize"), function() { ...@@ -194,5 +194,29 @@ describe(Helpers.getTestDialectTeaser("Sequelize"), function() {
}) })
}) })
}) })
describe('table', function() {
[
{ id: { type: Helpers.Sequelize.BIGINT } },
{ id: { type: Helpers.Sequelize.STRING, allowNull: true } },
{ id: { type: Helpers.Sequelize.BIGINT, allowNull: false, primaryKey: true, autoIncrement: true } }
].forEach(function(customAttributes) {
it('should be able to override options on the default attributes', function(done) {
var Picture = this.sequelize.define('picture', Helpers.Sequelize.Utils._.cloneDeep(customAttributes))
Picture.sync({ force: true }).success(function() {
Object.keys(customAttributes).forEach(function(attribute) {
Object.keys(customAttributes[attribute]).forEach(function(option) {
var optionValue = customAttributes[attribute][option];
expect(Picture.rawAttributes[attribute][option]).toBe(optionValue)
});
})
done()
})
})
})
})
}) })
}) })
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!