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

Commit 0234f990 by Mick Hansen

Merge

2 parents cc287b03 ab8850e6
...@@ -16,11 +16,10 @@ To install 2.x.x branch - which has a unstable API and will break backwards comp ...@@ -16,11 +16,10 @@ To install 2.x.x branch - which has a unstable API and will break backwards comp
- [Getting Started](http://sequelizejs.com/articles/getting-started) - [Getting Started](http://sequelizejs.com/articles/getting-started)
- [Documentation](http://sequelizejs.com/docs) - [Documentation](http://sequelizejs.com/docs)
<<<<<<< HEAD - [API Reference](https://github.com/sequelize/sequelize/wiki/API-Reference) *Work in progress*
======= - [Changelog](https://github.com/sequelize/sequelize/blob/master/changelog.md)
- [Collaboration and pull requests](https://github.com/sequelize/sequelize/wiki/Collaboration) - [Collaboration and pull requests](https://github.com/sequelize/sequelize/wiki/Collaboration)
- [Roadmap](https://github.com/sequelize/sequelize/wiki/Roadmap) - [Roadmap](https://github.com/sequelize/sequelize/wiki/Roadmap)
>>>>>>> master
## Important Notes ## ## Important Notes ##
......
Notice: All 1.7.x changed are present in 2.0.x aswell
# v2.0.0 (alpha1) # # v2.0.0 (alpha1) #
- [FEATURE] async validations. [#580](https://github.com/sequelize/sequelize/pull/580). thanks to Interlock - [FEATURE] async validations. [#580](https://github.com/sequelize/sequelize/pull/580). thanks to Interlock
# v1.7.0 # # v1.7.0-beta8
- max()/min() now supports dates [#1200](https://github.com/sequelize/sequelize/pull/1200)
- findAndCountAll now supports the include option
#### Backwards compatibility changes
- You will now need to include the relevant subtables to query on them in finders (find/findAll)
- Subquery logic no longer depends on where objects with keys containing '.', instead where options on the include options [#1199](https://github.com/sequelize/sequelize/pull/1199)
# v1.7.0-beta7 #
- Nested eager loading / prefetching is now supported. [Docs](http://sequelizejs.com/docs/latest/models#nested-eager-loading)
- Eager loading / prefetching now supports inner joins and extending the ON statement [#1199](https://github.com/sequelize/sequelize/pull/1199)
- Eager loading / prefetching now returns the attributes of through models aswell [#1198](https://github.com/sequelize/sequelize/pull/1198)
- New set/get/changed/previous feature [#1182](https://github.com/sequelize/sequelize/pull/1182)
- Various bug fixes
#### Backwards compatibility changes
None
# v1.7.0-beta1 #
- [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
- [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
- [DEPENDENCIES] Upgraded pg to 2.0.0. [#711](https://github.com/sequelize/sequelize/pull/711). thanks to durango - [DEPENDENCIES] Upgraded pg to 2.0.0. [#711](https://github.com/sequelize/sequelize/pull/711). thanks to durango
......
...@@ -39,8 +39,8 @@ module.exports = (function() { ...@@ -39,8 +39,8 @@ module.exports = (function() {
this.identifier = this.options.foreignKey || Utils._.underscoredIf(Utils.singularize(this.source.tableName, this.source.options.language) + "Id", this.options.underscored) this.identifier = this.options.foreignKey || Utils._.underscoredIf(Utils.singularize(this.source.tableName, this.source.options.language) + "Id", this.options.underscored)
newAttributes[this.identifier] = { type: this.options.keyType || keyType } newAttributes[this.identifier] = { type: this.options.keyType || keyType }
Helpers.addForeignKeyConstraints(newAttributes[this.identifier], this.source, this.target, this.options)
Utils._.defaults(this.target.rawAttributes, newAttributes) Utils._.defaults(this.target.rawAttributes, newAttributes)
Helpers.addForeignKeyConstraints(this.target.rawAttributes[this.identifier], this.source, this.target, this.options)
// Sync attributes and setters/getters to DAO prototype // Sync attributes and setters/getters to DAO prototype
this.target.refreshAttributes() this.target.refreshAttributes()
......
...@@ -66,6 +66,10 @@ module.exports = (function() { ...@@ -66,6 +66,10 @@ module.exports = (function() {
} }
}) })
Object.defineProperty(DAOFactory.prototype, 'sequelize', {
get: function() { return this.daoFactoryManager.sequelize }
})
Object.defineProperty(DAOFactory.prototype, 'QueryInterface', { Object.defineProperty(DAOFactory.prototype, 'QueryInterface', {
get: function() { return this.daoFactoryManager.sequelize.getQueryInterface() } get: function() { return this.daoFactoryManager.sequelize.getQueryInterface() }
}) })
...@@ -535,50 +539,72 @@ module.exports = (function() { ...@@ -535,50 +539,72 @@ module.exports = (function() {
}, queryOptions)) }, queryOptions))
} }
DAOFactory.prototype.count = function(options) { DAOFactory.prototype.aggregate = function(field, aggregateFunction, options) {
var tableField;
if (field == '*') {
tableField = field
} else {
tableField = this.QueryInterface.QueryGenerator.quoteIdentifier(field)
}
options = Utils._.extend({ attributes: [] }, options || {}) options = Utils._.extend({ attributes: [] }, options || {})
options.attributes.push(['count(*)', 'count']) options.attributes.push([aggregateFunction + '(' + tableField + ')', aggregateFunction])
options.parseInt = true
if (!options.dataType) {
if (this.rawAttributes[field]) {
options.dataType = this.rawAttributes[field]
} else {
// Use FLOAT as fallback
options.dataType = DataTypes.FLOAT
}
}
options = paranoidClause.call(this, options) options = paranoidClause.call(this, options)
return this.QueryInterface.rawSelect(this.getTableName(), options, 'count') return this.QueryInterface.rawSelect(this.getTableName(), options, aggregateFunction)
}
DAOFactory.prototype.count = function(options) {
options = Utils._.clone(options || {})
return new Utils.CustomEventEmitter(function (emitter) {
options.attributes = [
[this.sequelize.fn('COUNT', this.sequelize.col(this.tableName+'.*')), 'count']
]
options.includeIgnoreAttributes = false
this.find(options, {raw: true, transaction: options.transaction}).proxy(emitter, {events: ['sql', 'error']}).success(function (result) {
emitter.emit('success', parseInt(result.count, 10))
})
}.bind(this)).run()
} }
DAOFactory.prototype.findAndCountAll = function(options, queryOptions) { DAOFactory.prototype.findAndCountAll = function(findOptions, queryOptions) {
var self = this var self = this
// no limit, offset, order, attributes or include for the options given to count() // no limit, offset, order, attributes for the options given to count()
, copts = Utils._.omit(options || {}, ['offset', 'limit', 'order', 'include', 'attributes']) , countOptions = Utils._.omit(findOptions || {}, ['offset', 'limit', 'order', 'attributes'])
return new Utils.CustomEventEmitter(function (emitter) { return new Utils.CustomEventEmitter(function (emitter) {
var emit = { var emit = {
err : function(e) { // emit error okay : function(count, results) { // emit success
emitter.emit('error', e);
}
, okay : function(c, r) { // emit success
emitter.emit('success', { emitter.emit('success', {
count: c || 0, count: count || 0,
rows : (r && Array.isArray(r) ? r : []) rows : (results && Array.isArray(results) ? results : [])
}); })
}
, sql : function(s) { // emit SQL
emitter.emit('sql', s);
} }
} }
self.count(copts) self.count(countOptions)
.on('sql', emit.sql) .proxy(emitter, {events: ['sql', 'error']})
.error(emit.err) .success(function(count) {
.success(function(cnt) { if (count === 0) {
if (cnt === 0) { return emit.okay(count) // no records, no need for another query
return emit.okay(cnt) // no records, no need for another query
} }
self.findAll(options, queryOptions) self.findAll(findOptions, queryOptions)
.on('sql', emit.sql) .proxy(emitter, {events: ['sql', 'error']})
.error(emit.err) .success(function(results) {
.success(function(rows) { emit.okay(count, results)
emit.okay(cnt, rows)
}) })
}) })
...@@ -586,18 +612,11 @@ module.exports = (function() { ...@@ -586,18 +612,11 @@ module.exports = (function() {
} }
DAOFactory.prototype.max = function(field, options) { DAOFactory.prototype.max = function(field, options) {
options = Utils._.extend({ attributes: [] }, options || {}) return this.aggregate(field, 'max', options)
options.attributes.push(['max(' + this.QueryInterface.QueryGenerator.quoteIdentifier(field) + ')', 'max'])
options.parseFloat = true
return this.QueryInterface.rawSelect(this.getTableName(), options, 'max')
} }
DAOFactory.prototype.min = function(field, options) {
options = Utils._.extend({ attributes: [] }, options || {})
options.attributes.push(['min(' + this.QueryInterface.QueryGenerator.quoteIdentifier(field) + ')', 'min'])
options.parseFloat = true
return this.QueryInterface.rawSelect(this.getTableName(), options, 'min') DAOFactory.prototype.min = function(field, options) {
return this.aggregate(field, 'min', options)
} }
DAOFactory.prototype.build = function(values, options) { DAOFactory.prototype.build = function(values, options) {
...@@ -1270,11 +1289,15 @@ module.exports = (function() { ...@@ -1270,11 +1289,15 @@ module.exports = (function() {
options.include = options.include.map(function(include) { options.include = options.include.map(function(include) {
include = validateIncludedElement.call(this, include, options.daoFactory) include = validateIncludedElement.call(this, include, options.daoFactory)
options.includeMap[include.as] = include
options.includeNames.push(include.as)
if (include.association.isMultiAssociation) options.hasMultiAssociation = true if (include.association.isMultiAssociation) options.hasMultiAssociation = true
if (include.association.isSingleAssociation) options.hasSingleAssociation = true if (include.association.isSingleAssociation) options.hasSingleAssociation = true
options.includeMap[include.as] = include options.hasIncludeWhere = options.hasIncludeWhere || include.hasIncludeWhere || !!include.where
options.includeNames.push(include.as) options.hasIncludeRequired = options.hasIncludeRequired || include.hasIncludeRequired || !!include.required
return include return include
}.bind(this)) }.bind(this))
}; };
......
...@@ -608,14 +608,6 @@ module.exports = (function() { ...@@ -608,14 +608,6 @@ module.exports = (function() {
return result return result
} }
DAO.prototype.addAttribute = function(attribute, value) {
if (this.booleanValues.length && this.booleanValues.indexOf(attribute) !== -1 && value != null) { // transform integer 0,1 into boolean
value = !!value
}
this[attribute] = value;
}
DAO.prototype.setValidators = function(attribute, validators) { DAO.prototype.setValidators = function(attribute, validators) {
this.validators[attribute] = validators this.validators[attribute] = validators
} }
......
...@@ -46,6 +46,10 @@ var BLOB = function() { ...@@ -46,6 +46,10 @@ var BLOB = function() {
return BLOB.prototype.construct.apply(this, [BLOB].concat(Array.prototype.slice.apply(arguments))) return BLOB.prototype.construct.apply(this, [BLOB].concat(Array.prototype.slice.apply(arguments)))
} }
var DECIMAL = function() {
return DECIMAL.prototype.construct.apply(this, [DECIMAL].concat(Array.prototype.slice.apply(arguments)))
}
FLOAT._type = FLOAT FLOAT._type = FLOAT
FLOAT._typeName = 'FLOAT' FLOAT._typeName = 'FLOAT'
INTEGER._type = INTEGER INTEGER._type = INTEGER
...@@ -56,8 +60,11 @@ STRING._type = STRING ...@@ -56,8 +60,11 @@ STRING._type = STRING
STRING._typeName = 'VARCHAR' STRING._typeName = 'VARCHAR'
BLOB._type = BLOB BLOB._type = BLOB
BLOB._typeName = 'BLOB' BLOB._typeName = 'BLOB'
DECIMAL._type = DECIMAL
DECIMAL._typeName = 'DECIMAL'
BLOB.toString = STRING.toString = INTEGER.toString = FLOAT.toString = BIGINT.toString = function() { BLOB.toString = STRING.toString = INTEGER.toString = FLOAT.toString = BIGINT.toString = DECIMAL.toString = function() {
return new this._type().toString() return new this._type().toString()
} }
...@@ -145,6 +152,47 @@ FLOAT.prototype = BIGINT.prototype = INTEGER.prototype = { ...@@ -145,6 +152,47 @@ FLOAT.prototype = BIGINT.prototype = INTEGER.prototype = {
} }
} }
DECIMAL.prototype = {
construct: function(RealType, precision, scale) {
if (this instanceof RealType) {
this._typeName = RealType._typeName
if (typeof precision === 'number') {
this._precision = precision
} else {
this._precision = 0
}
if (typeof scale === 'number') {
this._scale = scale
} else {
this._scale = 0
}
} else {
return new RealType(precision, scale)
}
},
get type() {
return this.toString()
},
get PRECISION() {
return this._precision
},
get SCALE() {
return this._scale
},
toString: function() {
if (this._precision || this._scale) {
return 'DECIMAL(' + this._precision + ',' + this._scale + ')'
}
return 'DECIMAL'
}
}
var unsignedDesc = { var unsignedDesc = {
get: function() { get: function() {
return new this._type(undefined, undefined, true) return new this._type(undefined, undefined, true)
...@@ -163,11 +211,18 @@ var typeDesc = { ...@@ -163,11 +211,18 @@ var typeDesc = {
} }
} }
var decimalDesc = {
get: function() {
return new this._type(undefined, undefined, undefined)
}
}
Object.defineProperty(STRING, 'type', typeDesc) Object.defineProperty(STRING, 'type', typeDesc)
Object.defineProperty(INTEGER, 'type', typeDesc) Object.defineProperty(INTEGER, 'type', typeDesc)
Object.defineProperty(BIGINT, 'type', typeDesc) Object.defineProperty(BIGINT, 'type', typeDesc)
Object.defineProperty(FLOAT, 'type', typeDesc) Object.defineProperty(FLOAT, 'type', typeDesc)
Object.defineProperty(BLOB, 'type', typeDesc) Object.defineProperty(BLOB, 'type', typeDesc)
Object.defineProperty(DECIMAL, 'type', typeDesc)
Object.defineProperty(INTEGER, 'UNSIGNED', unsignedDesc) Object.defineProperty(INTEGER, 'UNSIGNED', unsignedDesc)
Object.defineProperty(BIGINT, 'UNSIGNED', unsignedDesc) Object.defineProperty(BIGINT, 'UNSIGNED', unsignedDesc)
...@@ -177,6 +232,9 @@ Object.defineProperty(INTEGER, 'ZEROFILL', zerofillDesc) ...@@ -177,6 +232,9 @@ Object.defineProperty(INTEGER, 'ZEROFILL', zerofillDesc)
Object.defineProperty(BIGINT, 'ZEROFILL', zerofillDesc) Object.defineProperty(BIGINT, 'ZEROFILL', zerofillDesc)
Object.defineProperty(FLOAT, 'ZEROFILL', zerofillDesc) Object.defineProperty(FLOAT, 'ZEROFILL', zerofillDesc)
Object.defineProperty(DECIMAL, 'PRECISION', decimalDesc)
Object.defineProperty(DECIMAL, 'SCALE', decimalDesc)
module.exports = { module.exports = {
STRING: STRING, STRING: STRING,
...@@ -188,6 +246,7 @@ module.exports = { ...@@ -188,6 +246,7 @@ module.exports = {
FLOAT: FLOAT, FLOAT: FLOAT,
NOW: 'NOW', NOW: 'NOW',
BLOB: BLOB, BLOB: BLOB,
DECIMAL: DECIMAL,
UUID: 'UUID', UUID: 'UUID',
UUIDV1: 'UUIDV1', UUIDV1: 'UUIDV1',
UUIDV4: 'UUIDV4', UUIDV4: 'UUIDV4',
...@@ -207,16 +266,6 @@ module.exports = { ...@@ -207,16 +266,6 @@ module.exports = {
return result return result
}, },
get DECIMAL() {
var result = function(precision, scale) {
return 'DECIMAL(' + precision + ',' + scale + ')'
}
result.toString = result.valueOf = function() { return 'DECIMAL' }
return result
},
ARRAY: function(type) { return type + '[]' }, ARRAY: function(type) { return type + '[]' },
get HSTORE() { get HSTORE() {
......
...@@ -460,32 +460,69 @@ module.exports = (function() { ...@@ -460,32 +460,69 @@ module.exports = (function() {
- limit -> The maximum count you want to get. - limit -> The maximum count you want to get.
- offset -> An offset value to start from. Only useable with limit! - offset -> An offset value to start from. Only useable with limit!
*/ */
selectQuery: function(tableName, options, factory) { selectQuery: function(tableName, options, factory) {
// Enter and change at your own peril -- Mick Hansen
options = options || {}
var table = null var table = null
, self = this , self = this
, joinQuery = "" , query
, limit = options.limit
, mainQueryItems = []
, mainAttributes = options.attributes
, mainJoinQueries = []
// We'll use a subquery if there's a limit, if we have hasMany associations and if any of them are filtered
, subQuery = limit && options && options.hasIncludeWhere && options.hasIncludeRequired && options.hasMultiAssociation
, subQueryItems = []
, subQueryAttributes = null
, subJoinQueries = []
// Escape table
options.table = table = !Array.isArray(tableName) ? this.quoteIdentifiers(tableName) : tableName.map(function(t) {
return this.quoteIdentifiers(t)
}.bind(this)).join(", ")
options = options || {} // Escape attributes
options.table = table = Array.isArray(tableName) ? tableName.map(function(t) { return this.quoteIdentifiers(t) }.bind(this)).join(", ") : this.quoteIdentifiers(tableName) mainAttributes = mainAttributes && mainAttributes.map(function(attr){
var addTable = true
if (attr instanceof Utils.literal) {
return attr.toString(this)
}
if (attr instanceof Utils.fn || attr instanceof Utils.col) {
return self.quote(attr)
}
options.attributes = options.attributes && options.attributes.map(function(attr){
if(Array.isArray(attr) && attr.length == 2) { if(Array.isArray(attr) && attr.length == 2) {
if (attr[0] instanceof Utils.fn || attr[0] instanceof Utils.col) {
attr[0] = self.quote(attr[0])
addTable = false
}
attr = [attr[0], this.quoteIdentifier(attr[1])].join(' as ') attr = [attr[0], this.quoteIdentifier(attr[1])].join(' as ')
} else { } else {
attr = attr.indexOf(Utils.TICK_CHAR) < 0 && attr.indexOf('"') < 0 ? this.quoteIdentifiers(attr) : attr attr = attr.indexOf(Utils.TICK_CHAR) < 0 && attr.indexOf('"') < 0 ? this.quoteIdentifiers(attr) : attr
} }
if (options.include && attr.indexOf('.') === -1) { if (options.include && attr.indexOf('.') === -1 && addTable) {
attr = this.quoteIdentifier(options.table) + '.' + attr attr = this.quoteIdentifier(options.table) + '.' + attr
} }
return attr return attr
}.bind(this)).join(", ") }.bind(this))
options.attributes = options.attributes || '*'
if (options.include) { // If no attributes specified, use *
var optAttributes = options.attributes === '*' ? [options.table + '.*'] : [options.attributes] mainAttributes = mainAttributes || (options.include ? [options.table+'.*'] : ['*'])
// If subquery, we ad the mainAttributes to the subQuery and set the mainAttributes to select * from subquery
if (subQuery) {
subQueryAttributes = mainAttributes
mainAttributes = [options.table+'.*']
}
if (options.include) {
var generateJoinQuery = function(include, parentTable) { var generateJoinQuery = function(include, parentTable) {
var table = include.daoFactory.tableName var table = include.daoFactory.tableName
, as = include.as , as = include.as
...@@ -494,7 +531,7 @@ module.exports = (function() { ...@@ -494,7 +531,7 @@ module.exports = (function() {
, association = include.association , association = include.association
, through = include.through , through = include.through
, joinType = include.required ? ' INNER JOIN ' : ' LEFT OUTER JOIN ' , joinType = include.required ? ' INNER JOIN ' : ' LEFT OUTER JOIN '
, where = {} , includeWhere = {}
, whereOptions = Utils._.clone(options) , whereOptions = Utils._.clone(options)
if (tableName !== parentTable) as = parentTable+'.'+include.as if (tableName !== parentTable) as = parentTable+'.'+include.as
...@@ -502,19 +539,25 @@ module.exports = (function() { ...@@ -502,19 +539,25 @@ module.exports = (function() {
if (include.where) { if (include.where) {
for (var key in include.where) { for (var key in include.where) {
if (include.where.hasOwnProperty(key)) { if (include.where.hasOwnProperty(key)) {
where[self.quoteIdentifier(as)+'.'+self.quoteIdentifiers(key)] = include.where[key] includeWhere[self.quoteIdentifier(as)+'.'+self.quoteIdentifiers(key)] = include.where[key]
} }
} }
include.where = where
whereOptions.keysEscaped = true whereOptions.keysEscaped = true
} }
// includeIgnoreAttributes is used by aggregate functions
if (options.includeIgnoreAttributes !== false) {
attributes = include.attributes.map(function(attr) { attributes = include.attributes.map(function(attr) {
return self.quoteIdentifier(as) + "." + self.quoteIdentifier(attr) + " AS " + self.quoteIdentifier(as + "." + attr) return self.quoteIdentifier(as) + "." + self.quoteIdentifier(attr) + " AS " + self.quoteIdentifier(as + "." + attr)
}) })
optAttributes = optAttributes.concat(attributes) // If not many to many, and we're doing a subquery, add attributes to the subquery
if (!include.association.isMultiAssociation && subQuery && (include.hasIncludeRequired || include.required)) {
subQueryAttributes = subQueryAttributes.concat(attributes)
} else {
mainAttributes = mainAttributes.concat(attributes)
}
}
if (through) { if (through) {
var throughTable = through.daoFactory.tableName var throughTable = through.daoFactory.tableName
...@@ -522,29 +565,61 @@ module.exports = (function() { ...@@ -522,29 +565,61 @@ module.exports = (function() {
, throughAttributes = through.attributes.map(function(attr) { , throughAttributes = through.attributes.map(function(attr) {
return self.quoteIdentifier(throughAs) + "." + self.quoteIdentifier(attr) + " AS " + self.quoteIdentifier(throughAs + "." + attr) return self.quoteIdentifier(throughAs) + "." + self.quoteIdentifier(attr) + " AS " + self.quoteIdentifier(throughAs + "." + attr)
}) })
, primaryKeysSource = Object.keys(association.source.primaryKeys)
optAttributes = optAttributes.concat(throughAttributes)
var primaryKeysSource = Object.keys(association.source.primaryKeys)
, tableSource = parentTable , tableSource = parentTable
, identSource = association.identifier , identSource = association.identifier
, attrSource = ((!association.source.hasPrimaryKeys || primaryKeysSource.length !== 1) ? 'id' : primaryKeysSource[0]) , attrSource = ((!association.source.hasPrimaryKeys || primaryKeysSource.length !== 1) ? 'id' : primaryKeysSource[0])
, where
var primaryKeysTarget = Object.keys(association.target.primaryKeys) , primaryKeysTarget = Object.keys(association.target.primaryKeys)
, tableTarget = as , tableTarget = as
, identTarget = association.foreignIdentifier , identTarget = association.foreignIdentifier
, attrTarget = ((!include.association.target.hasPrimaryKeys || primaryKeysTarget.length !== 1) ? 'id' : primaryKeysTarget[0]) , attrTarget = ((!include.association.target.hasPrimaryKeys || primaryKeysTarget.length !== 1) ? 'id' : primaryKeysTarget[0])
, sourceJoinOn
, targetJoinOn
, targetWhere
if (options.includeIgnoreAttributes !== false) {
// Through includes are always hasMany, so we need to add the attributes to the mainAttributes no matter what (Real join will never be executed in subquery)
mainAttributes = mainAttributes.concat(throughAttributes)
}
// Filter statement for left side of through
// Used by both join and subquery where
sourceJoinOn = self.quoteIdentifier(tableSource) + "." + self.quoteIdentifier(attrSource) + " = "
sourceJoinOn += self.quoteIdentifier(throughAs) + "." + self.quoteIdentifier(identSource)
// Filter statement for right side of through
// Used by both join and subquery where
targetJoinOn = self.quoteIdentifier(tableTarget) + "." + self.quoteIdentifier(attrTarget) + " = "
targetJoinOn += self.quoteIdentifier(throughAs) + "." + self.quoteIdentifier(identTarget)
// Generate join SQL for left side of through
joinQueryItem += joinType + self.quoteIdentifier(throughTable) + " AS " + self.quoteIdentifier(throughAs) + " ON " joinQueryItem += joinType + self.quoteIdentifier(throughTable) + " AS " + self.quoteIdentifier(throughAs) + " ON "
joinQueryItem += self.quoteIdentifier(tableSource) + "." + self.quoteIdentifier(attrSource) + " = " joinQueryItem += sourceJoinOn
joinQueryItem += self.quoteIdentifier(throughAs) + "." + self.quoteIdentifier(identSource)
// Generate join SQL for right side of through
joinQueryItem += joinType + self.quoteIdentifier(table) + " AS " + self.quoteIdentifier(as) + " ON " joinQueryItem += joinType + self.quoteIdentifier(table) + " AS " + self.quoteIdentifier(as) + " ON "
joinQueryItem += self.quoteIdentifier(tableTarget) + "." + self.quoteIdentifier(attrTarget) + " = " joinQueryItem += targetJoinOn
joinQueryItem += self.quoteIdentifier(throughAs) + "." + self.quoteIdentifier(identTarget)
if (include.where) { if (include.where) {
joinQueryItem += " AND "+self.hashToWhereConditions(include.where, include.daoFactory, whereOptions) targetWhere = self.hashToWhereConditions(includeWhere, include.daoFactory, whereOptions)
joinQueryItem += " AND "+ targetWhere
if (subQuery) {
if (!options.where) options.where = {}
// Creating the as-is where for the subQuery, checks that the required association exists
var _where = "(";
_where += "SELECT "+self.quoteIdentifier(identSource)+" FROM " + self.quoteIdentifier(throughTable) + " AS " + self.quoteIdentifier(throughAs);
_where += joinType + self.quoteIdentifier(table) + " AS " + self.quoteIdentifier(as) + " ON "+targetJoinOn;
_where += " WHERE " + sourceJoinOn + " AND " + targetWhere + " LIMIT 1"
_where += ")";
_where += " IS NOT NULL"
options.where["__"+throughAs] = self.sequelize.asIs(_where)
}
} }
} else { } else {
var primaryKeysLeft = ((association.associationType === 'BelongsTo') ? Object.keys(association.target.primaryKeys) : Object.keys(include.association.source.primaryKeys)) var primaryKeysLeft = ((association.associationType === 'BelongsTo') ? Object.keys(association.target.primaryKeys) : Object.keys(include.association.source.primaryKeys))
...@@ -552,13 +627,26 @@ module.exports = (function() { ...@@ -552,13 +627,26 @@ module.exports = (function() {
, attrLeft = ((primaryKeysLeft.length !== 1) ? 'id' : primaryKeysLeft[0]) , attrLeft = ((primaryKeysLeft.length !== 1) ? 'id' : primaryKeysLeft[0])
, tableRight = ((association.associationType === 'BelongsTo') ? parentTable : as) , tableRight = ((association.associationType === 'BelongsTo') ? parentTable : as)
, attrRight = association.identifier , attrRight = association.identifier
, where
// Filter statement
// Used by both join and subquery where
where = self.quoteIdentifier(tableLeft) + "." + self.quoteIdentifier(attrLeft) + " = "
where += self.quoteIdentifier(tableRight) + "." + self.quoteIdentifier(attrRight)
// Generate join SQL
joinQueryItem += joinType + self.quoteIdentifier(table) + " AS " + self.quoteIdentifier(as) + " ON " joinQueryItem += joinType + self.quoteIdentifier(table) + " AS " + self.quoteIdentifier(as) + " ON "
joinQueryItem += self.quoteIdentifier(tableLeft) + "." + self.quoteIdentifier(attrLeft) + " = " joinQueryItem += where
joinQueryItem += self.quoteIdentifier(tableRight) + "." + self.quoteIdentifier(attrRight)
if (include.where) { if (include.where) {
joinQueryItem += " AND "+self.hashToWhereConditions(include.where, include.daoFactory, whereOptions) joinQueryItem += " AND "+self.hashToWhereConditions(includeWhere, include.daoFactory, whereOptions)
// If its a multi association we need to add a where query to the main where (executed in the subquery)
if (subQuery && association.isMultiAssociation) {
if (!options.where) options.where = {}
// Creating the as-is where for the subQuery, checks that the required association exists
options.where["__"+as] = self.sequelize.asIs("(SELECT "+self.quoteIdentifier(attrRight)+" FROM " + self.quoteIdentifier(tableRight) + " WHERE " + where + " LIMIT 1) IS NOT NULL")
}
} }
} }
...@@ -572,47 +660,84 @@ module.exports = (function() { ...@@ -572,47 +660,84 @@ module.exports = (function() {
return joinQueryItem return joinQueryItem
} }
// Loop through includes and generate subqueries
options.include.forEach(function(include) { options.include.forEach(function(include) {
joinQuery += generateJoinQuery(include, tableName) var joinQueryItem = generateJoinQuery(include, tableName)
}.bind(this))
options.attributes = optAttributes.join(', ') // If not many to many, and we're doing a subquery, add joinQuery to subQueries
if (!include.association.isMultiAssociation && (subQuery && (include.hasIncludeWhere || include.where))) {
subJoinQueries.push(joinQueryItem)
} else {
mainJoinQueries.push(joinQueryItem)
}
}.bind(this))
} }
var conditionalJoins = ((options.hasMultiAssociation && (options.limit || options.offset)) || !options.include) && this.getConditionalJoins(options, factory), // If using subQuery select defined subQuery attributes and join subJoinQueries
query; if (subQuery) {
subQueryItems.push("SELECT " + subQueryAttributes.join(', ') + " FROM " + options.table)
subQueryItems.push(subJoinQueries.join(''))
if (conditionalJoins) { // Else do it the reguar way
query = "SELECT " + options.attributes + " FROM ( "
+ "SELECT " + options.table + ".* FROM " + options.table + this.getConditionalJoins(options, factory)
} else { } else {
query = "SELECT " + options.attributes + " FROM " + options.table mainQueryItems.push("SELECT " + mainAttributes.join(', ') + " FROM " + options.table)
query += joinQuery mainQueryItems.push(mainJoinQueries.join(''))
} }
// Add WHERE to sub or main query
if (options.hasOwnProperty('where')) { if (options.hasOwnProperty('where')) {
options.where = this.getWhereConditions(options.where, tableName, factory, options) options.where = this.getWhereConditions(options.where, tableName, factory, options)
query += " WHERE " + options.where if (subQuery) {
subQueryItems.push(" WHERE " + options.where)
} else {
mainQueryItems.push(" WHERE " + options.where)
}
} }
// Add GROUP BY to sub or main query
if (options.group) { if (options.group) {
options.group = Array.isArray(options.group) ? options.group.map(function (t) { return this.quote(t) }.bind(this)).join(', ') : options.group options.group = Array.isArray(options.group) ? options.group.map(function (t) { return this.quote(t) }.bind(this)).join(', ') : options.group
query += " GROUP BY " + options.group if (subQuery) {
subQueryItems.push(" GROUP BY " + options.group)
} else {
mainQueryItems.push(" GROUP BY " + options.group)
}
} }
// Add ORDER to sub or main query
if (options.order) { if (options.order) {
options.order = Array.isArray(options.order) ? options.order.map(function (t) { return this.quote(t) }.bind(this)).join(', ') : options.order options.order = Array.isArray(options.order) ? options.order.map(function (t) { return this.quote(t) }.bind(this)).join(', ') : options.order
query += " ORDER BY " + options.order
if (subQuery) {
subQueryItems.push(" ORDER BY " + options.order)
} else {
mainQueryItems.push(" ORDER BY " + options.order)
} }
}
query = this.addLimitAndOffset(options, query) var limitOrder = this.addLimitAndOffset(options, query)
if (conditionalJoins) { // Add LIMIT, OFFSET to sub or main query
query += ") AS " + options.table if (limitOrder) {
query += joinQuery if (subQuery) {
subQueryItems.push(limitOrder)
} else {
mainQueryItems.push(limitOrder)
}
}
// If using subQuery, select attributes from wrapped subQuery and join out join tables
if (subQuery) {
query = "SELECT " + mainAttributes.join(', ') + " FROM ("
query += subQueryItems.join('')
query += ") AS "+options.table
query += mainJoinQueries.join('')
} else {
query = mainQueryItems.join('')
} }
query += ";" query += ";";
return query return query
}, },
...@@ -662,6 +787,8 @@ module.exports = (function() { ...@@ -662,6 +787,8 @@ module.exports = (function() {
}, },
addLimitAndOffset: function(options, query){ addLimitAndOffset: function(options, query){
query = query || ""
if (options.offset && !options.limit) { if (options.offset && !options.limit) {
query += " LIMIT " + options.offset + ", " + 10000000000000; query += " LIMIT " + options.offset + ", " + 10000000000000;
} else if (options.limit && !(options.include && (options.limit === 1))) { } else if (options.limit && !(options.include && (options.limit === 1))) {
...@@ -847,11 +974,16 @@ module.exports = (function() { ...@@ -847,11 +974,16 @@ module.exports = (function() {
options = options || {} options = options || {}
for (var key in hash) { // Closures are nice
var value = hash[key] Utils._.each(hash, function (value, key) {
, _key var _key
, _value = null , _value = null
if (value instanceof Utils.asIs) {
result.push(value.toString(this))
return
}
if (options.keysEscaped) { if (options.keysEscaped) {
_key = key _key = key
} else { } else {
...@@ -862,8 +994,6 @@ module.exports = (function() { ...@@ -862,8 +994,6 @@ module.exports = (function() {
} }
} }
//handle qualified key names
if (Array.isArray(value)) { if (Array.isArray(value)) {
result.push(this.arrayValue(value, key, _key, dao)) result.push(this.arrayValue(value, key, _key, dao))
} else if ((value) && (typeof value == 'object') && !(value instanceof Date) && !Buffer.isBuffer(value)) { } else if ((value) && (typeof value == 'object') && !(value instanceof Date) && !Buffer.isBuffer(value)) {
...@@ -896,9 +1026,10 @@ module.exports = (function() { ...@@ -896,9 +1026,10 @@ module.exports = (function() {
} else { } else {
_value = this.escape(value) _value = this.escape(value)
} }
result.push((_value == 'NULL') ? _key + " IS NULL" : [_key, _value].join("=")) result.push((_value == 'NULL') ? _key + " IS NULL" : [_key, _value].join("="))
} }
} }.bind(this))
return result.join(" AND ") return result.join(" AND ")
}, },
......
...@@ -364,6 +364,7 @@ module.exports = (function() { ...@@ -364,6 +364,7 @@ module.exports = (function() {
}) })
delete result.__children delete result.__children
}) })
return results return results
} }
......
var _ = require('lodash') var _ = require('lodash')
, MySQL = require('./mysql') , MySQL = require('../mysql')
var MariaDialect = function(sequelize) { var MariaDialect = function(sequelize) {
this.sequelize = sequelize this.sequelize = sequelize
......
var _ = require('lodash') var _ = require('lodash')
, Abstract = require('./abstract/dialect') , Abstract = require('../abstract')
var MysqlDialect = function(sequelize) { var MysqlDialect = function(sequelize) {
this.sequelize = sequelize this.sequelize = sequelize
......
...@@ -374,6 +374,7 @@ module.exports = (function() { ...@@ -374,6 +374,7 @@ module.exports = (function() {
}, },
addLimitAndOffset: function(options, query){ addLimitAndOffset: function(options, query){
query = query || ""
if (options.offset && !options.limit) { if (options.offset && !options.limit) {
query += " LIMIT " + options.offset + ", " + 18440000000000000000; query += " LIMIT " + options.offset + ", " + 18440000000000000000;
} else if (options.limit && !(options.include && (options.limit === 1))) { } else if (options.limit && !(options.include && (options.limit === 1))) {
...@@ -387,6 +388,7 @@ module.exports = (function() { ...@@ -387,6 +388,7 @@ module.exports = (function() {
}, },
quoteIdentifier: function(identifier, force) { quoteIdentifier: function(identifier, force) {
if (identifier === '*') return identifier
return Utils.addTicks(identifier, "`") return Utils.addTicks(identifier, "`")
}, },
......
var _ = require('lodash') var _ = require('lodash')
, Abstract = require('./abstract/dialect') , Abstract = require('../abstract')
var PostgresDialect = function(sequelize) { var PostgresDialect = function(sequelize) {
this.sequelize = sequelize this.sequelize = sequelize
......
...@@ -389,6 +389,7 @@ module.exports = (function() { ...@@ -389,6 +389,7 @@ module.exports = (function() {
}, },
addLimitAndOffset: function(options, query){ addLimitAndOffset: function(options, query){
query = query || ""
if (!(options.include && (options.limit === 1))) { if (!(options.include && (options.limit === 1))) {
if (options.limit) { if (options.limit) {
query += " LIMIT " + options.limit query += " LIMIT " + options.limit
...@@ -774,6 +775,7 @@ module.exports = (function() { ...@@ -774,6 +775,7 @@ module.exports = (function() {
}, },
quoteIdentifier: function(identifier, force) { quoteIdentifier: function(identifier, force) {
if (identifier === '*') return identifier
if(!force && this.options && this.options.quoteIdentifiers === false) { // default is `true` if(!force && this.options && this.options.quoteIdentifiers === false) { // default is `true`
// In Postgres, if tables or attributes are created double-quoted, // In Postgres, if tables or attributes are created double-quoted,
// they are also case sensitive. If they contain any uppercase // they are also case sensitive. If they contain any uppercase
......
var _ = require('lodash') var _ = require('lodash')
, Abstract = require('./abstract/dialect') , Abstract = require('../abstract')
var SqliteDialect = function(sequelize) { var SqliteDialect = function(sequelize) {
this.sequelize = sequelize this.sequelize = sequelize
......
...@@ -132,6 +132,7 @@ module.exports = (function() { ...@@ -132,6 +132,7 @@ module.exports = (function() {
}, },
addLimitAndOffset: function(options, query){ addLimitAndOffset: function(options, query){
query = query || ""
if (options.offset && !options.limit) { if (options.offset && !options.limit) {
query += " LIMIT " + options.offset + ", " + 10000000000000; query += " LIMIT " + options.offset + ", " + 10000000000000;
} else if (options.limit && !(options.include && (options.limit === 1))) { } else if (options.limit && !(options.include && (options.limit === 1))) {
...@@ -380,6 +381,7 @@ module.exports = (function() { ...@@ -380,6 +381,7 @@ module.exports = (function() {
}, },
quoteIdentifier: function(identifier, force) { quoteIdentifier: function(identifier, force) {
if (identifier === '*') return identifier
return Utils.addTicks(identifier, "`") return Utils.addTicks(identifier, "`")
}, },
......
...@@ -9,6 +9,7 @@ module.exports = (function() { ...@@ -9,6 +9,7 @@ module.exports = (function() {
this.QueryGenerator = require('./dialects/' + this.sequelize.options.dialect + '/query-generator') this.QueryGenerator = require('./dialects/' + this.sequelize.options.dialect + '/query-generator')
this.QueryGenerator.options = this.sequelize.options this.QueryGenerator.options = this.sequelize.options
this.QueryGenerator._dialect = this.sequelize.dialect this.QueryGenerator._dialect = this.sequelize.dialect
this.QueryGenerator.sequelize = this.sequelize
} }
Utils.addEventEmitter(QueryInterface) Utils.addEventEmitter(QueryInterface)
...@@ -686,12 +687,18 @@ module.exports = (function() { ...@@ -686,12 +687,18 @@ module.exports = (function() {
.success(function(data) { .success(function(data) {
var result = data ? data[attributeSelector] : null var result = data ? data[attributeSelector] : null
if (options && options.parseInt) { if (options && options.dataType) {
result = parseInt(result, 10) var dataType = options.dataType;
}
if (options && options.parseFloat) { if (dataType instanceof DataTypes.DECIMAL || dataType instanceof DataTypes.FLOAT) {
result = parseFloat(result) result = parseFloat(result);
} else if (dataType === DataTypes.INTEGER || dataType instanceof DataTypes.BIGINT) {
result = parseInt(result, 10);
} else if (dataType === DataTypes.DATE) {
result = new Date(result + 'Z');
} else if (dataType === DataTypes.STRING) {
// Nothing to do, result is already a string.
}
} }
self.emit('rawSelect', null) self.emit('rawSelect', null)
......
...@@ -113,7 +113,8 @@ module.exports = (function() { ...@@ -113,7 +113,8 @@ module.exports = (function() {
} }
try { try {
this.dialect = new (require("./dialects/" + this.getDialect()))(this) var Dialect = require("./dialects/" + this.getDialect())
this.dialect = new Dialect(this)
} catch(err) { } catch(err) {
throw new Error("The dialect " + this.getDialect() + " is not supported.") throw new Error("The dialect " + this.getDialect() + " is not supported.")
} }
...@@ -395,22 +396,26 @@ module.exports = (function() { ...@@ -395,22 +396,26 @@ module.exports = (function() {
}).run() }).run()
} }
Sequelize.prototype.fn = function (fn) { Sequelize.fn = Sequelize.prototype.fn = function (fn) {
return new Utils.fn(fn, Array.prototype.slice.call(arguments, 1)) return new Utils.fn(fn, Array.prototype.slice.call(arguments, 1))
} }
Sequelize.prototype.col = function (col) { Sequelize.col = Sequelize.prototype.col = function (col) {
return new Utils.col(col) return new Utils.col(col)
} }
Sequelize.prototype.cast = function (val, type) { Sequelize.cast = Sequelize.prototype.cast = function (val, type) {
return new Utils.cast(val, type) return new Utils.cast(val, type)
} }
Sequelize.prototype.literal = function (val) { Sequelize.literal = Sequelize.prototype.literal = function (val) {
return new Utils.literal(val) return new Utils.literal(val)
} }
Sequelize.asIs = Sequelize.prototype.asIs = function (val) {
return new Utils.asIs(val)
}
Sequelize.prototype.transaction = function(_options, _callback) { Sequelize.prototype.transaction = function(_options, _callback) {
var options = (typeof _options === 'function') ? {} : _options var options = (typeof _options === 'function') ? {} : _options
, callback = (typeof _options === 'function') ? _options : _callback , callback = (typeof _options === 'function') ? _options : _callback
......
...@@ -450,7 +450,9 @@ var Utils = module.exports = { ...@@ -450,7 +450,9 @@ var Utils = module.exports = {
var _hash = {} var _hash = {}
for (var key in hash) { for (var key in hash) {
if (key.indexOf('.') === -1) { if (key instanceof Utils.literal) {
_hash[key] = hash[key]
} else if (key.indexOf('.') === -1) {
_hash[tableName + '.' + key] = hash[key] _hash[tableName + '.' + key] = hash[key]
} else { } else {
_hash[key] = hash[key] _hash[key] = hash[key]
...@@ -540,6 +542,10 @@ var Utils = module.exports = { ...@@ -540,6 +542,10 @@ var Utils = module.exports = {
this.val = val this.val = val
}, },
asIs: function(val) {
this.val = val
},
generateUUID: function() { generateUUID: function() {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) { return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8) var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8)
...@@ -558,6 +564,7 @@ var Utils = module.exports = { ...@@ -558,6 +564,7 @@ var Utils = module.exports = {
Utils.literal.prototype.toString = function() { Utils.literal.prototype.toString = function() {
return this.val return this.val
} }
Utils.asIs.prototype = Utils.literal.prototype
Utils.cast.prototype.toString = function(queryGenerator) { Utils.cast.prototype.toString = function(queryGenerator) {
if (!this.val instanceof Utils.fn && !this.val instanceof Utils.col && !this.val instanceof Utils.literal) { if (!this.val instanceof Utils.fn && !this.val instanceof Utils.col && !this.val instanceof Utils.literal) {
...@@ -580,6 +587,7 @@ Utils.fn.prototype.toString = function(queryGenerator) { ...@@ -580,6 +587,7 @@ Utils.fn.prototype.toString = function(queryGenerator) {
} }
Utils.col.prototype.toString = function (queryGenerator) { Utils.col.prototype.toString = function (queryGenerator) {
if (this.col.indexOf('*') !== -1) return '*'
return queryGenerator.quote(this.col) return queryGenerator.quote(this.col)
} }
......
...@@ -47,12 +47,12 @@ describe(Support.getTestDialectTeaser("Multiple Level Filters"), function() { ...@@ -47,12 +47,12 @@ describe(Support.getTestDialectTeaser("Multiple Level Filters"), function() {
Task.findAll({ Task.findAll({
where: { where: {
'project.user.username': 'leia' 'project.user.username': 'leia'
}/*, },
include: [ include: [
{model: Project, include: [ {model: Project, include: [
User User
]} ]}
]*/ ]
}).done(function(err, tasks){ }).done(function(err, tasks){
expect(err).not.to.be.ok expect(err).not.to.be.ok
...@@ -113,12 +113,12 @@ describe(Support.getTestDialectTeaser("Multiple Level Filters"), function() { ...@@ -113,12 +113,12 @@ describe(Support.getTestDialectTeaser("Multiple Level Filters"), function() {
where: { where: {
'project.user.username': 'leia', 'project.user.username': 'leia',
'project.user.id': 1 'project.user.id': 1
}/*, },
include: [ include: [
{model: Project, include: [ {model: Project, include: [
User User
]} ]}
]*/ ]
}).success(function(tasks){ }).success(function(tasks){
try{ try{
expect(tasks.length).to.be.equal(2); expect(tasks.length).to.be.equal(2);
...@@ -175,12 +175,12 @@ describe(Support.getTestDialectTeaser("Multiple Level Filters"), function() { ...@@ -175,12 +175,12 @@ describe(Support.getTestDialectTeaser("Multiple Level Filters"), function() {
User.findAll({ User.findAll({
where: { where: {
'projects.tasks.title': 'fight empire' 'projects.tasks.title': 'fight empire'
}/*, },
include: [ include: [
{model: Project, include: [ {model: Project, include: [
Task Task
]} ]}
]*/ ]
}).done(function(err, users){ }).done(function(err, users){
try{ try{
expect(users.length).to.be.equal(1); expect(users.length).to.be.equal(1);
...@@ -224,10 +224,10 @@ describe(Support.getTestDialectTeaser("Multiple Level Filters"), function() { ...@@ -224,10 +224,10 @@ describe(Support.getTestDialectTeaser("Multiple Level Filters"), function() {
User.findAll({ User.findAll({
where: { where: {
'projects.title': 'republic' 'projects.title': 'republic'
}/*, },
include: [ include: [
{model: Project} {model: Project}
]*/ ]
}).success(function(users){ }).success(function(users){
try{ try{
expect(users.length).to.be.equal(1); expect(users.length).to.be.equal(1);
......
...@@ -1036,6 +1036,27 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () { ...@@ -1036,6 +1036,27 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
}) })
}) })
}) })
it("should allow strings in min", function(done) {
var self = this
this.User.bulkCreate([{username: 'bbb'}, {username: 'yyy'}]).success(function(){
self.User.min('username').success(function(min){
expect(min).to.equal('bbb')
done()
})
})
})
it("should allow dates in min", function(done){
var self = this
this.User.bulkCreate([{theDate: new Date(2000, 01, 01)}, {theDate: new Date(1990, 01, 01)}]).success(function(){
self.User.min('theDate').success(function(min){
expect(min).to.be.a('Date');
expect(new Date(1990, 01, 01)).to.equalDate(min)
done()
})
})
})
}) })
describe('max', function() { describe('max', function() {
...@@ -1107,6 +1128,27 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () { ...@@ -1107,6 +1128,27 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
}) })
}) })
it("should allow dates in max", function(done) {
var self = this
this.User.bulkCreate([{theDate: new Date(2013, 12, 31)}, {theDate: new Date(2000, 01, 01)}]).success(function(){
self.User.max('theDate').success(function(max){
expect(max).to.be.a('Date');
expect(max).to.equalDate(new Date(2013, 12, 31))
done()
})
})
})
it("should allow strings in max", function(done) {
var self = this
this.User.bulkCreate([{username: 'aaa'}, {username: 'zzz'}]).success(function(){
self.User.max('username').success(function(max){
expect(max).to.equal('zzz')
done()
})
})
})
it('allows sql logging', function(done) { it('allows sql logging', function(done) {
this.UserWithAge.max('age').on('sql', function(sql) { this.UserWithAge.max('age').on('sql', function(sql) {
expect(sql).to.exist expect(sql).to.exist
......
...@@ -6,13 +6,43 @@ var chai = require('chai') ...@@ -6,13 +6,43 @@ var chai = require('chai')
chai.Assertion.includeStack = true chai.Assertion.includeStack = true
describe(Support.getTestDialectTeaser('DataTypes'), function() { describe(Support.getTestDialectTeaser('DataTypes'), function() {
it('should return DECIMAL for the default decimal type', function(done) { it('should return false when comparing DECIMAL and DECIMAL(10,2)', function(done) {
expect(Sequelize.DECIMAL.toString()).to.equal('DECIMAL') expect(Sequelize.DECIMAL).to.not.equal(Sequelize.DECIMAL(10,2))
done() done()
}) })
it('should return DECIMAL(10,2) for the default decimal type with arguments', function(done) { it('DECIMAL(10,2) should be an instance of DECIMAL', function(done) {
expect(Sequelize.DECIMAL(10, 2)).to.equal('DECIMAL(10,2)') expect(Sequelize.DECIMAL(10,2)).to.be.an.instanceof(Sequelize.DECIMAL)
done()
})
it('should return false when comparing FLOAT and FLOAT(11)', function(done) {
expect(Sequelize.FLOAT).to.not.equal(Sequelize.FLOAT(11))
done()
})
it('FLOAT(11) should be an instance of FLOAT', function(done) {
expect(Sequelize.FLOAT(11)).to.be.an.instanceof(Sequelize.FLOAT)
done()
})
it('should return false when comparing STRING and STRING(4096)', function(done) {
expect(Sequelize.STRING).to.not.equal(Sequelize.STRING(4096))
done()
})
it('STRING(4096) should be an instance of STRING', function(done) {
expect(Sequelize.STRING(4096)).to.be.an.instanceof(Sequelize.STRING)
done()
})
it('should return false when comparing BIGINT and BIGINT(11)', function(done) {
expect(Sequelize.BIGINT).to.not.equal(Sequelize.BIGINT(11))
done()
})
it('BIGINT(11) should be an instance of BIGINT', function(done) {
expect(Sequelize.BIGINT(11)).to.be.an.instanceof(Sequelize.BIGINT)
done() done()
}) })
...@@ -61,7 +91,10 @@ describe(Support.getTestDialectTeaser('DataTypes'), function() { ...@@ -61,7 +91,10 @@ describe(Support.getTestDialectTeaser('DataTypes'), function() {
[Sequelize.FLOAT(11, 12).UNSIGNED, 'FLOAT(11,12).UNSIGNED', 'FLOAT(11,12) UNSIGNED'], [Sequelize.FLOAT(11, 12).UNSIGNED, 'FLOAT(11,12).UNSIGNED', 'FLOAT(11,12) UNSIGNED'],
[Sequelize.FLOAT(11, 12).UNSIGNED.ZEROFILL,'FLOAT(11,12).UNSIGNED.ZEROFILL','FLOAT(11,12) UNSIGNED ZEROFILL'], [Sequelize.FLOAT(11, 12).UNSIGNED.ZEROFILL,'FLOAT(11,12).UNSIGNED.ZEROFILL','FLOAT(11,12) UNSIGNED ZEROFILL'],
[Sequelize.FLOAT(11, 12).ZEROFILL,'FLOAT(11,12).ZEROFILL', 'FLOAT(11,12) ZEROFILL'], [Sequelize.FLOAT(11, 12).ZEROFILL,'FLOAT(11,12).ZEROFILL', 'FLOAT(11,12) ZEROFILL'],
[Sequelize.FLOAT(11, 12).ZEROFILL.UNSIGNED,'FLOAT(11,12).ZEROFILL.UNSIGNED', 'FLOAT(11,12) UNSIGNED ZEROFILL'] [Sequelize.FLOAT(11, 12).ZEROFILL.UNSIGNED,'FLOAT(11,12).ZEROFILL.UNSIGNED', 'FLOAT(11,12) UNSIGNED ZEROFILL'],
[Sequelize.DECIMAL, 'DECIMAL', 'DECIMAL'],
[Sequelize.DECIMAL(10,2), 'DECIMAL(10,2)','DECIMAL(10,2)']
] ]
tests.forEach(function(test) { tests.forEach(function(test) {
......
...@@ -23,7 +23,6 @@ describe(Support.getTestDialectTeaser("Hooks"), function () { ...@@ -23,7 +23,6 @@ describe(Support.getTestDialectTeaser("Hooks"), function () {
}, { }, {
hooks: { hooks: {
beforeValidate: function(user, fn) { beforeValidate: function(user, fn) {
console.log("beforeValidate")
user.mood = 'happy' user.mood = 'happy'
fn(null, user) fn(null, user)
}, },
......
...@@ -514,838 +514,8 @@ describe(Support.getTestDialectTeaser("Include"), function () { ...@@ -514,838 +514,8 @@ describe(Support.getTestDialectTeaser("Include"), function () {
}) })
}) })
describe('findAll', function () {
it('should support an include with multiple different association types', function (done) {
var User = this.sequelize.define('User', {})
, Product = this.sequelize.define('Product', {
title: DataTypes.STRING
})
, Tag = this.sequelize.define('Tag', {
name: DataTypes.STRING
})
, Price = this.sequelize.define('Price', {
value: DataTypes.FLOAT
})
, Customer = this.sequelize.define('Customer', {
name: DataTypes.STRING
})
, Group = this.sequelize.define('Group', {
name: DataTypes.STRING
})
, GroupMember = this.sequelize.define('GroupMember', {
})
, Rank = this.sequelize.define('Rank', {
name: DataTypes.STRING,
canInvite: {
type: DataTypes.INTEGER,
defaultValue: 0
},
canRemove: {
type: DataTypes.INTEGER,
defaultValue: 0
}
})
User.hasMany(Product)
Product.belongsTo(User)
Product.hasMany(Tag)
Tag.hasMany(Product)
Product.belongsTo(Tag, {as: 'Category'})
Product.hasMany(Price)
Price.belongsTo(Product)
User.hasMany(GroupMember, {as: 'Memberships'})
GroupMember.belongsTo(User)
GroupMember.belongsTo(Rank)
GroupMember.belongsTo(Group)
Group.hasMany(GroupMember, {as: 'Memberships'})
this.sequelize.sync({force: true}).done(function () {
var count = 4
, i = -1
async.auto({
groups: function(callback) {
Group.bulkCreate([
{name: 'Developers'},
{name: 'Designers'}
]).done(function () {
Group.findAll().done(callback)
})
},
ranks: function(callback) {
Rank.bulkCreate([
{name: 'Admin', canInvite: 1, canRemove: 1},
{name: 'Member', canInvite: 1, canRemove: 0}
]).done(function () {
Rank.findAll().done(callback)
})
},
tags: function(callback) {
Tag.bulkCreate([
{name: 'A'},
{name: 'B'},
{name: 'C'}
]).done(function () {
Tag.findAll().done(callback)
})
},
loop: ['groups', 'ranks', 'tags', function (done, results) {
var groups = results.groups
, ranks = results.ranks
, tags = results.tags
async.whilst(
function () { return i < count; },
function (callback) {
i++
async.auto({
user: function (callback) {
User.create().done(callback)
},
memberships: ['user', function (callback, results) {
GroupMember.bulkCreate([
{UserId: results.user.id, GroupId: groups[0].id, RankId: ranks[0].id},
{UserId: results.user.id, GroupId: groups[1].id, RankId: ranks[1].id}
]).done(callback)
}],
products: function (callback) {
Product.bulkCreate([
{title: 'Chair'},
{title: 'Desk'}
]).done(function () {
Product.findAll().done(callback)
})
},
userProducts: ['user', 'products', function (callback, results) {
results.user.setProducts([
results.products[(i * 2)+0],
results.products[(i * 2)+1]
]).done(callback)
}],
productTags: ['products', function (callback, results) {
var chainer = new Sequelize.Utils.QueryChainer()
chainer.add(results.products[(i * 2) + 0].setTags([
tags[0],
tags[2]
]))
chainer.add(results.products[(i * 2) + 1].setTags([
tags[1]
]))
chainer.add(results.products[(i * 2) + 0].setCategory(tags[1]))
chainer.run().done(callback)
}],
prices: ['products', function (callback, results) {
Price.bulkCreate([
{ProductId: results.products[(i * 2) + 0].id, value: 5},
{ProductId: results.products[(i * 2) + 0].id, value: 10},
{ProductId: results.products[(i * 2) + 1].id, value: 5},
{ProductId: results.products[(i * 2) + 1].id, value: 10},
{ProductId: results.products[(i * 2) + 1].id, value: 15},
{ProductId: results.products[(i * 2) + 1].id, value: 20}
]).done(callback)
}]
}, callback)
},
function (err) {
expect(err).not.to.be.ok
User.findAll({
include: [
{model: GroupMember, as: 'Memberships', include: [
Group,
Rank
]},
{model: Product, include: [
Tag,
{model: Tag, as: 'Category'},
Price
]}
],
order: 'id ASC'
}).done(function (err, users) {
expect(err).not.to.be.ok
users.forEach(function (user, i) {
user.memberships.sort(sortById)
expect(user.memberships.length).to.equal(2)
expect(user.memberships[0].group.name).to.equal('Developers')
expect(user.memberships[0].rank.canRemove).to.equal(1)
expect(user.memberships[1].group.name).to.equal('Designers')
expect(user.memberships[1].rank.canRemove).to.equal(0)
user.products.sort(sortById)
expect(user.products.length).to.equal(2)
expect(user.products[0].tags.length).to.equal(2)
expect(user.products[1].tags.length).to.equal(1)
expect(user.products[0].category).to.be.ok
expect(user.products[1].category).not.to.be.ok
expect(user.products[0].prices.length).to.equal(2)
expect(user.products[1].prices.length).to.equal(4)
done()
})
})
}
)
}]
}, done)
})
})
it('should support many levels of belongsTo', function (done) {
var A = this.sequelize.define('A', {})
, B = this.sequelize.define('B', {})
, C = this.sequelize.define('C', {})
, D = this.sequelize.define('D', {})
, E = this.sequelize.define('E', {})
, F = this.sequelize.define('F', {})
, G = this.sequelize.define('G', {})
, H = this.sequelize.define('H', {})
A.belongsTo(B)
B.belongsTo(C)
C.belongsTo(D)
D.belongsTo(E)
E.belongsTo(F)
F.belongsTo(G)
G.belongsTo(H)
var b, singles = [
B,
C,
D,
E,
F,
G,
H
]
this.sequelize.sync().done(function () {
async.auto({
as: function (callback) {
A.bulkCreate([
{},
{},
{},
{},
{},
{},
{},
{}
]).done(function () {
A.findAll().done(callback)
})
},
singleChain: function (callback) {
var previousInstance
, previousModel
async.eachSeries(singles, function (model, callback, i) {
model.create({}).done(function (err, instance) {
if (previousInstance) {
previousInstance["set"+model.name](instance).done(function () {
previousInstance = instance
callback()
})
} else {
previousInstance = b = instance
callback()
}
})
}, callback)
},
abs: ['as', 'singleChain', function (callback, results) {
var chainer = new Sequelize.Utils.QueryChainer()
results.as.forEach(function (a) {
chainer.add(a.setB(b))
})
chainer.run().done(callback)
}]
}, function () {
A.findAll({
include: [
{model: B, include: [
{model: C, include: [
{model: D, include: [
{model: E, include: [
{model: F, include: [
{model: G, include: [
{model: H}
]}
]}
]}
]}
]}
]}
]
}).done(function (err, as) {
expect(err).not.to.be.ok
expect(as.length).to.be.ok
as.forEach(function (a) {
expect(a.b.c.d.e.f.g.h).to.be.ok
})
done()
})
})
})
})
it('should support ordering with only belongsTo includes', function(done) {
var User = this.sequelize.define('User', {})
, Item = this.sequelize.define('Item', {'test': DataTypes.STRING})
, Order = this.sequelize.define('Order', {'position': DataTypes.INTEGER})
User.belongsTo(Item, {'as': 'itemA', foreignKey: 'itemA_id'})
User.belongsTo(Item, {'as': 'itemB', foreignKey: 'itemB_id'})
User.belongsTo(Order)
this.sequelize.sync().done(function() {
async.auto({
users: function(callback) {
User.bulkCreate([{}, {}, {}]).done(function() {
User.findAll().done(callback)
})
},
items: function(callback) {
Item.bulkCreate([
{'test': 'abc'},
{'test': 'def'},
{'test': 'ghi'},
{'test': 'jkl'}
]).done(function() {
Item.findAll({order: ['id']}).done(callback)
})
},
orders: function(callback) {
Order.bulkCreate([
{'position': 2},
{'position': 3},
{'position': 1}
]).done(function() {
Order.findAll({order: ['id']}).done(callback)
})
},
associate: ['users', 'items', 'orders', function(callback, results) {
var chainer = new Sequelize.Utils.QueryChainer()
var user1 = results.users[0]
var user2 = results.users[1]
var user3 = results.users[2]
var item1 = results.items[0]
var item2 = results.items[1]
var item3 = results.items[2]
var item4 = results.items[3]
var order1 = results.orders[0]
var order2 = results.orders[1]
var order3 = results.orders[2]
chainer.add(user1.setItemA(item1))
chainer.add(user1.setItemB(item2))
chainer.add(user1.setOrder(order3))
chainer.add(user2.setItemA(item3))
chainer.add(user2.setItemB(item4))
chainer.add(user2.setOrder(order2))
chainer.add(user3.setItemA(item1))
chainer.add(user3.setItemB(item4))
chainer.add(user3.setOrder(order1))
chainer.run().done(callback)
}]
}, function() {
User.findAll({
'where': {'itemA.test': 'abc'},
'include': [
{'model': Item, 'as': 'itemA'},
{'model': Item, 'as': 'itemB'},
Order],
'order': ['Order.position']
}).done(function(err, as) {
expect(err).not.to.be.ok
expect(as.length).to.eql(2)
expect(as[0].itemA.test).to.eql('abc')
expect(as[1].itemA.test).to.eql('abc')
expect(as[0].order.position).to.eql(1)
expect(as[1].order.position).to.eql(2)
done()
})
})
})
})
it('should include attributes from through models', function (done) {
var Product = this.sequelize.define('Product', {
title: DataTypes.STRING
})
, Tag = this.sequelize.define('Tag', {
name: DataTypes.STRING
})
, ProductTag = this.sequelize.define('ProductTag', {
priority: DataTypes.INTEGER
})
Product.hasMany(Tag, {through: ProductTag})
Tag.hasMany(Product, {through: ProductTag})
this.sequelize.sync({force: true}).done(function () {
async.auto({
products: function (callback) {
Product.bulkCreate([
{title: 'Chair'},
{title: 'Desk'},
{title: 'Dress'}
]).done(function () {
Product.findAll().done(callback)
})
},
tags: function(callback) {
Tag.bulkCreate([
{name: 'A'},
{name: 'B'},
{name: 'C'}
]).done(function () {
Tag.findAll().done(callback)
})
},
productTags: ['products', 'tags', function (callback, results) {
var chainer = new Sequelize.Utils.QueryChainer()
chainer.add(results.products[0].addTag(results.tags[0], {priority: 1}))
chainer.add(results.products[0].addTag(results.tags[1], {priority: 2}))
chainer.add(results.products[1].addTag(results.tags[1], {priority: 1}))
chainer.add(results.products[2].addTag(results.tags[0], {priority: 3}))
chainer.add(results.products[2].addTag(results.tags[1], {priority: 1}))
chainer.add(results.products[2].addTag(results.tags[2], {priority: 2}))
chainer.run().done(callback)
}]
}, function (err, results) {
expect(err).not.to.be.ok
Product.findAll({
include: [
{model: Tag}
],
order: [
['id', 'ASC'],
['Tags.id', 'ASC']
]
}).done(function (err, products) {
expect(err).not.to.be.ok
expect(products[0].tags[0].productTag.priority).to.equal(1)
expect(products[0].tags[1].productTag.priority).to.equal(2)
expect(products[1].tags[0].productTag.priority).to.equal(1)
expect(products[2].tags[0].productTag.priority).to.equal(3)
expect(products[2].tags[1].productTag.priority).to.equal(1)
expect(products[2].tags[2].productTag.priority).to.equal(2)
done()
})
})
})
})
it('should support a required belongsTo include', function (done) {
var User = this.sequelize.define('User', {})
, Group = this.sequelize.define('Group', {})
User.belongsTo(Group)
this.sequelize.sync({force: true}).done(function () {
async.auto({
groups: function (callback) {
Group.bulkCreate([{}, {}]).done(function () {
Group.findAll().done(callback)
})
},
users: function (callback) {
User.bulkCreate([{}, {}, {}]).done(function () {
User.findAll().done(callback)
})
},
userGroups: ['users', 'groups', function (callback, results) {
results.users[2].setGroup(results.groups[1]).done(callback)
}]
}, function (err, results) {
expect(err).not.to.be.ok
User.findAll({
include: [
{model: Group, required: true}
]
}).done(function (err, users) {
expect(err).not.to.be.ok
expect(users.length).to.equal(1)
expect(users[0].group).to.be.ok
done()
})
})
})
})
it('should be possible to extend the on clause with a where option on a belongsTo include', function (done) {
var User = this.sequelize.define('User', {})
, Group = this.sequelize.define('Group', {
name: DataTypes.STRING
})
User.belongsTo(Group)
this.sequelize.sync({force: true}).done(function () {
async.auto({
groups: function (callback) {
Group.bulkCreate([
{name: 'A'},
{name: 'B'}
]).done(function () {
Group.findAll().done(callback)
})
},
users: function (callback) {
User.bulkCreate([{}, {}]).done(function () {
User.findAll().done(callback)
})
},
userGroups: ['users', 'groups', function (callback, results) {
var chainer = new Sequelize.Utils.QueryChainer()
chainer.add(results.users[0].setGroup(results.groups[1]))
chainer.add(results.users[1].setGroup(results.groups[0]))
chainer.run().done(callback)
}]
}, function (err, results) {
expect(err).not.to.be.ok
User.findAll({
include: [
{model: Group, where: {name: 'A'}}
]
}).done(function (err, users) {
expect(err).not.to.be.ok
expect(users.length).to.equal(1)
expect(users[0].group).to.be.ok
expect(users[0].group.name).to.equal('A')
done()
})
})
})
})
it('should be possible to extend the on clause with a where option on a hasOne include', function (done) {
var User = this.sequelize.define('User', {})
, Project = this.sequelize.define('Project', {
title: DataTypes.STRING
})
User.hasOne(Project, {as: 'LeaderOf'})
this.sequelize.sync({force: true}).done(function () {
async.auto({
projects: function (callback) {
Project.bulkCreate([
{title: 'Alpha'},
{title: 'Beta'}
]).done(function () {
Project.findAll().done(callback)
})
},
users: function (callback) {
User.bulkCreate([{}, {}]).done(function () {
User.findAll().done(callback)
})
},
userProjects: ['users', 'projects', function (callback, results) {
var chainer = new Sequelize.Utils.QueryChainer()
chainer.add(results.users[1].setLeaderOf(results.projects[1]))
chainer.add(results.users[0].setLeaderOf(results.projects[0]))
chainer.run().done(callback)
}]
}, function (err, results) {
expect(err).not.to.be.ok
User.findAll({
include: [
{model: Project, as: 'LeaderOf', where: {title: 'Beta'}}
]
}).done(function (err, users) {
expect(err).not.to.be.ok
expect(users.length).to.equal(1)
expect(users[0].leaderOf).to.be.ok
expect(users[0].leaderOf.title).to.equal('Beta')
done()
})
})
})
})
it('should be possible to extend the on clause with a where option on a hasMany include with a through model', function (done) {
var Product = this.sequelize.define('Product', {
title: DataTypes.STRING
})
, Tag = this.sequelize.define('Tag', {
name: DataTypes.STRING
})
, ProductTag = this.sequelize.define('ProductTag', {
priority: DataTypes.INTEGER
})
Product.hasMany(Tag, {through: ProductTag})
Tag.hasMany(Product, {through: ProductTag})
this.sequelize.sync({force: true}).done(function () {
async.auto({
products: function (callback) {
Product.bulkCreate([
{title: 'Chair'},
{title: 'Desk'},
{title: 'Dress'}
]).done(function () {
Product.findAll().done(callback)
})
},
tags: function(callback) {
Tag.bulkCreate([
{name: 'A'},
{name: 'B'},
{name: 'C'}
]).done(function () {
Tag.findAll().done(callback)
})
},
productTags: ['products', 'tags', function (callback, results) {
var chainer = new Sequelize.Utils.QueryChainer()
chainer.add(results.products[0].addTag(results.tags[0], {priority: 1}))
chainer.add(results.products[0].addTag(results.tags[1], {priority: 2}))
chainer.add(results.products[1].addTag(results.tags[1], {priority: 1}))
chainer.add(results.products[2].addTag(results.tags[0], {priority: 3}))
chainer.add(results.products[2].addTag(results.tags[1], {priority: 1}))
chainer.add(results.products[2].addTag(results.tags[2], {priority: 2}))
chainer.run().done(callback)
}]
}, function (err, results) {
expect(err).not.to.be.ok
Product.findAll({
include: [
{model: Tag, where: {name: 'C'}}
]
}).done(function (err, products) {
expect(err).not.to.be.ok
expect(products.length).to.equal(1)
expect(products[0].tags.length).to.equal(1)
done()
})
})
})
})
it('should be possible to extend the on clause with a where option on nested includes', function (done) {
var User = this.sequelize.define('User', {})
, Product = this.sequelize.define('Product', {
title: DataTypes.STRING
})
, Tag = this.sequelize.define('Tag', {
name: DataTypes.STRING
})
, Price = this.sequelize.define('Price', {
value: DataTypes.FLOAT
})
, Customer = this.sequelize.define('Customer', {
name: DataTypes.STRING
})
, Group = this.sequelize.define('Group', {
name: DataTypes.STRING
})
, GroupMember = this.sequelize.define('GroupMember', {
})
, Rank = this.sequelize.define('Rank', {
name: DataTypes.STRING,
canInvite: {
type: DataTypes.INTEGER,
defaultValue: 0
},
canRemove: {
type: DataTypes.INTEGER,
defaultValue: 0
}
})
User.hasMany(Product)
Product.belongsTo(User)
Product.hasMany(Tag)
Tag.hasMany(Product)
Product.belongsTo(Tag, {as: 'Category'})
Product.hasMany(Price)
Price.belongsTo(Product)
User.hasMany(GroupMember, {as: 'Memberships'})
GroupMember.belongsTo(User)
GroupMember.belongsTo(Rank)
GroupMember.belongsTo(Group)
Group.hasMany(GroupMember, {as: 'Memberships'})
this.sequelize.sync({force: true}).done(function () {
var count = 4
, i = -1
async.auto({
groups: function(callback) {
Group.bulkCreate([
{name: 'Developers'},
{name: 'Designers'}
]).done(function () {
Group.findAll().done(callback)
})
},
ranks: function(callback) {
Rank.bulkCreate([
{name: 'Admin', canInvite: 1, canRemove: 1},
{name: 'Member', canInvite: 1, canRemove: 0}
]).done(function () {
Rank.findAll().done(callback)
})
},
tags: function(callback) {
Tag.bulkCreate([
{name: 'A'},
{name: 'B'},
{name: 'C'}
]).done(function () {
Tag.findAll().done(callback)
})
},
loop: ['groups', 'ranks', 'tags', function (done, results) {
var groups = results.groups
, ranks = results.ranks
, tags = results.tags
async.whilst(
function () { return i < count; },
function (callback) {
i++
async.auto({
user: function (callback) {
User.create().done(callback)
},
memberships: ['user', function (callback, results) {
GroupMember.bulkCreate([
{UserId: results.user.id, GroupId: groups[0].id, RankId: ranks[0].id},
{UserId: results.user.id, GroupId: groups[1].id, RankId: ranks[1].id}
]).done(callback)
}],
products: function (callback) {
Product.bulkCreate([
{title: 'Chair'},
{title: 'Desk'}
]).done(function () {
Product.findAll().done(callback)
})
},
userProducts: ['user', 'products', function (callback, results) {
results.user.setProducts([
results.products[(i * 2)+0],
results.products[(i * 2)+1]
]).done(callback)
}],
productTags: ['products', function (callback, results) {
var chainer = new Sequelize.Utils.QueryChainer()
chainer.add(results.products[(i * 2) + 0].setTags([
tags[0],
tags[2]
]))
chainer.add(results.products[(i * 2) + 1].setTags([
tags[1]
]))
chainer.add(results.products[(i * 2) + 0].setCategory(tags[1]))
chainer.run().done(callback)
}],
prices: ['products', function (callback, results) {
Price.bulkCreate([
{ProductId: results.products[(i * 2) + 0].id, value: 5},
{ProductId: results.products[(i * 2) + 0].id, value: 10},
{ProductId: results.products[(i * 2) + 1].id, value: 5},
{ProductId: results.products[(i * 2) + 1].id, value: 10},
{ProductId: results.products[(i * 2) + 1].id, value: 15},
{ProductId: results.products[(i * 2) + 1].id, value: 20}
]).done(callback)
}]
}, callback)
},
function (err) {
expect(err).not.to.be.ok
User.findAll({
include: [
{model: GroupMember, as: 'Memberships', include: [
Group,
{model: Rank, where: {name: 'Admin'}}
]},
{model: Product, include: [
Tag,
{model: Tag, as: 'Category'},
{model: Price, where: {
value: {
gt: 15
}
}}
]}
],
order: 'id ASC'
}).done(function (err, users) {
expect(err).not.to.be.ok
users.forEach(function (user) {
expect(user.memberships.length).to.equal(1)
expect(user.memberships[0].rank.name).to.equal('Admin')
expect(user.products.length).to.equal(1)
expect(user.products[0].prices.length).to.equal(1)
})
done()
})
}
)
}]
}, done)
})
})
})
describe('findAndCountAll', function () { describe('findAndCountAll', function () {
xit('should include associations to findAndCountAll', function(done) { it('should include associations to findAndCountAll', function(done) {
var User = this.sequelize.define('User', {}) var User = this.sequelize.define('User', {})
, Item = this.sequelize.define('Item', {'test': DataTypes.STRING}) , Item = this.sequelize.define('Item', {'test': DataTypes.STRING})
...@@ -1381,12 +551,17 @@ describe(Support.getTestDialectTeaser("Include"), function () { ...@@ -1381,12 +551,17 @@ describe(Support.getTestDialectTeaser("Include"), function () {
chainer.run().done(callback) chainer.run().done(callback)
}] }]
}, function() { }, function() {
User.findAndCountAll({'where': {'item.test': 'def'}, 'include': [Item]}).done(function(err, result) { User.findAndCountAll({include: [
{model: Item, where: {
test: 'def'
}}
]}).done(function(err, result) {
expect(err).not.to.be.ok expect(err).not.to.be.ok
expect(result.count).to.eql(1) expect(result.count).to.eql(1)
expect(result.rows.length).to.eql(1) expect(result.rows.length).to.eql(1)
expect(result.rows[0].item.test).to.eql('def') expect(result.rows[0].item.test).to.eql('def')
done()
}) })
}) })
}) })
......
/* jshint camelcase: false */
/* jshint expr: true */
var chai = require('chai')
, Sequelize = require('../../index')
, expect = chai.expect
, Support = require(__dirname + '/../support')
, DataTypes = require(__dirname + "/../../lib/data-types")
, dialect = Support.getTestDialect()
, config = require(__dirname + "/../config/config")
, sinon = require('sinon')
, datetime = require('chai-datetime')
, _ = require('lodash')
, moment = require('moment')
, async = require('async')
chai.use(datetime)
chai.Assertion.includeStack = true
var sortById = function(a, b) {
return a.id < b.id ? -1 : 1
}
describe(Support.getTestDialectTeaser("Include"), function () {
describe('findAll', function () {
beforeEach(function () {
this.fixtureA = function(done) {
var User = this.sequelize.define('User', {})
, Company = this.sequelize.define('Company', {
name: DataTypes.STRING
})
, Product = this.sequelize.define('Product', {
title: DataTypes.STRING
})
, Tag = this.sequelize.define('Tag', {
name: DataTypes.STRING
})
, Price = this.sequelize.define('Price', {
value: DataTypes.FLOAT
})
, Customer = this.sequelize.define('Customer', {
name: DataTypes.STRING
})
, Group = this.sequelize.define('Group', {
name: DataTypes.STRING
})
, GroupMember = this.sequelize.define('GroupMember', {
})
, Rank = this.sequelize.define('Rank', {
name: DataTypes.STRING,
canInvite: {
type: DataTypes.INTEGER,
defaultValue: 0
},
canRemove: {
type: DataTypes.INTEGER,
defaultValue: 0
},
canPost: {
type: DataTypes.INTEGER,
defaultValue: 0
}
})
this.models = {
User: User,
Company: Company,
Product: Product,
Tag: Tag,
Price: Price,
Customer: Customer,
Group: Group,
GroupMember: GroupMember,
Rank: Rank
}
User.hasMany(Product)
Product.belongsTo(User)
Product.hasMany(Tag)
Tag.hasMany(Product)
Product.belongsTo(Tag, {as: 'Category'})
Product.belongsTo(Company)
Product.hasMany(Price)
Price.belongsTo(Product)
User.hasMany(GroupMember, {as: 'Memberships'})
GroupMember.belongsTo(User)
GroupMember.belongsTo(Rank)
GroupMember.belongsTo(Group)
Group.hasMany(GroupMember, {as: 'Memberships'})
this.sequelize.sync({force: true}).done(function () {
var count = 4
, i = -1
async.auto({
groups: function(callback) {
Group.bulkCreate([
{name: 'Developers'},
{name: 'Designers'},
{name: 'Managers'}
]).done(function () {
Group.findAll().done(callback)
})
},
companies: function(callback) {
Company.bulkCreate([
{name: 'Sequelize'},
{name: 'Coca Cola'},
{name: 'Bonanza'},
{name: 'NYSE'},
{name: 'Coshopr'}
]).done(function (err) {
if (err) return callback(err);
Company.findAll().done(callback)
})
},
ranks: function(callback) {
Rank.bulkCreate([
{name: 'Admin', canInvite: 1, canRemove: 1, canPost: 1},
{name: 'Trustee', canInvite: 1, canRemove: 0, canPost: 1},
{name: 'Member', canInvite: 1, canRemove: 0, canPost: 0}
]).done(function (err) {
Rank.findAll().done(callback)
})
},
tags: function(callback) {
Tag.bulkCreate([
{name: 'A'},
{name: 'B'},
{name: 'C'},
{name: 'D'},
{name: 'E'}
]).done(function () {
Tag.findAll().done(callback)
})
},
loop: ['groups', 'ranks', 'tags', 'companies', function (done, results) {
var groups = results.groups
, ranks = results.ranks
, tags = results.tags
, companies = results.companies
async.whilst(
function () { return i < count; },
function (callback) {
i++
async.auto({
user: function (callback) {
User.create().done(callback)
},
memberships: ['user', function (callback, results) {
var groupMembers = [
{UserId: results.user.id, GroupId: groups[0].id, RankId: ranks[0].id},
{UserId: results.user.id, GroupId: groups[1].id, RankId: ranks[2].id}
]
if (i < 3) {
groupMembers.push({UserId: results.user.id, GroupId: groups[2].id, RankId: ranks[1].id})
}
GroupMember.bulkCreate(groupMembers).done(callback)
}],
products: function (callback) {
Product.bulkCreate([
{title: 'Chair'},
{title: 'Desk'},
{title: 'Bed'},
{title: 'Pen'},
{title: 'Monitor'}
]).done(function (err) {
if (err) return callback(err);
Product.findAll().done(callback)
})
},
userProducts: ['user', 'products', function (callback, results) {
results.user.setProducts([
results.products[(i * 5)+0],
results.products[(i * 5)+1],
results.products[(i * 5)+3]
]).done(callback)
}],
productTags: ['products', function (callback, results) {
var chainer = new Sequelize.Utils.QueryChainer()
chainer.add(results.products[(i * 5) + 0].setTags([
tags[0],
tags[2]
]))
chainer.add(results.products[(i * 5) + 1].setTags([
tags[1]
]))
chainer.add(results.products[(i * 5) + 0].setCategory(tags[1]))
chainer.add(results.products[(i * 5) + 2].setTags([
tags[0]
]))
chainer.add(results.products[(i * 5) + 3].setTags([
tags[0]
]))
chainer.run().done(callback)
}],
companies: ['products', function (callback, results) {
var chainer = new Sequelize.Utils.QueryChainer()
results.products[(i * 5)+0].setCompany(companies[4])
results.products[(i * 5)+1].setCompany(companies[3])
results.products[(i * 5)+2].setCompany(companies[2])
results.products[(i * 5)+3].setCompany(companies[1])
results.products[(i * 5)+4].setCompany(companies[0])
chainer.run().done(callback)
}],
prices: ['products', function (callback, results) {
Price.bulkCreate([
{ProductId: results.products[(i * 5) + 0].id, value: 5},
{ProductId: results.products[(i * 5) + 0].id, value: 10},
{ProductId: results.products[(i * 5) + 1].id, value: 5},
{ProductId: results.products[(i * 5) + 1].id, value: 10},
{ProductId: results.products[(i * 5) + 1].id, value: 15},
{ProductId: results.products[(i * 5) + 1].id, value: 20},
{ProductId: results.products[(i * 5) + 2].id, value: 20},
{ProductId: results.products[(i * 5) + 3].id, value: 20}
]).done(callback)
}]
}, callback)
},
function (err) {
expect(err).not.to.be.ok
done()
}
)
}]
}, done.bind(this))
})
}
})
it('should support an include with multiple different association types', function (done) {
var User = this.sequelize.define('User', {})
, Product = this.sequelize.define('Product', {
title: DataTypes.STRING
})
, Tag = this.sequelize.define('Tag', {
name: DataTypes.STRING
})
, Price = this.sequelize.define('Price', {
value: DataTypes.FLOAT
})
, Customer = this.sequelize.define('Customer', {
name: DataTypes.STRING
})
, Group = this.sequelize.define('Group', {
name: DataTypes.STRING
})
, GroupMember = this.sequelize.define('GroupMember', {
})
, Rank = this.sequelize.define('Rank', {
name: DataTypes.STRING,
canInvite: {
type: DataTypes.INTEGER,
defaultValue: 0
},
canRemove: {
type: DataTypes.INTEGER,
defaultValue: 0
}
})
User.hasMany(Product)
Product.belongsTo(User)
Product.hasMany(Tag)
Tag.hasMany(Product)
Product.belongsTo(Tag, {as: 'Category'})
Product.hasMany(Price)
Price.belongsTo(Product)
User.hasMany(GroupMember, {as: 'Memberships'})
GroupMember.belongsTo(User)
GroupMember.belongsTo(Rank)
GroupMember.belongsTo(Group)
Group.hasMany(GroupMember, {as: 'Memberships'})
this.sequelize.sync({force: true}).done(function () {
var count = 4
, i = -1
async.auto({
groups: function(callback) {
Group.bulkCreate([
{name: 'Developers'},
{name: 'Designers'}
]).done(function () {
Group.findAll().done(callback)
})
},
ranks: function(callback) {
Rank.bulkCreate([
{name: 'Admin', canInvite: 1, canRemove: 1},
{name: 'Member', canInvite: 1, canRemove: 0}
]).done(function () {
Rank.findAll().done(callback)
})
},
tags: function(callback) {
Tag.bulkCreate([
{name: 'A'},
{name: 'B'},
{name: 'C'}
]).done(function () {
Tag.findAll().done(callback)
})
},
loop: ['groups', 'ranks', 'tags', function (done, results) {
var groups = results.groups
, ranks = results.ranks
, tags = results.tags
async.whilst(
function () { return i < count; },
function (callback) {
i++
async.auto({
user: function (callback) {
User.create().done(callback)
},
memberships: ['user', function (callback, results) {
GroupMember.bulkCreate([
{UserId: results.user.id, GroupId: groups[0].id, RankId: ranks[0].id},
{UserId: results.user.id, GroupId: groups[1].id, RankId: ranks[1].id}
]).done(callback)
}],
products: function (callback) {
Product.bulkCreate([
{title: 'Chair'},
{title: 'Desk'}
]).done(function () {
Product.findAll().done(callback)
})
},
userProducts: ['user', 'products', function (callback, results) {
results.user.setProducts([
results.products[(i * 2)+0],
results.products[(i * 2)+1]
]).done(callback)
}],
productTags: ['products', function (callback, results) {
var chainer = new Sequelize.Utils.QueryChainer()
chainer.add(results.products[(i * 2) + 0].setTags([
tags[0],
tags[2]
]))
chainer.add(results.products[(i * 2) + 1].setTags([
tags[1]
]))
chainer.add(results.products[(i * 2) + 0].setCategory(tags[1]))
chainer.run().done(callback)
}],
prices: ['products', function (callback, results) {
Price.bulkCreate([
{ProductId: results.products[(i * 2) + 0].id, value: 5},
{ProductId: results.products[(i * 2) + 0].id, value: 10},
{ProductId: results.products[(i * 2) + 1].id, value: 5},
{ProductId: results.products[(i * 2) + 1].id, value: 10},
{ProductId: results.products[(i * 2) + 1].id, value: 15},
{ProductId: results.products[(i * 2) + 1].id, value: 20}
]).done(callback)
}]
}, callback)
},
function (err) {
expect(err).not.to.be.ok
User.findAll({
include: [
{model: GroupMember, as: 'Memberships', include: [
Group,
Rank
]},
{model: Product, include: [
Tag,
{model: Tag, as: 'Category'},
Price
]}
],
order: [
['Users.id', 'ASC']
]
}).done(function (err, users) {
expect(err).not.to.be.ok
users.forEach(function (user, i) {
user.memberships.sort(sortById)
expect(user.memberships.length).to.equal(2)
expect(user.memberships[0].group.name).to.equal('Developers')
expect(user.memberships[0].rank.canRemove).to.equal(1)
expect(user.memberships[1].group.name).to.equal('Designers')
expect(user.memberships[1].rank.canRemove).to.equal(0)
user.products.sort(sortById)
expect(user.products.length).to.equal(2)
expect(user.products[0].tags.length).to.equal(2)
expect(user.products[1].tags.length).to.equal(1)
expect(user.products[0].category).to.be.ok
expect(user.products[1].category).not.to.be.ok
expect(user.products[0].prices.length).to.equal(2)
expect(user.products[1].prices.length).to.equal(4)
done()
})
})
}
)
}]
}, done)
})
})
it('should support many levels of belongsTo', function (done) {
var A = this.sequelize.define('A', {})
, B = this.sequelize.define('B', {})
, C = this.sequelize.define('C', {})
, D = this.sequelize.define('D', {})
, E = this.sequelize.define('E', {})
, F = this.sequelize.define('F', {})
, G = this.sequelize.define('G', {})
, H = this.sequelize.define('H', {})
A.belongsTo(B)
B.belongsTo(C)
C.belongsTo(D)
D.belongsTo(E)
E.belongsTo(F)
F.belongsTo(G)
G.belongsTo(H)
var b, singles = [
B,
C,
D,
E,
F,
G,
H
]
this.sequelize.sync().done(function () {
async.auto({
as: function (callback) {
A.bulkCreate([
{},
{},
{},
{},
{},
{},
{},
{}
]).done(function () {
A.findAll().done(callback)
})
},
singleChain: function (callback) {
var previousInstance
, previousModel
async.eachSeries(singles, function (model, callback, i) {
model.create({}).done(function (err, instance) {
if (previousInstance) {
previousInstance["set"+model.name](instance).done(function () {
previousInstance = instance
callback()
})
} else {
previousInstance = b = instance
callback()
}
})
}, callback)
},
abs: ['as', 'singleChain', function (callback, results) {
var chainer = new Sequelize.Utils.QueryChainer()
results.as.forEach(function (a) {
chainer.add(a.setB(b))
})
chainer.run().done(callback)
}]
}, function () {
A.findAll({
include: [
{model: B, include: [
{model: C, include: [
{model: D, include: [
{model: E, include: [
{model: F, include: [
{model: G, include: [
{model: H}
]}
]}
]}
]}
]}
]}
]
}).done(function (err, as) {
expect(err).not.to.be.ok
expect(as.length).to.be.ok
as.forEach(function (a) {
expect(a.b.c.d.e.f.g.h).to.be.ok
})
done()
})
})
})
})
it('should support ordering with only belongsTo includes', function(done) {
var User = this.sequelize.define('User', {})
, Item = this.sequelize.define('Item', {'test': DataTypes.STRING})
, Order = this.sequelize.define('Order', {'position': DataTypes.INTEGER})
User.belongsTo(Item, {'as': 'itemA', foreignKey: 'itemA_id'})
User.belongsTo(Item, {'as': 'itemB', foreignKey: 'itemB_id'})
User.belongsTo(Order)
this.sequelize.sync().done(function() {
async.auto({
users: function(callback) {
User.bulkCreate([{}, {}, {}]).done(function() {
User.findAll().done(callback)
})
},
items: function(callback) {
Item.bulkCreate([
{'test': 'abc'},
{'test': 'def'},
{'test': 'ghi'},
{'test': 'jkl'}
]).done(function() {
Item.findAll({order: ['id']}).done(callback)
})
},
orders: function(callback) {
Order.bulkCreate([
{'position': 2},
{'position': 3},
{'position': 1}
]).done(function() {
Order.findAll({order: ['id']}).done(callback)
})
},
associate: ['users', 'items', 'orders', function(callback, results) {
var chainer = new Sequelize.Utils.QueryChainer()
var user1 = results.users[0]
var user2 = results.users[1]
var user3 = results.users[2]
var item1 = results.items[0]
var item2 = results.items[1]
var item3 = results.items[2]
var item4 = results.items[3]
var order1 = results.orders[0]
var order2 = results.orders[1]
var order3 = results.orders[2]
chainer.add(user1.setItemA(item1))
chainer.add(user1.setItemB(item2))
chainer.add(user1.setOrder(order3))
chainer.add(user2.setItemA(item3))
chainer.add(user2.setItemB(item4))
chainer.add(user2.setOrder(order2))
chainer.add(user3.setItemA(item1))
chainer.add(user3.setItemB(item4))
chainer.add(user3.setOrder(order1))
chainer.run().done(callback)
}]
}, function() {
User.findAll({
'where': {'itemA.test': 'abc'},
'include': [
{'model': Item, 'as': 'itemA'},
{'model': Item, 'as': 'itemB'},
Order],
'order': ['Order.position']
}).done(function(err, as) {
expect(err).not.to.be.ok
expect(as.length).to.eql(2)
expect(as[0].itemA.test).to.eql('abc')
expect(as[1].itemA.test).to.eql('abc')
expect(as[0].order.position).to.eql(1)
expect(as[1].order.position).to.eql(2)
done()
})
})
})
})
it('should include attributes from through models', function (done) {
var Product = this.sequelize.define('Product', {
title: DataTypes.STRING
})
, Tag = this.sequelize.define('Tag', {
name: DataTypes.STRING
})
, ProductTag = this.sequelize.define('ProductTag', {
priority: DataTypes.INTEGER
})
Product.hasMany(Tag, {through: ProductTag})
Tag.hasMany(Product, {through: ProductTag})
this.sequelize.sync({force: true}).done(function () {
async.auto({
products: function (callback) {
Product.bulkCreate([
{title: 'Chair'},
{title: 'Desk'},
{title: 'Dress'}
]).done(function () {
Product.findAll().done(callback)
})
},
tags: function(callback) {
Tag.bulkCreate([
{name: 'A'},
{name: 'B'},
{name: 'C'}
]).done(function () {
Tag.findAll().done(callback)
})
},
productTags: ['products', 'tags', function (callback, results) {
var chainer = new Sequelize.Utils.QueryChainer()
chainer.add(results.products[0].addTag(results.tags[0], {priority: 1}))
chainer.add(results.products[0].addTag(results.tags[1], {priority: 2}))
chainer.add(results.products[1].addTag(results.tags[1], {priority: 1}))
chainer.add(results.products[2].addTag(results.tags[0], {priority: 3}))
chainer.add(results.products[2].addTag(results.tags[1], {priority: 1}))
chainer.add(results.products[2].addTag(results.tags[2], {priority: 2}))
chainer.run().done(callback)
}]
}, function (err, results) {
expect(err).not.to.be.ok
Product.findAll({
include: [
{model: Tag}
],
order: [
['id', 'ASC'],
['Tags.id', 'ASC']
]
}).done(function (err, products) {
expect(err).not.to.be.ok
expect(products[0].tags[0].productTag.priority).to.equal(1)
expect(products[0].tags[1].productTag.priority).to.equal(2)
expect(products[1].tags[0].productTag.priority).to.equal(1)
expect(products[2].tags[0].productTag.priority).to.equal(3)
expect(products[2].tags[1].productTag.priority).to.equal(1)
expect(products[2].tags[2].productTag.priority).to.equal(2)
done()
})
})
})
})
it('should support a required belongsTo include', function (done) {
var User = this.sequelize.define('User', {})
, Group = this.sequelize.define('Group', {})
User.belongsTo(Group)
this.sequelize.sync({force: true}).done(function () {
async.auto({
groups: function (callback) {
Group.bulkCreate([{}, {}]).done(function () {
Group.findAll().done(callback)
})
},
users: function (callback) {
User.bulkCreate([{}, {}, {}]).done(function () {
User.findAll().done(callback)
})
},
userGroups: ['users', 'groups', function (callback, results) {
results.users[2].setGroup(results.groups[1]).done(callback)
}]
}, function (err, results) {
expect(err).not.to.be.ok
User.findAll({
include: [
{model: Group, required: true}
]
}).done(function (err, users) {
expect(err).not.to.be.ok
expect(users.length).to.equal(1)
expect(users[0].group).to.be.ok
done()
})
})
})
})
it('should be possible to extend the on clause with a where option on a belongsTo include', function (done) {
var User = this.sequelize.define('User', {})
, Group = this.sequelize.define('Group', {
name: DataTypes.STRING
})
User.belongsTo(Group)
this.sequelize.sync({force: true}).done(function () {
async.auto({
groups: function (callback) {
Group.bulkCreate([
{name: 'A'},
{name: 'B'}
]).done(function () {
Group.findAll().done(callback)
})
},
users: function (callback) {
User.bulkCreate([{}, {}]).done(function () {
User.findAll().done(callback)
})
},
userGroups: ['users', 'groups', function (callback, results) {
var chainer = new Sequelize.Utils.QueryChainer()
chainer.add(results.users[0].setGroup(results.groups[1]))
chainer.add(results.users[1].setGroup(results.groups[0]))
chainer.run().done(callback)
}]
}, function (err, results) {
expect(err).not.to.be.ok
User.findAll({
include: [
{model: Group, where: {name: 'A'}}
]
}).done(function (err, users) {
expect(err).not.to.be.ok
expect(users.length).to.equal(1)
expect(users[0].group).to.be.ok
expect(users[0].group.name).to.equal('A')
done()
})
})
})
})
it('should be possible to extend the on clause with a where option on a hasOne include', function (done) {
var User = this.sequelize.define('User', {})
, Project = this.sequelize.define('Project', {
title: DataTypes.STRING
})
User.hasOne(Project, {as: 'LeaderOf'})
this.sequelize.sync({force: true}).done(function () {
async.auto({
projects: function (callback) {
Project.bulkCreate([
{title: 'Alpha'},
{title: 'Beta'}
]).done(function () {
Project.findAll().done(callback)
})
},
users: function (callback) {
User.bulkCreate([{}, {}]).done(function () {
User.findAll().done(callback)
})
},
userProjects: ['users', 'projects', function (callback, results) {
var chainer = new Sequelize.Utils.QueryChainer()
chainer.add(results.users[1].setLeaderOf(results.projects[1]))
chainer.add(results.users[0].setLeaderOf(results.projects[0]))
chainer.run().done(callback)
}]
}, function (err, results) {
expect(err).not.to.be.ok
User.findAll({
include: [
{model: Project, as: 'LeaderOf', where: {title: 'Beta'}}
]
}).done(function (err, users) {
expect(err).not.to.be.ok
expect(users.length).to.equal(1)
expect(users[0].leaderOf).to.be.ok
expect(users[0].leaderOf.title).to.equal('Beta')
done()
})
})
})
})
it('should be possible to extend the on clause with a where option on a hasMany include with a through model', function (done) {
var Product = this.sequelize.define('Product', {
title: DataTypes.STRING
})
, Tag = this.sequelize.define('Tag', {
name: DataTypes.STRING
})
, ProductTag = this.sequelize.define('ProductTag', {
priority: DataTypes.INTEGER
})
Product.hasMany(Tag, {through: ProductTag})
Tag.hasMany(Product, {through: ProductTag})
this.sequelize.sync({force: true}).done(function () {
async.auto({
products: function (callback) {
Product.bulkCreate([
{title: 'Chair'},
{title: 'Desk'},
{title: 'Dress'}
]).done(function () {
Product.findAll().done(callback)
})
},
tags: function(callback) {
Tag.bulkCreate([
{name: 'A'},
{name: 'B'},
{name: 'C'}
]).done(function () {
Tag.findAll().done(callback)
})
},
productTags: ['products', 'tags', function (callback, results) {
var chainer = new Sequelize.Utils.QueryChainer()
chainer.add(results.products[0].addTag(results.tags[0], {priority: 1}))
chainer.add(results.products[0].addTag(results.tags[1], {priority: 2}))
chainer.add(results.products[1].addTag(results.tags[1], {priority: 1}))
chainer.add(results.products[2].addTag(results.tags[0], {priority: 3}))
chainer.add(results.products[2].addTag(results.tags[1], {priority: 1}))
chainer.add(results.products[2].addTag(results.tags[2], {priority: 2}))
chainer.run().done(callback)
}]
}, function (err, results) {
expect(err).not.to.be.ok
Product.findAll({
include: [
{model: Tag, where: {name: 'C'}}
]
}).done(function (err, products) {
expect(err).not.to.be.ok
expect(products.length).to.equal(1)
expect(products[0].tags.length).to.equal(1)
done()
})
})
})
})
it('should be possible to extend the on clause with a where option on nested includes', function (done) {
var User = this.sequelize.define('User', {})
, Product = this.sequelize.define('Product', {
title: DataTypes.STRING
})
, Tag = this.sequelize.define('Tag', {
name: DataTypes.STRING
})
, Price = this.sequelize.define('Price', {
value: DataTypes.FLOAT
})
, Customer = this.sequelize.define('Customer', {
name: DataTypes.STRING
})
, Group = this.sequelize.define('Group', {
name: DataTypes.STRING
})
, GroupMember = this.sequelize.define('GroupMember', {
})
, Rank = this.sequelize.define('Rank', {
name: DataTypes.STRING,
canInvite: {
type: DataTypes.INTEGER,
defaultValue: 0
},
canRemove: {
type: DataTypes.INTEGER,
defaultValue: 0
}
})
User.hasMany(Product)
Product.belongsTo(User)
Product.hasMany(Tag)
Tag.hasMany(Product)
Product.belongsTo(Tag, {as: 'Category'})
Product.hasMany(Price)
Price.belongsTo(Product)
User.hasMany(GroupMember, {as: 'Memberships'})
GroupMember.belongsTo(User)
GroupMember.belongsTo(Rank)
GroupMember.belongsTo(Group)
Group.hasMany(GroupMember, {as: 'Memberships'})
this.sequelize.sync({force: true}).done(function () {
var count = 4
, i = -1
async.auto({
groups: function(callback) {
Group.bulkCreate([
{name: 'Developers'},
{name: 'Designers'}
]).done(function () {
Group.findAll().done(callback)
})
},
ranks: function(callback) {
Rank.bulkCreate([
{name: 'Admin', canInvite: 1, canRemove: 1},
{name: 'Member', canInvite: 1, canRemove: 0}
]).done(function () {
Rank.findAll().done(callback)
})
},
tags: function(callback) {
Tag.bulkCreate([
{name: 'A'},
{name: 'B'},
{name: 'C'}
]).done(function () {
Tag.findAll().done(callback)
})
},
loop: ['groups', 'ranks', 'tags', function (done, results) {
var groups = results.groups
, ranks = results.ranks
, tags = results.tags
async.whilst(
function () { return i < count; },
function (callback) {
i++
async.auto({
user: function (callback) {
User.create().done(callback)
},
memberships: ['user', function (callback, results) {
GroupMember.bulkCreate([
{UserId: results.user.id, GroupId: groups[0].id, RankId: ranks[0].id},
{UserId: results.user.id, GroupId: groups[1].id, RankId: ranks[1].id}
]).done(callback)
}],
products: function (callback) {
Product.bulkCreate([
{title: 'Chair'},
{title: 'Desk'}
]).done(function () {
Product.findAll().done(callback)
})
},
userProducts: ['user', 'products', function (callback, results) {
results.user.setProducts([
results.products[(i * 2)+0],
results.products[(i * 2)+1]
]).done(callback)
}],
productTags: ['products', function (callback, results) {
var chainer = new Sequelize.Utils.QueryChainer()
chainer.add(results.products[(i * 2) + 0].setTags([
tags[0],
tags[2]
]))
chainer.add(results.products[(i * 2) + 1].setTags([
tags[1]
]))
chainer.add(results.products[(i * 2) + 0].setCategory(tags[1]))
chainer.run().done(callback)
}],
prices: ['products', function (callback, results) {
Price.bulkCreate([
{ProductId: results.products[(i * 2) + 0].id, value: 5},
{ProductId: results.products[(i * 2) + 0].id, value: 10},
{ProductId: results.products[(i * 2) + 1].id, value: 5},
{ProductId: results.products[(i * 2) + 1].id, value: 10},
{ProductId: results.products[(i * 2) + 1].id, value: 15},
{ProductId: results.products[(i * 2) + 1].id, value: 20}
]).done(callback)
}]
}, callback)
},
function (err) {
expect(err).not.to.be.ok
User.findAll({
include: [
{model: GroupMember, as: 'Memberships', include: [
Group,
{model: Rank, where: {name: 'Admin'}}
]},
{model: Product, include: [
Tag,
{model: Tag, as: 'Category'},
{model: Price, where: {
value: {
gt: 15
}
}}
]}
],
order: 'id ASC'
}).done(function (err, users) {
expect(err).not.to.be.ok
users.forEach(function (user) {
expect(user.memberships.length).to.equal(1)
expect(user.memberships[0].rank.name).to.equal('Admin')
expect(user.products.length).to.equal(1)
expect(user.products[0].prices.length).to.equal(1)
})
done()
})
}
)
}]
}, done)
})
})
it('should be possible use limit and a where with a belongsTo include', function (done) {
var User = this.sequelize.define('User', {})
, Group = this.sequelize.define('Group', {
name: DataTypes.STRING
})
User.belongsTo(Group)
this.sequelize.sync({force: true}).done(function () {
async.auto({
groups: function (callback) {
Group.bulkCreate([
{name: 'A'},
{name: 'B'},
]).done(function () {
Group.findAll().done(callback)
})
},
users: function (callback) {
User.bulkCreate([{}, {}, {}, {}]).done(function () {
User.findAll().done(callback)
})
},
userGroups: ['users', 'groups', function (callback, results) {
var chainer = new Sequelize.Utils.QueryChainer()
chainer.add(results.users[0].setGroup(results.groups[0]))
chainer.add(results.users[1].setGroup(results.groups[0]))
chainer.add(results.users[2].setGroup(results.groups[0]))
chainer.add(results.users[3].setGroup(results.groups[1]))
chainer.run().done(callback)
}]
}, function (err, results) {
expect(err).not.to.be.ok
User.findAll({
include: [
{model: Group, where: {name: 'A'}}
],
limit: 2
}).done(function (err, users) {
expect(err).not.to.be.ok
expect(users.length).to.equal(2)
users.forEach(function (user) {
expect(user.group.name).to.equal('A')
})
done()
})
})
})
})
it('should be possible use limit and a where on a belongsTo with additional hasMany includes', function (done) {
var self = this
this.fixtureA(function () {
self.models.Product.findAll({
include: [
{model: self.models.Company, where: {name: 'NYSE'}},
{model: self.models.Tag},
{model: self.models.Price}
],
limit: 3,
order: 'id ASC'
}).done(function (err, products) {
expect(err).not.to.be.ok
expect(products.length).to.equal(3)
products.forEach(function (product) {
expect(product.tags.length).to.be.ok
expect(product.prices.length).to.be.ok
})
done()
})
})
})
it('should be possible use limit and a where on a hasMany with additional includes', function (done) {
var self = this
this.fixtureA(function () {
self.models.Product.findAll({
include: [
{model: self.models.Company},
{model: self.models.Tag},
{model: self.models.Price, where: {
value: {gt: 5}
}}
],
limit: 6,
order: 'id ASC'
}).done(function (err, products) {
expect(err).not.to.be.ok
expect(products.length).to.equal(6)
products.forEach(function (product) {
expect(product.tags.length).to.be.ok
expect(product.prices.length).to.be.ok
product.prices.forEach(function (price) {
expect(price.value).to.be.above(5)
})
})
done()
})
})
})
it('should be possible use limit and a where on a hasMany with a through model with additional includes', function (done) {
var self = this
this.fixtureA(function () {
self.models.Product.findAll({
include: [
{model: self.models.Company},
{model: self.models.Tag, where: {name: ['A', 'B','C']}},
{model: self.models.Price}
],
limit: 10,
order: 'id ASC'
}).done(function (err, products) {
expect(err).not.to.be.ok
expect(products.length).to.equal(10)
products.forEach(function (product) {
expect(product.tags.length).to.be.ok
expect(product.prices.length).to.be.ok
product.tags.forEach(function (tag) {
expect(['A', 'B', 'C']).to.include(tag.name)
})
})
done()
})
})
})
})
})
\ No newline at end of file
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!