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

Commit c6982f2c by Mick Hansen

Merge pull request #2535 from sequelize/refactor-nm-filtering

Refactor nm filtering
2 parents 26b980d7 088c2dbd
...@@ -723,7 +723,9 @@ module.exports = (function() { ...@@ -723,7 +723,9 @@ module.exports = (function() {
, mainAttributes = options.attributes && options.attributes.slice(0) , mainAttributes = options.attributes && options.attributes.slice(0)
, mainJoinQueries = [] , mainJoinQueries = []
// We'll use a subquery if we have hasMany associations and a limit and a filtered/required association // We'll use a subquery if we have hasMany associations and a limit and a filtered/required association
, subQuery = limit && (options.hasIncludeWhere || options.hasIncludeRequired || options.hasMultiAssociation) && options.subQuery !== false , subQuery = options.subQuery === undefined ?
limit && (options.hasIncludeWhere || options.hasIncludeRequired || options.hasMultiAssociation) && options.subQuery !== false :
options.subquery
, subQueryItems = [] , subQueryItems = []
, subQueryAttributes = null , subQueryAttributes = null
, subJoinQueries = [] , subJoinQueries = []
...@@ -920,17 +922,74 @@ module.exports = (function() { ...@@ -920,17 +922,74 @@ module.exports = (function() {
joinQueryItem += ' AND ' + targetWhere; joinQueryItem += ' AND ' + targetWhere;
if (subQuery && include.required) { if (subQuery && include.required) {
if (!options.where) options.where = {}; if (!options.where) options.where = {};
(function (include) {
// Closure to use sane local variables
var parent = include
, child = include
, nestedIncludes = []
, topParent
, topInclude
, $query;
while (parent = parent.parent) {
nestedIncludes = [_.extend({}, child, {include: nestedIncludes})];
child = parent;
}
// Creating the as-is where for the subQuery, checks that the required association exists topInclude = nestedIncludes[0];
options.where['__' + throughAs] = self.sequelize.asIs(['(', topParent = topInclude.parent;
'SELECT ' + self.quoteIdentifier(throughAs) + '.' + self.quoteIdentifier(identSource) + ' FROM ' + self.quoteTable(throughTable, throughAs), if (topInclude.through && Object(topInclude.through.model) === topInclude.through.model) {
! include.required && joinType + self.quoteTable(association.source.tableName, tableSource) + ' ON ' + sourceJoinOn || '', $query = self.selectQuery(topInclude.through.model.getTableName(), {
joinType + self.quoteTable(table, as) + ' ON ' + targetJoinOn, attributes: [topInclude.through.model.primaryKeyAttributes[0]],
'WHERE ' + (! include.required && targetWhere || sourceJoinOn + ' AND ' + targetWhere), include: [{
'LIMIT 1', model: topInclude.model,
as: topInclude.model.name,
attributes: [],
association: {
associationType: 'BelongsTo',
isSingleAssociation: true,
source: topInclude.association.target,
target: topInclude.association.source,
identifier: topInclude.association.foreignIdentifier,
identifierField: topInclude.association.foreignIdentifierField
},
required: true,
include: topInclude.include,
_pseudo: true
}],
where: {
$join: self.sequelize.asIs([
self.quoteTable(topParent.model.name) + '.' + self.quoteIdentifier(topParent.model.primaryKeyAttributes[0]),
self.quoteIdentifier(topInclude.through.model.name) + '.' + self.quoteIdentifier(topInclude.association.identifierField)
].join(" = "))
},
limit: 1,
includeIgnoreAttributes: false
}, topInclude.through.model);
} else {
$query = self.selectQuery(topInclude.model.tableName, {
attributes: [topInclude.model.primaryKeyAttributes[0]],
include: topInclude.include,
where: {
$join: self.sequelize.asIs([
self.quoteTable(topParent.model.name) + '.' + self.quoteIdentifier(topParent.model.primaryKeyAttributes[0]),
self.quoteIdentifier(topInclude.model.name) + '.' + self.quoteIdentifier(topInclude.association.identifierField)
].join(" = "))
},
limit: 1,
includeIgnoreAttributes: false
}, topInclude.model);
}
')', 'IS NOT NULL'].join(' ')); options.where['__' + throughAs] = self.sequelize.asIs([
'(',
$query.replace(/\;$/, ""),
')',
'IS NOT NULL'
].join(' '));
})(include);
} }
} }
} else { } else {
......
...@@ -752,7 +752,7 @@ module.exports = (function() { ...@@ -752,7 +752,7 @@ module.exports = (function() {
type: QueryTypes.SELECT, type: QueryTypes.SELECT,
hasJoin: hasJoin, hasJoin: hasJoin,
tableNames: Object.keys(tableNames) tableNames: Object.keys(tableNames)
}, queryOptions, { transaction: (options || {}).transaction })); }, queryOptions, { transaction: options.transaction }));
}).tap(function(results) { }).tap(function(results) {
if (options.hooks) { if (options.hooks) {
return this.runHooks('afterFind', results, options); return this.runHooks('afterFind', results, options);
...@@ -1816,6 +1816,7 @@ module.exports = (function() { ...@@ -1816,6 +1816,7 @@ module.exports = (function() {
options.hasIncludeRequired = options.hasIncludeRequired || include.hasIncludeRequired || !!include.required; options.hasIncludeRequired = options.hasIncludeRequired || include.hasIncludeRequired || !!include.required;
} }
}; };
Model.$validateIncludedElements = validateIncludedElements;
var validateIncludedElement = function(include, tableNames) { var validateIncludedElement = function(include, tableNames) {
if (!include.hasOwnProperty('model') && !include.hasOwnProperty('association')) { if (!include.hasOwnProperty('model') && !include.hasOwnProperty('association')) {
......
...@@ -7,6 +7,7 @@ var chai = require('chai') ...@@ -7,6 +7,7 @@ var chai = require('chai')
, DataTypes = require(__dirname + "/../../lib/data-types") , DataTypes = require(__dirname + "/../../lib/data-types")
, datetime = require('chai-datetime') , datetime = require('chai-datetime')
, async = require('async') , async = require('async')
, Promise = Sequelize.Promise;
chai.use(datetime) chai.use(datetime)
chai.config.includeStack = true chai.config.includeStack = true
...@@ -235,6 +236,78 @@ describe(Support.getTestDialectTeaser("Include"), function () { ...@@ -235,6 +236,78 @@ describe(Support.getTestDialectTeaser("Include"), function () {
} }
}) })
it('should accept nested `where` and `limit` at the same time', function () {
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
})
, Set = this.sequelize.define('Set', {
title: DataTypes.STRING
});
Set.hasMany(Product);
Product.belongsTo(Set);
Product.hasMany(Tag, {through: ProductTag});
Tag.hasMany(Product, {through: ProductTag});
return this.sequelize.sync({force: true}).then(function () {
return Promise.join(
Set.bulkCreate([
{title: 'office'}
]),
Product.bulkCreate([
{title: 'Chair'},
{title: 'Desk'},
{title: 'Dress'}
]),
Tag.bulkCreate([
{name: 'A'},
{name: 'B'},
{name: 'C'}
])
).then(function () {
return Promise.join(
Set.findAll(),
Product.findAll(),
Tag.findAll()
);
}).spread(function (sets, products, tags) {
return Promise.join(
sets[0].addProducts([products[0], products[1]]),
products[0].addTag(tags[0], {priority: 1}).then(function() {
return products[0].addTag(tags[1], {priority:2});
}).then(function() {
return products[0].addTag(tags[2], {priority:1});
}),
products[1].addTag(tags[1], {priority:2}).then(function() {
return products[2].addTag(tags[1], {priority:3});
}).then( function() {
return products[2].addTag(tags[2], {priority:0});
})
);
}).then(function() {
return Set.findAll({
include: [{
model: Product,
include: [{
model: Tag,
where: {
name: 'A'
}
}]
}],
limit: 1
}).on('sql', function (sql) {
console.log(sql);
});
});
});
});
it('should support an include with multiple different association types', function (done) { it('should support an include with multiple different association types', function (done) {
var User = this.sequelize.define('User', {}) var User = this.sequelize.define('User', {})
, Product = this.sequelize.define('Product', { , Product = this.sequelize.define('Product', {
......
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!