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

Commit a54e1c78 by Mick Hansen

Merge branch 'master' into milestones/2.0.0

2 parents 3574404d df42db65
...@@ -5,6 +5,7 @@ Notice: All 1.7.x changes are present in 2.0.x aswell ...@@ -5,6 +5,7 @@ Notice: All 1.7.x changes are present in 2.0.x aswell
- fixes various issues with limit and includes [#1322](https://github.com/sequelize/sequelize/pull/1322) - fixes various issues with limit and includes [#1322](https://github.com/sequelize/sequelize/pull/1322)
- fixes issues with migrations/queryInterface createTable and enums - fixes issues with migrations/queryInterface createTable and enums
- migration/queryInterface.addIndex() no longer fals on reserved keywords like 'from' - migration/queryInterface.addIndex() no longer fals on reserved keywords like 'from'
- bulkCreate now supports a `ignoreDuplicates` option for MySQL, SQLite and MariaDB that will use `INSERT IGNORE`
#### Backwards compatability changes #### Backwards compatability changes
- find/findAll will now always return primary keys regardless of `attributes` settings. (Motivation was to fix various issues with eager loading) - find/findAll will now always return primary keys regardless of `attributes` settings. (Motivation was to fix various issues with eager loading)
......
...@@ -19,7 +19,7 @@ module.exports = (function() { ...@@ -19,7 +19,7 @@ module.exports = (function() {
this.sequelize = source.daoFactoryManager.sequelize this.sequelize = source.daoFactoryManager.sequelize
this.through = options.through this.through = options.through
this.isMultiAssociation = true this.isMultiAssociation = true
this.isSelfAssociation = (this.source.tableName === this.target.tableName) this.isSelfAssociation = this.source === this.target
this.doubleLinked = false this.doubleLinked = false
this.combinedTableName = Utils.combineTableNames( this.combinedTableName = Utils.combineTableNames(
this.source.tableName, this.source.tableName,
...@@ -136,7 +136,7 @@ module.exports = (function() { ...@@ -136,7 +136,7 @@ module.exports = (function() {
// is there already a single sided association between the source and the target? // is there already a single sided association between the source and the target?
// or is the association on the model itself? // or is the association on the model itself?
if ((this.isSelfAssociation && this.through) || doubleLinked) { if ((this.isSelfAssociation && Object(this.through) === this.through) || doubleLinked) {
// remove the obsolete association identifier from the source // remove the obsolete association identifier from the source
if (this.isSelfAssociation) { if (this.isSelfAssociation) {
this.foreignIdentifier = Utils._.underscoredIf((this.options.as || this.target.tableName) + 'Id', this.options.underscored) this.foreignIdentifier = Utils._.underscoredIf((this.options.as || this.target.tableName) + 'Id', this.options.underscored)
......
...@@ -791,7 +791,8 @@ module.exports = (function() { ...@@ -791,7 +791,8 @@ module.exports = (function() {
options = Utils._.extend({ options = Utils._.extend({
validate: false, validate: false,
hooks: false hooks: false,
ignoreDuplicates: false
}, options || {}) }, options || {})
if (fieldsOrOptions instanceof Array) { if (fieldsOrOptions instanceof Array) {
...@@ -801,6 +802,12 @@ module.exports = (function() { ...@@ -801,6 +802,12 @@ module.exports = (function() {
options = Utils._.extend(options, fieldsOrOptions) options = Utils._.extend(options, fieldsOrOptions)
} }
if(this.daoFactoryManager.sequelize.options.dialect === 'postgres' && options.ignoreDuplicates ) {
return new Utils.CustomEventEmitter(function(emitter) {
emitter.emit('error', new Error('Postgres does not support the \'ignoreDuplicates\' option.'))
}).run();
}
var self = this var self = this
, updatedAtAttr = Utils._.underscoredIf(self.options.updatedAt, self.options.underscored) , updatedAtAttr = Utils._.underscoredIf(self.options.updatedAt, self.options.underscored)
, createdAtAttr = Utils._.underscoredIf(self.options.createdAt, self.options.underscored) , createdAtAttr = Utils._.underscoredIf(self.options.createdAt, self.options.underscored)
......
var Utils = require("../../utils") var Utils = require("../../utils")
, SqlString = require("../../sql-string") , SqlString = require("../../sql-string")
, daoFactory = require("../../dao-factory")
module.exports = (function() { module.exports = (function() {
var QueryGenerator = { var QueryGenerator = {
...@@ -331,7 +332,14 @@ module.exports = (function() { ...@@ -331,7 +332,14 @@ module.exports = (function() {
/* /*
Quote an object based on its type. This is a more general version of quoteIdentifiers Quote an object based on its type. This is a more general version of quoteIdentifiers
Strings: should proxy to quoteIdentifiers Strings: should proxy to quoteIdentifiers
Arrays: First argument should be qouted, second argument should be append without quoting Arrays:
* Expects array in the form: [<model> (optional), <model> (optional),... String, String (optional)]
Each <model> can be a daoFactory or an object {model: DaoFactory, as: String}, matching include
* Zero or more models can be included in the array and are used to trace a path through the tree of
included nested associations. This produces the correct table name for the ORDER BY/GROUP BY SQL
and quotes it.
* If a single string is appended to end of array, it is quoted.
If two strings appended, the 1st string is quoted, the 2nd string unquoted.
Objects: Objects:
* If raw is set, that value should be returned verbatim, without quoting * If raw is set, that value should be returned verbatim, without quoting
* If fn is set, the string should start with the value of fn, starting paren, followed by * If fn is set, the string should start with the value of fn, starting paren, followed by
...@@ -339,14 +347,71 @@ module.exports = (function() { ...@@ -339,14 +347,71 @@ module.exports = (function() {
unless they are themselves objects unless they are themselves objects
* If direction is set, should be prepended * If direction is set, should be prepended
Currently this function is only used for ordering / grouping columns, but it could Currently this function is only used for ordering / grouping columns and Sequelize.col(), but it could
potentially also be used for other places where we want to be able to call SQL functions (e.g. as default values) potentially also be used for other places where we want to be able to call SQL functions (e.g. as default values)
*/ */
quote: function(obj, force) { quote: function(obj, parent, force) {
if (Utils._.isString(obj)) { if (Utils._.isString(obj)) {
return this.quoteIdentifiers(obj, force) return this.quoteIdentifiers(obj, force)
} else if (Array.isArray(obj)) { } else if (Array.isArray(obj)) {
return this.quote(obj[0], force) + ' ' + obj[1] // loop through array, adding table names of models to quoted
// (checking associations to see if names should be singularised or not)
var quoted = []
, i
, len = obj.length
for (i = 0; i < len - 1; i++) {
var item = obj[i]
if (Utils._.isString(item) || item instanceof Utils.fn || item instanceof Utils.col || item instanceof Utils.literal || item instanceof Utils.cast || 'raw' in item) {
break
}
if (item instanceof daoFactory) {
item = {model: item}
}
// find applicable association for linking parent to this model
var model = item.model
, as
, associations = parent.associations
, association
if (item.hasOwnProperty('as')) {
as = item.as
association = Utils._.find(associations, function(association, associationName) {
return association.target === model && associationName === as
})
} else {
association = Utils._.find(associations, function(association, associationName) {
return association.target === model ?
associationName === (
association.doubleLinked ?
association.combinedName:
(
association.isSingleAssociation ?
Utils.singularize(model.tableName, model.options.language) :
parent.tableName + model.tableName
)
) :
association.targetAssociation && association.targetAssociation.through === model
})
// NB association.target !== model clause below is to singularize names of through tables in hasMany-hasMany joins
as = (association && (association.isSingleAssociation || association.target !== model)) ? Utils.singularize(model.tableName, model.options.language) : model.tableName
}
quoted[i] = as
if (!association) {
throw new Error('\'' + quoted.join('.') + '\' in order / group clause is not valid association')
}
parent = model
}
// add 1st string as quoted, 2nd as unquoted raw
var sql = (i > 0 ? this.quoteIdentifier(quoted.join('.')) + '.' : '') + this.quote(obj[i], parent, force)
if (i < len - 1) {
sql += ' ' + obj[i + 1]
}
return sql
} else if (obj instanceof Utils.fn || obj instanceof Utils.col || obj instanceof Utils.literal || obj instanceof Utils.cast) { } else if (obj instanceof Utils.fn || obj instanceof Utils.col || obj instanceof Utils.literal || obj instanceof Utils.cast) {
return obj.toString(this) return obj.toString(this)
} else if (Utils._.isObject(obj) && 'raw' in obj) { } else if (Utils._.isObject(obj) && 'raw' in obj) {
...@@ -497,12 +562,12 @@ module.exports = (function() { ...@@ -497,12 +562,12 @@ module.exports = (function() {
} }
if (attr instanceof Utils.fn || attr instanceof Utils.col) { if (attr instanceof Utils.fn || attr instanceof Utils.col) {
return self.quote(attr) return attr.toString(self)
} }
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) { if (attr[0] instanceof Utils.fn || attr[0] instanceof Utils.col) {
attr[0] = self.quote(attr[0]) attr[0] = attr[0].toString(self)
addTable = false addTable = false
} }
attr = [attr[0], this.quoteIdentifier(attr[1])].join(' as ') attr = [attr[0], this.quoteIdentifier(attr[1])].join(' as ')
...@@ -703,7 +768,7 @@ module.exports = (function() { ...@@ -703,7 +768,7 @@ module.exports = (function() {
// Add GROUP BY to sub or main query // 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, factory) }.bind(this)).join(', ') : options.group
if (subQuery) { if (subQuery) {
subQueryItems.push(" GROUP BY " + options.group) subQueryItems.push(" GROUP BY " + options.group)
} else { } else {
...@@ -723,7 +788,7 @@ module.exports = (function() { ...@@ -723,7 +788,7 @@ module.exports = (function() {
// Add ORDER to sub or main query // 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, factory) }.bind(this)).join(', ') : options.order
if (subQuery) { if (subQuery) {
subQueryItems.push(" ORDER BY " + options.order) subQueryItems.push(" ORDER BY " + options.order)
...@@ -950,9 +1015,9 @@ module.exports = (function() { ...@@ -950,9 +1015,9 @@ module.exports = (function() {
}) })
if (options.include) { if (options.include) {
return this.quoteIdentifier(keyParts.join('.')) + '.' + this.quote(attributePart); return this.quoteIdentifier(keyParts.join('.')) + '.' + this.quoteIdentifiers(attributePart)
} }
return this.quoteIdentifiers(dao.tableName + '.' + attributePart); return this.quoteIdentifiers(dao.tableName + '.' + attributePart)
}, },
getConditionalJoins: function(options, originalDao){ getConditionalJoins: function(options, originalDao){
......
...@@ -176,8 +176,8 @@ module.exports = (function() { ...@@ -176,8 +176,8 @@ module.exports = (function() {
return Utils._.template(query)({ tableName: tableName, attributes: attrString.join(', ') }) return Utils._.template(query)({ tableName: tableName, attributes: attrString.join(', ') })
}, },
bulkInsertQuery: function(tableName, attrValueHashes) { bulkInsertQuery: function(tableName, attrValueHashes, options) {
var query = "INSERT INTO <%= table %> (<%= attributes %>) VALUES <%= tuples %>;" var query = "INSERT<%= ignoreDuplicates %> INTO <%= table %> (<%= attributes %>) VALUES <%= tuples %>;"
, tuples = [] , tuples = []
, allAttributes = [] , allAttributes = []
...@@ -196,6 +196,7 @@ module.exports = (function() { ...@@ -196,6 +196,7 @@ module.exports = (function() {
}.bind(this)) }.bind(this))
var replacements = { var replacements = {
ignoreDuplicates: options && options.ignoreDuplicates ? ' IGNORE' : '',
table: this.quoteIdentifier(tableName), table: this.quoteIdentifier(tableName),
attributes: allAttributes.map(function(attr){ attributes: allAttributes.map(function(attr){
return this.quoteIdentifier(attr) return this.quoteIdentifier(attr)
......
...@@ -267,7 +267,7 @@ module.exports = (function() { ...@@ -267,7 +267,7 @@ module.exports = (function() {
}) })
}, },
bulkInsertQuery: function(tableName, attrValueHashes) { bulkInsertQuery: function(tableName, attrValueHashes, options) {
var query = "INSERT INTO <%= table %> (<%= attributes %>) VALUES <%= tuples %> RETURNING *;" var query = "INSERT INTO <%= table %> (<%= attributes %>) VALUES <%= tuples %> RETURNING *;"
, tuples = [] , tuples = []
, serials = [] , serials = []
......
...@@ -172,8 +172,8 @@ module.exports = (function() { ...@@ -172,8 +172,8 @@ module.exports = (function() {
return "SELECT name FROM sqlite_master WHERE type='table' and name!='sqlite_sequence';" return "SELECT name FROM sqlite_master WHERE type='table' and name!='sqlite_sequence';"
}, },
bulkInsertQuery: function(tableName, attrValueHashes) { bulkInsertQuery: function(tableName, attrValueHashes, options) {
var query = "INSERT INTO <%= table %> (<%= attributes %>) VALUES <%= tuples %>;" var query = "INSERT<%= ignoreDuplicates %> INTO <%= table %> (<%= attributes %>) VALUES <%= tuples %>;"
, tuples = [] , tuples = []
, allAttributes = [] , allAttributes = []
...@@ -192,6 +192,7 @@ module.exports = (function() { ...@@ -192,6 +192,7 @@ module.exports = (function() {
}.bind(this)) }.bind(this))
var replacements = { var replacements = {
ignoreDuplicates: options && options.ignoreDuplicates ? ' OR IGNORE' : '',
table: this.quoteIdentifier(tableName), table: this.quoteIdentifier(tableName),
attributes: allAttributes.map(function(attr){ attributes: allAttributes.map(function(attr){
return this.quoteIdentifier(attr) return this.quoteIdentifier(attr)
......
...@@ -510,7 +510,7 @@ module.exports = (function() { ...@@ -510,7 +510,7 @@ module.exports = (function() {
} }
QueryInterface.prototype.bulkInsert = function(tableName, records, options) { QueryInterface.prototype.bulkInsert = function(tableName, records, options) {
var sql = this.QueryGenerator.bulkInsertQuery(tableName, records) var sql = this.QueryGenerator.bulkInsertQuery(tableName, records, options)
return queryAndEmit.call(this, [sql, null, options], 'bulkInsert') return queryAndEmit.call(this, [sql, null, options], 'bulkInsert')
} }
......
...@@ -534,6 +534,9 @@ var Utils = module.exports = { ...@@ -534,6 +534,9 @@ var Utils = module.exports = {
}, },
col: function (col) { col: function (col) {
if (arguments.length > 1) {
col = Array.prototype.slice.call(arguments);
}
this.col = col this.col = col
}, },
...@@ -588,21 +591,25 @@ Utils.cast.prototype.toString = function(queryGenerator) { ...@@ -588,21 +591,25 @@ Utils.cast.prototype.toString = function(queryGenerator) {
return 'CAST(' + this.val + ' AS ' + this.type.toUpperCase() + ')' return 'CAST(' + this.val + ' AS ' + this.type.toUpperCase() + ')'
} }
Utils.fn.prototype.toString = function(queryGenerator) { Utils.fn.prototype.toString = function(queryGenerator, parentModel) {
return this.fn + '(' + this.args.map(function (arg) { return this.fn + '(' + this.args.map(function (arg) {
if (arg instanceof Utils.fn || arg instanceof Utils.col) { if (arg instanceof Utils.fn || arg instanceof Utils.col) {
return arg.toString(queryGenerator) return arg.toString(queryGenerator, parentModel)
} else { } else {
return queryGenerator.escape(arg) return queryGenerator.escape(arg)
} }
}).join(', ') + ')' }).join(', ') + ')'
} }
Utils.col.prototype.toString = function (queryGenerator) { Utils.col.prototype.toString = function (queryGenerator, parentModel) {
if (this.col.indexOf('*') === 0) { if (Array.isArray(this.col)) {
if (!parent) {
throw new Error('Cannot call Sequelize.col() with array outside of order / group clause')
}
} else if (this.col.indexOf('*') === 0) {
return '*' return '*'
} }
return queryGenerator.quote(this.col) return queryGenerator.quote(this.col, parentModel)
} }
Utils.CustomEventEmitter = require(__dirname + "/emitters/custom-event-emitter") Utils.CustomEventEmitter = require(__dirname + "/emitters/custom-event-emitter")
......
...@@ -518,7 +518,17 @@ describe(Support.getTestDialectTeaser("HasMany"), function() { ...@@ -518,7 +518,17 @@ describe(Support.getTestDialectTeaser("HasMany"), function() {
}) // end optimization using bulk create, destroy and update }) // end optimization using bulk create, destroy and update
describe('selfAssociations', function () { describe('selfAssociations', function () {
it('should work', function (done) { it('should work with alias', function (done) {
var Person = this.sequelize.define('Group', {})
Person.hasMany(Person, { as: 'Children'});
this.sequelize.sync().done(function (err) {
expect(err).not.to.be.ok
done()
})
})
it('should work with through', function (done) {
var Group = this.sequelize.define('Group', {}) var Group = this.sequelize.define('Group', {})
Group.hasMany(Group, { through: 'groups_outsourcing_companies', as: 'OutsourcingCompanies'}); Group.hasMany(Group, { through: 'groups_outsourcing_companies', as: 'OutsourcingCompanies'});
......
...@@ -24,7 +24,8 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () { ...@@ -24,7 +24,8 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
data: DataTypes.STRING, data: DataTypes.STRING,
intVal: DataTypes.INTEGER, intVal: DataTypes.INTEGER,
theDate: DataTypes.DATE, theDate: DataTypes.DATE,
aBool: DataTypes.BOOLEAN aBool: DataTypes.BOOLEAN,
uniqueName: { type: DataTypes.STRING, unique: true }
}) })
this.User.sync({ force: true }).success(function() { this.User.sync({ force: true }).success(function() {
...@@ -1005,6 +1006,47 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () { ...@@ -1005,6 +1006,47 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
}) })
}) })
if (Support.getTestDialect() !== 'postgres') {
it("should support the ignoreDuplicates option", function(done) {
var self = this
, data = [{ uniqueName: 'Peter', secretValue: '42' },
{ uniqueName: 'Paul', secretValue: '23' }]
this.User.bulkCreate(data, { fields: ['uniqueName', 'secretValue'] }).success(function() {
data.push({ uniqueName: 'Michael', secretValue: '26' });
self.User.bulkCreate(data, { fields: ['uniqueName', 'secretValue'], ignoreDuplicates: true }).success(function() {
self.User.findAll({order: 'id'}).success(function(users) {
expect(users.length).to.equal(3)
expect(users[0].uniqueName).to.equal("Peter")
expect(users[0].secretValue).to.equal("42");
expect(users[1].uniqueName).to.equal("Paul")
expect(users[1].secretValue).to.equal("23");
expect(users[2].uniqueName).to.equal("Michael")
expect(users[2].secretValue).to.equal("26");
done()
});
});
})
})
} else {
it("should throw an error when the ignoreDuplicates option is passed", function(done) {
var self = this
, data = [{ uniqueName: 'Peter', secretValue: '42' },
{ uniqueName: 'Paul', secretValue: '23' }]
this.User.bulkCreate(data, { fields: ['uniqueName', 'secretValue'] }).success(function() {
data.push({ uniqueName: 'Michael', secretValue: '26' });
self.User.bulkCreate(data, { fields: ['uniqueName', 'secretValue'], ignoreDuplicates: true }).error(function(err) {
expect(err).to.exist
expect(err.message).to.match(/Postgres does not support the \'ignoreDuplicates\' option./)
done();
})
})
})
}
describe('enums', function() { describe('enums', function() {
it('correctly restores enum values', function(done) { it('correctly restores enum values', function(done) {
var self = this var self = this
......
...@@ -761,6 +761,224 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () { ...@@ -761,6 +761,224 @@ describe(Support.getTestDialectTeaser("DAOFactory"), function () {
}) })
}) })
describe('order by eager loaded tables', function() {
describe('HasMany', function() {
beforeEach(function(done) {
var self = this
self.Continent = this.sequelize.define('Continent', { name: Sequelize.STRING })
self.Country = this.sequelize.define('Country', { name: Sequelize.STRING })
self.Person = this.sequelize.define('Person', { name: Sequelize.STRING, lastName: Sequelize.STRING })
self.Continent.hasMany(self.Country)
self.Country.belongsTo(self.Continent)
self.Country.hasMany(self.Person)
self.Person.belongsTo(self.Country)
self.Country.hasMany(self.Person, { as: 'Residents', foreignKey: 'CountryResidentId' })
self.Person.belongsTo(self.Country, { as: 'CountryResident', foreignKey: 'CountryResidentId' })
async.forEach([ self.Continent, self.Country, self.Person ], function(model, callback) {
model.sync({ force: true }).done(callback)
}, function () {
async.parallel({
europe: function(callback) {self.Continent.create({ name: 'Europe' }).done(callback)},
asia: function(callback) {self.Continent.create({ name: 'Asia' }).done(callback)},
england: function(callback) {self.Country.create({ name: 'England' }).done(callback)},
france: function(callback) {self.Country.create({ name: 'France' }).done(callback)},
korea: function(callback) {self.Country.create({ name: 'Korea' }).done(callback)},
bob: function(callback) {self.Person.create({ name: 'Bob', lastName: 'Becket' }).done(callback)},
fred: function(callback) {self.Person.create({ name: 'Fred', lastName: 'Able' }).done(callback)},
pierre: function(callback) {self.Person.create({ name: 'Pierre', lastName: 'Paris' }).done(callback)},
kim: function(callback) {self.Person.create({ name: 'Kim', lastName: 'Z' }).done(callback)}
}, function(err, r) {
if (err) throw err
_.forEach(r, function(item, itemName) {
self[itemName] = item
})
async.parallel([
function(callback) {self.england.setContinent(self.europe).done(callback)},
function(callback) {self.france.setContinent(self.europe).done(callback)},
function(callback) {self.korea.setContinent(self.asia).done(callback)},
function(callback) {self.bob.setCountry(self.england).done(callback)},
function(callback) {self.fred.setCountry(self.england).done(callback)},
function(callback) {self.pierre.setCountry(self.france).done(callback)},
function(callback) {self.kim.setCountry(self.korea).done(callback)},
function(callback) {self.bob.setCountryResident(self.england).done(callback)},
function(callback) {self.fred.setCountryResident(self.france).done(callback)},
function(callback) {self.pierre.setCountryResident(self.korea).done(callback)},
function(callback) {self.kim.setCountryResident(self.england).done(callback)}
], function(err) {
if (err) throw err
done()
})
})
})
})
it('sorts simply', function(done) {
var self = this
async.eachSeries([ [ 'ASC', 'Asia' ], [ 'DESC', 'Europe' ] ], function(params, callback) {
self.Continent.findAll({
order: [ [ 'name', params[0] ] ]
}).done(function(err, continents) {
expect(err).not.to.be.ok
expect(continents).to.exist
expect(continents[0]).to.exist
expect(continents[0].name).to.equal(params[1])
callback()
})
}, function() {done()})
})
it('sorts by 1st degree association', function(done) {
var self = this
async.forEach([ [ 'ASC', 'Europe', 'England' ], [ 'DESC', 'Asia', 'Korea' ] ], function(params, callback) {
self.Continent.findAll({
include: [ self.Country ],
order: [ [ self.Country, 'name', params[0] ] ]
}).done(function(err, continents) {
expect(err).not.to.be.ok
expect(continents).to.exist
expect(continents[0]).to.exist
expect(continents[0].name).to.equal(params[1])
expect(continents[0].countries).to.exist
expect(continents[0].countries[0]).to.exist
expect(continents[0].countries[0].name).to.equal(params[2])
callback()
})
}, function() {done()})
}),
it('sorts by 2nd degree association', function(done) {
var self = this
async.forEach([ [ 'ASC', 'Europe', 'England', 'Fred' ], [ 'DESC', 'Asia', 'Korea', 'Kim' ] ], function(params, callback) {
self.Continent.findAll({
include: [ { model: self.Country, include: [ self.Person ] } ],
order: [ [ self.Country, self.Person, 'lastName', params[0] ] ]
}).done(function(err, continents) {
expect(err).not.to.be.ok
expect(continents).to.exist
expect(continents[0]).to.exist
expect(continents[0].name).to.equal(params[1])
expect(continents[0].countries).to.exist
expect(continents[0].countries[0]).to.exist
expect(continents[0].countries[0].name).to.equal(params[2])
expect(continents[0].countries[0].persons).to.exist
expect(continents[0].countries[0].persons[0]).to.exist
expect(continents[0].countries[0].persons[0].name).to.equal(params[3])
callback()
})
}, function() {done()})
}),
it('sorts by 2nd degree association with alias', function(done) {
var self = this
async.forEach([ [ 'ASC', 'Europe', 'France', 'Fred' ], [ 'DESC', 'Europe', 'England', 'Kim' ] ], function(params, callback) {
self.Continent.findAll({
include: [ { model: self.Country, include: [ self.Person, {model: self.Person, as: 'Residents' } ] } ],
order: [ [ self.Country, {model: self.Person, as: 'Residents' }, 'lastName', params[0] ] ]
}).done(function(err, continents) {
expect(err).not.to.be.ok
expect(continents).to.exist
expect(continents[0]).to.exist
expect(continents[0].name).to.equal(params[1])
expect(continents[0].countries).to.exist
expect(continents[0].countries[0]).to.exist
expect(continents[0].countries[0].name).to.equal(params[2])
expect(continents[0].countries[0].residents).to.exist
expect(continents[0].countries[0].residents[0]).to.exist
expect(continents[0].countries[0].residents[0].name).to.equal(params[3])
callback()
})
}, function() {done()})
})
}),
describe('ManyToMany', function() {
beforeEach(function(done) {
var self = this
self.Country = this.sequelize.define('Country', { name: Sequelize.STRING })
self.Industry = this.sequelize.define('Industry', { name: Sequelize.STRING })
self.IndustryCountry = this.sequelize.define('IndustryCountry', { numYears: Sequelize.INTEGER })
self.Country.hasMany(self.Industry, {through: self.IndustryCountry})
self.Industry.hasMany(self.Country, {through: self.IndustryCountry})
async.forEach([ self.Country, self.Industry ], function(model, callback) {
model.sync({ force: true }).done(callback)
}, function () {
async.parallel({
england: function(callback) {self.Country.create({ name: 'England' }).done(callback)},
france: function(callback) {self.Country.create({ name: 'France' }).done(callback)},
korea: function(callback) {self.Country.create({ name: 'Korea' }).done(callback)},
energy: function(callback) {self.Industry.create({ name: 'Energy' }).done(callback)},
media: function(callback) {self.Industry.create({ name: 'Media' }).done(callback)},
tech: function(callback) {self.Industry.create({ name: 'Tech' }).done(callback)}
}, function(err, r) {
if (err) throw err
_.forEach(r, function(item, itemName) {
self[itemName] = item
})
async.parallel([
function(callback) {self.england.addIndustry(self.energy, {numYears: 20}).done(callback)},
function(callback) {self.england.addIndustry(self.media, {numYears: 40}).done(callback)},
function(callback) {self.france.addIndustry(self.media, {numYears: 80}).done(callback)},
function(callback) {self.korea.addIndustry(self.tech, {numYears: 30}).done(callback)}
], function(err) {
if (err) throw err
done()
})
})
})
})
it('sorts by 1st degree association', function(done) {
var self = this
async.forEach([ [ 'ASC', 'England', 'Energy' ], [ 'DESC', 'Korea', 'Tech' ] ], function(params, callback) {
self.Country.findAll({
include: [ self.Industry ],
order: [ [ self.Industry, 'name', params[0] ] ]
}).done(function(err, countries) {
expect(err).not.to.be.ok
expect(countries).to.exist
expect(countries[0]).to.exist
expect(countries[0].name).to.equal(params[1])
expect(countries[0].industries).to.exist
expect(countries[0].industries[0]).to.exist
expect(countries[0].industries[0].name).to.equal(params[2])
callback()
})
}, function() {done()})
})
it('sorts by through table attribute', function(done) {
var self = this
async.forEach([ [ 'ASC', 'England', 'Energy' ], [ 'DESC', 'France', 'Media' ] ], function(params, callback) {
self.Country.findAll({
include: [ self.Industry ],
order: [ [ self.Industry, self.IndustryCountry, 'numYears', params[0] ] ]
}).done(function(err, countries) {
expect(err).not.to.be.ok
expect(countries).to.exist
expect(countries[0]).to.exist
expect(countries[0].name).to.equal(params[1])
expect(countries[0].industries).to.exist
expect(countries[0].industries[0]).to.exist
expect(countries[0].industries[0].name).to.equal(params[2])
callback()
})
}, function() {done()})
})
})
})
describe('normal findAll', function() { describe('normal findAll', function() {
beforeEach(function(done) { beforeEach(function(done) {
var self = this var self = this
......
...@@ -389,6 +389,9 @@ if (Support.dialectIsMySQL()) { ...@@ -389,6 +389,9 @@ if (Support.dialectIsMySQL()) {
}, { }, {
arguments: ['myTable', [{name: "foo", value: true}, {name: 'bar', value: false}]], arguments: ['myTable', [{name: "foo", value: true}, {name: 'bar', value: false}]],
expectation: "INSERT INTO `myTable` (`name`,`value`) VALUES ('foo',true),('bar',false);" expectation: "INSERT INTO `myTable` (`name`,`value`) VALUES ('foo',true),('bar',false);"
}, {
arguments: ['myTable', [{name: 'foo'}, {name: 'bar'}], {ignoreDuplicates: true}],
expectation: "INSERT IGNORE INTO `myTable` (`name`) VALUES ('foo'),('bar');"
} }
], ],
......
...@@ -385,6 +385,9 @@ if (dialect === 'sqlite') { ...@@ -385,6 +385,9 @@ if (dialect === 'sqlite') {
arguments: ['myTable', [{name: 'foo', foo: 1, nullValue: null}, {name: 'bar', foo: 2, nullValue: null}]], arguments: ['myTable', [{name: 'foo', foo: 1, nullValue: null}, {name: 'bar', foo: 2, nullValue: null}]],
expectation: "INSERT INTO `myTable` (`name`,`foo`,`nullValue`) VALUES ('foo',1,NULL),('bar',2,NULL);", expectation: "INSERT INTO `myTable` (`name`,`foo`,`nullValue`) VALUES ('foo',1,NULL),('bar',2,NULL);",
context: {options: {omitNull: true}} // Note: As above context: {options: {omitNull: true}} // Note: As above
}, {
arguments: ['myTable', [{name: 'foo'}, {name: 'bar'}], {ignoreDuplicates: true}],
expectation: "INSERT OR IGNORE INTO `myTable` (`name`) VALUES ('foo'),('bar');"
} }
], ],
......
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!