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

Commit 20cac7a1 by Jozef Hartinger Committed by Sushant

fix(query-generator): generate subQuery filter for nested required joins (#9188)

1 parent a7e9e2b8
...@@ -1249,7 +1249,7 @@ const QueryGenerator = { ...@@ -1249,7 +1249,7 @@ const QueryGenerator = {
? this.quoteIdentifiers(attr) ? this.quoteIdentifiers(attr)
: this.escape(attr); : this.escape(attr);
} }
if (options.include && attr.indexOf('.') === -1 && addTable) { if (!_.isEmpty(options.include) && attr.indexOf('.') === -1 && addTable) {
attr = mainTableAs + '.' + attr; attr = mainTableAs + '.' + attr;
} }
...@@ -1258,7 +1258,6 @@ const QueryGenerator = { ...@@ -1258,7 +1258,6 @@ const QueryGenerator = {
}, },
generateInclude(include, parentTableName, topLevelInfo) { generateInclude(include, parentTableName, topLevelInfo) {
const association = include.association;
const joinQueries = { const joinQueries = {
mainQuery: [], mainQuery: [],
subQuery: [] subQuery: []
...@@ -1334,43 +1333,7 @@ const QueryGenerator = { ...@@ -1334,43 +1333,7 @@ const QueryGenerator = {
if (include.through) { if (include.through) {
joinQuery = this.generateThroughJoin(include, includeAs, parentTableName.internalAs, topLevelInfo); joinQuery = this.generateThroughJoin(include, includeAs, parentTableName.internalAs, topLevelInfo);
} else { } else {
if (topLevelInfo.subQuery && include.subQueryFilter) { this._generateSubQueryFilter(include, includeAs, topLevelInfo);
const associationWhere = {};
associationWhere[association.identifierField] = {
[Op.eq]: this.sequelize.literal(`${this.quoteTable(parentTableName.internalAs)}.${this.quoteIdentifier(association.sourceKeyField || association.source.primaryKeyField)}`)
};
if (!topLevelInfo.options.where) {
topLevelInfo.options.where = {};
}
// Creating the as-is where for the subQuery, checks that the required association exists
const $query = this.selectQuery(include.model.getTableName(), {
attributes: [association.identifierField],
where: {
[Op.and]: [
associationWhere,
include.where || {}
]
},
limit: 1,
tableAs: include.as
}, include.model);
const subQueryWhere = this.sequelize.asIs([
'(',
$query.replace(/\;$/, ''),
')',
'IS NOT NULL'
].join(' '));
if (_.isPlainObject(topLevelInfo.options.where)) {
topLevelInfo.options.where['__' + includeAs.internalAs] = subQueryWhere;
} else {
topLevelInfo.options.where = { [Op.and]: [topLevelInfo.options.where, subQueryWhere] };
}
}
joinQuery = this.generateJoin(include, topLevelInfo); joinQuery = this.generateJoin(include, topLevelInfo);
} }
...@@ -1609,71 +1572,10 @@ const QueryGenerator = { ...@@ -1609,71 +1572,10 @@ const QueryGenerator = {
joinCondition += ` AND ${targetWhere}`; joinCondition += ` AND ${targetWhere}`;
} }
} }
if (topLevelInfo.subQuery && include.required) {
if (!topLevelInfo.options.where) {
topLevelInfo.options.where = {};
}
let parent = include;
let child = include;
let nestedIncludes = [];
let query;
while ((parent = parent.parent)) { // eslint-disable-line
nestedIncludes = [_.extend({}, child, { include: nestedIncludes })];
child = parent;
}
const topInclude = nestedIncludes[0];
const topParent = topInclude.parent;
if (topInclude.through && Object(topInclude.through.model) === topInclude.through.model) {
query = this.selectQuery(topInclude.through.model.getTableName(), {
attributes: [topInclude.through.model.primaryKeyField],
include: Model._validateIncludedElements({
model: topInclude.through.model,
include: [{
association: topInclude.association.toTarget,
required: true
}]
}).include,
model: topInclude.through.model,
where: {
[Op.and]: [
this.sequelize.asIs([
this.quoteTable(topParent.model.name) + '.' + this.quoteIdentifier(topParent.model.primaryKeyField),
this.quoteIdentifier(topInclude.through.model.name) + '.' + this.quoteIdentifier(topInclude.association.identifierField)
].join(' = ')),
topInclude.through.where
]
},
limit: 1,
includeIgnoreAttributes: false
}, topInclude.through.model);
} else {
const isBelongsTo = topInclude.association.associationType === 'BelongsTo';
const join = [
this.quoteTable(topParent.model.name) + '.' + this.quoteIdentifier(isBelongsTo ? topInclude.association.identifierField : topParent.model.primaryKeyAttributes[0]),
this.quoteIdentifier(topInclude.model.name) + '.' + this.quoteIdentifier(isBelongsTo ? topInclude.model.primaryKeyAttributes[0] : topInclude.association.identifierField)
].join(' = ');
query = this.selectQuery(topInclude.model.tableName, {
attributes: [topInclude.model.primaryKeyAttributes[0]],
include: topInclude.include,
where: {
[Op.join]: this.sequelize.asIs(join)
},
limit: 1,
includeIgnoreAttributes: false
}, topInclude.model);
}
topLevelInfo.options.where['__' + throughAs] = this.sequelize.asIs([
'(',
query.replace(/\;$/, ''),
')',
'IS NOT NULL'
].join(' '));
}
} }
this._generateSubQueryFilter(include, includeAs, topLevelInfo);
return { return {
join: joinType, join: joinType,
body: joinBody, body: joinBody,
...@@ -1682,6 +1584,123 @@ const QueryGenerator = { ...@@ -1682,6 +1584,123 @@ const QueryGenerator = {
}; };
}, },
/*
* Generates subQueryFilter - a select nested in the where clause of the subQuery.
* For a given include a query is generated that contains all the way from the subQuery
* table to the include table plus everything that's in required transitive closure of the
* given include.
*/
_generateSubQueryFilter(include, includeAs, topLevelInfo) {
if (!topLevelInfo.subQuery || !include.subQueryFilter) {
return;
}
if (!topLevelInfo.options.where) {
topLevelInfo.options.where = {};
}
let parent = include;
let child = include;
let nestedIncludes = this._getRequiredClosure(include).include;
let query;
while ((parent = parent.parent)) { // eslint-disable-line
if (parent.parent && !parent.required) {
return; // only generate subQueryFilter if all the parents of this include are required
}
if (parent.subQueryFilter) {
// the include is already handled as this parent has the include on its required closure
// skip to prevent duplicate subQueryFilter
return;
}
nestedIncludes = [_.extend({}, child, { include: nestedIncludes, attributes: [] })];
child = parent;
}
const topInclude = nestedIncludes[0];
const topParent = topInclude.parent;
const topAssociation = topInclude.association;
topInclude.association = undefined;
if (topInclude.through && Object(topInclude.through.model) === topInclude.through.model) {
query = this.selectQuery(topInclude.through.model.getTableName(), {
attributes: [topInclude.through.model.primaryKeyField],
include: Model._validateIncludedElements({
model: topInclude.through.model,
include: [{
association: topAssociation.toTarget,
required: true,
where: topInclude.where,
include: topInclude.include
}]
}).include,
model: topInclude.through.model,
where: {
[Op.and]: [
this.sequelize.asIs([
this.quoteTable(topParent.model.name) + '.' + this.quoteIdentifier(topParent.model.primaryKeyField),
this.quoteIdentifier(topInclude.through.model.name) + '.' + this.quoteIdentifier(topAssociation.identifierField)
].join(' = ')),
topInclude.through.where
]
},
limit: 1,
includeIgnoreAttributes: false
}, topInclude.through.model);
} else {
const isBelongsTo = topAssociation.associationType === 'BelongsTo';
const sourceField = isBelongsTo ? topAssociation.identifierField : (topAssociation.sourceKeyField || topParent.model.primaryKeyField);
const targetField = isBelongsTo ? (topAssociation.sourceKeyField || topInclude.model.primaryKeyField) : topAssociation.identifierField;
const join = [
this.quoteIdentifier(topInclude.as) + '.' + this.quoteIdentifier(targetField),
this.quoteTable(topParent.as || topParent.model.name) + '.' + this.quoteIdentifier(sourceField)
].join(' = ');
query = this.selectQuery(topInclude.model.getTableName(), {
attributes: [targetField],
include: Model._validateIncludedElements(topInclude).include,
model: topInclude.model,
where: {
[Op.and]: [{
[Op.join]: this.sequelize.asIs(join)
}]
},
limit: 1,
tableAs: topInclude.as,
includeIgnoreAttributes: false
}, topInclude.model);
}
if (!topLevelInfo.options.where[Op.and]) {
topLevelInfo.options.where[Op.and] = [];
}
topLevelInfo.options.where[`__${includeAs.internalAs}`] = this.sequelize.asIs([
'(',
query.replace(/\;$/, ''),
')',
'IS NOT NULL'
].join(' '));
},
/*
* For a given include hierarchy creates a copy of it where only the required includes
* are preserved.
*/
_getRequiredClosure(include) {
const copy = _.extend({}, include, {attributes: [], include: []});
if (Array.isArray(include.include)) {
copy.include = include.include
.filter(i => i.required)
.map(inc => this._getRequiredClosure(inc));
}
return copy;
},
getQueryOrders(options, model, subQuery) { getQueryOrders(options, model, subQuery) {
const mainQueryOrder = []; const mainQueryOrder = [];
const subQueryOrder = []; const subQueryOrder = [];
......
...@@ -390,6 +390,7 @@ class Model { ...@@ -390,6 +390,7 @@ class Model {
options.include = options.include.map(include => { options.include = options.include.map(include => {
include = this._conformInclude(include); include = this._conformInclude(include);
include.parent = options; include.parent = options;
include.topLimit = options.topLimit;
this._validateIncludedElement.call(options.model, include, tableNames, options); this._validateIncludedElement.call(options.model, include, tableNames, options);
......
'use strict';
const chai = require('chai'),
Sequelize = require('../../../index'),
expect = chai.expect,
Support = require(__dirname + '/../support'),
DataTypes = require(__dirname + '/../../../lib/data-types'),
Promise = Sequelize.Promise,
Op = Sequelize.Op;
describe(Support.getTestDialectTeaser('Include'), () => {
describe('LIMIT', () => {
/*
* shortcut for building simple {name: 'foo'} seed data
*/
function build() {
return Array.prototype.slice.call(arguments).map(arg => ({name: arg}));
}
/*
* association overview
* [Task]N---N[Project]N---N[User]N---N[Hobby]
* 1
* |
* |
* |
* N
* [Comment]N---1[Post]N---N[Tag]N---1[Color]
* 1
* |
* |
* |
* [Footnote]
*/
beforeEach(function () {
this.Project = this.sequelize.define('Project', {
name: {
type: DataTypes.STRING,
primaryKey: true
}
}, {timestamps: false});
this.User = this.sequelize.define('User', {
name: {
type: DataTypes.STRING,
primaryKey: true
}
}, {timestamps: false});
this.Task = this.sequelize.define('Task', {
name: {
type: DataTypes.STRING,
primaryKey: true
}
}, {timestamps: false});
this.Hobby = this.sequelize.define('Hobby', {
name: {
type: DataTypes.STRING,
primaryKey: true
}
}, {timestamps: false});
this.User.belongsToMany(this.Project, {through: 'user_project'});
this.Project.belongsToMany(this.User, {through: 'user_project'});
this.Project.belongsToMany(this.Task, {through: 'task_project'});
this.Task.belongsToMany(this.Project, {through: 'task_project'});
this.User.belongsToMany(this.Hobby, {through: 'user_hobby'});
this.Hobby.belongsToMany(this.User, {through: 'user_hobby'});
this.Post = this.sequelize.define('Post', {
name: {
type: DataTypes.STRING,
primaryKey: true
}
}, {timestamps: false});
this.Comment = this.sequelize.define('Comment', {
name: {
type: DataTypes.STRING,
primaryKey: true
}
}, {timestamps: false});
this.Tag = this.sequelize.define('Tag', {
name: {
type: DataTypes.STRING,
primaryKey: true
}
}, {timestamps: false});
this.Color = this.sequelize.define('Color', {
name: {
type: DataTypes.STRING,
primaryKey: true
}
}, {timestamps: false});
this.Footnote = this.sequelize.define('Footnote', {
name: {
type: DataTypes.STRING,
primaryKey: true
}
}, {timestamps: false});
this.Post.hasMany(this.Comment);
this.Comment.belongsTo(this.Post);
this.Post.belongsToMany(this.Tag, {through: 'post_tag'});
this.Tag.belongsToMany(this.Post, {through: 'post_tag'});
this.Post.hasMany(this.Footnote);
this.Footnote.belongsTo(this.Post);
this.User.hasMany(this.Post);
this.Post.belongsTo(this.User);
this.Tag.belongsTo(this.Color);
this.Color.hasMany(this.Tag);
});
/*
* many-to-many
*/
it('supports many-to-many association with where clause', function () {
return this.sequelize.sync({ force: true })
.then(() => Promise.join(
this.Project.bulkCreate(build('alpha', 'bravo', 'charlie')),
this.User.bulkCreate(build('Alice', 'Bob'))
))
.spread((projects, users) => Promise.join(
projects[0].addUser(users[0]),
projects[1].addUser(users[1]),
projects[2].addUser(users[0])
))
.then(() => this.Project.findAll({
include: [{
model: this.User,
where: {
name: 'Alice'
}
}],
order: ['name'],
limit: 1,
offset: 1
}))
.then(result => {
expect(result.length).to.equal(1);
expect(result[0].name).to.equal('charlie');
});
});
it('supports 2 levels of required many-to-many associations', function () {
return this.sequelize.sync({ force: true })
.then(() => Promise.join(
this.Project.bulkCreate(build('alpha', 'bravo', 'charlie')),
this.User.bulkCreate(build('Alice', 'Bob')),
this.Hobby.bulkCreate(build('archery', 'badminton'))
))
.spread((projects, users, hobbies) => Promise.join(
projects[0].addUser(users[0]),
projects[1].addUser(users[1]),
projects[2].addUser(users[0]),
users[0].addHobby(hobbies[0])
))
.then(() => this.Project.findAll({
include: [{
model: this.User,
required: true,
include: [{
model: this.Hobby,
required: true
}]
}],
order: ['name'],
limit: 1,
offset: 1
}))
.then(result => {
expect(result.length).to.equal(1);
expect(result[0].name).to.equal('charlie');
});
});
it('supports 2 levels of required many-to-many associations with where clause', function () {
return this.sequelize.sync({ force: true })
.then(() => Promise.join(
this.Project.bulkCreate(build('alpha', 'bravo', 'charlie')),
this.User.bulkCreate(build('Alice', 'Bob')),
this.Hobby.bulkCreate(build('archery', 'badminton'))
))
.spread((projects, users, hobbies) => Promise.join(
projects[0].addUser(users[0]),
projects[1].addUser(users[1]),
projects[2].addUser(users[0]),
users[0].addHobby(hobbies[0]),
users[1].addHobby(hobbies[1])
))
.then(() => this.Project.findAll({
include: [{
model: this.User,
required: true,
include: [{
model: this.Hobby,
where: {
name: 'archery'
}
}]
}],
order: ['name'],
limit: 1,
offset: 1
}))
.then(result => {
expect(result.length).to.equal(1);
expect(result[0].name).to.equal('charlie');
});
});
it('supports 2 levels of required many-to-many associations with through.where clause', function () {
return this.sequelize.sync({ force: true })
.then(() => Promise.join(
this.Project.bulkCreate(build('alpha', 'bravo', 'charlie')),
this.User.bulkCreate(build('Alice', 'Bob')),
this.Hobby.bulkCreate(build('archery', 'badminton'))
))
.spread((projects, users, hobbies) => Promise.join(
projects[0].addUser(users[0]),
projects[1].addUser(users[1]),
projects[2].addUser(users[0]),
users[0].addHobby(hobbies[0]),
users[1].addHobby(hobbies[1])
))
.then(() => this.Project.findAll({
include: [{
model: this.User,
required: true,
include: [{
model: this.Hobby,
required: true,
through: {
where: {
HobbyName: 'archery'
}
}
}]
}],
order: ['name'],
limit: 1,
offset: 1
}))
.then(result => {
expect(result.length).to.equal(1);
expect(result[0].name).to.equal('charlie');
});
});
it('supports 3 levels of required many-to-many associations with where clause', function () {
return this.sequelize.sync({ force: true })
.then(() => Promise.join(
this.Task.bulkCreate(build('alpha', 'bravo', 'charlie')),
this.Project.bulkCreate(build('alpha', 'bravo', 'charlie')),
this.User.bulkCreate(build('Alice', 'Bob', 'Charlotte')),
this.Hobby.bulkCreate(build('archery', 'badminton'))
))
.spread((tasks, projects, users, hobbies) => Promise.join(
tasks[0].addProject(projects[0]),
tasks[1].addProject(projects[1]),
tasks[2].addProject(projects[2]),
projects[0].addUser(users[0]),
projects[1].addUser(users[1]),
projects[2].addUser(users[0]),
users[0].addHobby(hobbies[0]),
users[1].addHobby(hobbies[1])
))
.then(() => this.Task.findAll({
include: [{
model: this.Project,
required: true,
include: [{
model: this.User,
required: true,
include: [{
model: this.Hobby,
where: {
name: 'archery'
}
}]
}]
}],
order: ['name'],
limit: 1,
offset: 1
}))
.then(result => {
expect(result.length).to.equal(1);
expect(result[0].name).to.equal('charlie');
});
});
it('supports required many-to-many association', function () {
return this.sequelize.sync({ force: true })
.then(() => Promise.join(
this.Project.bulkCreate(build('alpha', 'bravo', 'charlie')),
this.User.bulkCreate(build('Alice', 'Bob'))
))
.spread((projects, users) => Promise.join(
projects[0].addUser(users[0]), // alpha
projects[2].addUser(users[0]) // charlie
))
.then(() => this.Project.findAll({
include: [{
model: this.User,
required: true
}],
order: ['name'],
limit: 1,
offset: 1
}))
.then(result => {
expect(result.length).to.equal(1);
expect(result[0].name).to.equal('charlie');
});
});
it('supports 2 required many-to-many association', function () {
return this.sequelize.sync({ force: true })
.then(() => Promise.join(
this.Project.bulkCreate(build('alpha', 'bravo', 'charlie', 'delta')),
this.User.bulkCreate(build('Alice', 'Bob', 'David')),
this.Task.bulkCreate(build('a', 'c', 'd'))
))
.spread((projects, users, tasks) => Promise.join(
projects[0].addUser(users[0]),
projects[0].addTask(tasks[0]),
projects[1].addUser(users[1]),
projects[2].addTask(tasks[1]),
projects[3].addUser(users[2]),
projects[3].addTask(tasks[2])
))
.then(() => this.Project.findAll({
include: [{
model: this.User,
required: true
}, {
model: this.Task,
required: true
}],
order: ['name'],
limit: 1,
offset: 1
}))
.then(result => {
expect(result.length).to.equal(1);
expect(result[0].name).to.equal('delta');
});
});
/*
* one-to-one
*/
it('supports required one-to-many association', function () {
return this.sequelize.sync({ force: true })
.then(() => Promise.join(
this.Post.bulkCreate(build('alpha', 'bravo', 'charlie')),
this.Comment.bulkCreate(build('comment0', 'comment1'))
))
.spread((posts, comments) => Promise.join(
posts[0].addComment(comments[0]),
posts[2].addComment(comments[1])
))
.then(() => this.Post.findAll({
include: [{
model: this.Comment,
required: true
}],
order: ['name'],
limit: 1,
offset: 1
}))
.then(result => {
expect(result.length).to.equal(1);
expect(result[0].name).to.equal('charlie');
});
});
it('supports required one-to-many association with where clause', function () {
return this.sequelize.sync({ force: true })
.then(() => Promise.join(
this.Post.bulkCreate(build('alpha', 'bravo', 'charlie')),
this.Comment.bulkCreate(build('comment0', 'comment1'))
))
.spread((posts, comments) => Promise.join(
posts[0].addComment(comments[0]),
posts[2].addComment(comments[1])
))
.then(() => this.Post.findAll({
include: [{
model: this.Comment,
required: true,
where: {
name: {
[this.sequelize.Op.like]: 'comment%'
}
}
}],
order: ['name'],
limit: 1,
offset: 1
}))
.then(result => {
expect(result.length).to.equal(1);
expect(result[0].name).to.equal('charlie');
});
});
it('supports 2 levels of required one-to-many associations', function () {
return this.sequelize.sync({ force: true })
.then(() => Promise.join(
this.User.bulkCreate(build('Alice', 'Bob', 'Charlotte', 'David')),
this.Post.bulkCreate(build('post0', 'post1', 'post2')),
this.Comment.bulkCreate(build('comment0', 'comment1', 'comment2'))
))
.spread((users, posts, comments) => Promise.join(
users[0].addPost(posts[0]),
users[1].addPost(posts[1]),
users[3].addPost(posts[2]),
posts[0].addComment(comments[0]),
posts[2].addComment(comments[2])
))
.then(() => this.User.findAll({
include: [{
model: this.Post,
required: true,
include: [{
model: this.Comment,
required: true
}]
}],
order: ['name'],
limit: 1,
offset: 1
}))
.then(result => {
expect(result.length).to.equal(1);
expect(result[0].name).to.equal('David');
});
});
/*
* mixed many-to-many, one-to-many and many-to-one
*/
it('supports required one-to-many association with nested required many-to-many association', function () {
return this.sequelize.sync({ force: true })
.then(() => Promise.join(
this.User.bulkCreate(build('Alice', 'Bob', 'Charlotte', 'David')),
this.Post.bulkCreate(build('alpha', 'charlie', 'delta')),
this.Tag.bulkCreate(build('atag', 'btag', 'dtag'))
))
.spread((users, posts, tags) => Promise.join(
users[0].addPost(posts[0]),
users[2].addPost(posts[1]),
users[3].addPost(posts[2]),
posts[0].addTag([tags[0]]),
posts[2].addTag([tags[2]])
))
.then(() => this.User.findAll({
include: [{
model: this.Post,
required: true,
include: [{
model: this.Tag,
required: true
}]
}],
order: ['name'],
limit: 1,
offset: 1
}))
.then(result => {
expect(result.length).to.equal(1);
expect(result[0].name).to.equal('David');
});
});
it('supports required many-to-many association with nested required one-to-many association', function () {
return this.sequelize.sync({ force: true })
.then(() => Promise.join(
this.Project.bulkCreate(build('alpha', 'bravo', 'charlie', 'delta')),
this.User.bulkCreate(build('Alice', 'Bob', 'David')),
this.Post.bulkCreate(build('post0', 'post1', 'post2'))
))
.spread((projects, users, posts) => Promise.join(
projects[0].addUser(users[0]),
projects[1].addUser(users[1]),
projects[3].addUser(users[2]),
users[0].addPost([posts[0]]),
users[2].addPost([posts[2]])
))
.then(() => this.Project.findAll({
include: [{
model: this.User,
required: true,
include: [{
model: this.Post,
required: true,
duplicating: true
}]
}],
order: ['name'],
limit: 1,
offset: 1
}))
.then(result => {
expect(result.length).to.equal(1);
expect(result[0].name).to.equal('delta');
});
});
it('supports required many-to-one association with nested many-to-many association with where clause', function () {
return this.sequelize.sync({ force: true })
.then(() => Promise.join(
this.Post.bulkCreate(build('post0', 'post1', 'post2', 'post3')),
this.User.bulkCreate(build('Alice', 'Bob', 'Charlotte', 'David')),
this.Hobby.bulkCreate(build('archery', 'badminton'))
))
.spread((posts, users, hobbies) => Promise.join(
posts[0].setUser(users[0]),
posts[1].setUser(users[1]),
posts[3].setUser(users[3]),
users[0].addHobby(hobbies[0]),
users[1].addHobby(hobbies[1]),
users[3].addHobby(hobbies[0])
))
.then(() => this.Post.findAll({
include: [{
model: this.User,
required: true,
include: [{
model: this.Hobby,
where: {
name: 'archery'
}
}]
}],
order: ['name'],
limit: 1,
offset: 1
}))
.then(result => {
expect(result.length).to.equal(1);
expect(result[0].name).to.equal('post3');
});
});
it('supports required many-to-one association with nested many-to-many association with through.where clause', function () {
return this.sequelize.sync({ force: true })
.then(() => Promise.join(
this.Post.bulkCreate(build('post0', 'post1', 'post2', 'post3')),
this.User.bulkCreate(build('Alice', 'Bob', 'Charlotte', 'David')),
this.Hobby.bulkCreate(build('archery', 'badminton'))
))
.spread((posts, users, hobbies) => Promise.join(
posts[0].setUser(users[0]),
posts[1].setUser(users[1]),
posts[3].setUser(users[3]),
users[0].addHobby(hobbies[0]),
users[1].addHobby(hobbies[1]),
users[3].addHobby(hobbies[0])
))
.then(() => this.Post.findAll({
include: [{
model: this.User,
required: true,
include: [{
model: this.Hobby,
required: true,
through: {
where: {
HobbyName: 'archery'
}
}
}]
}],
order: ['name'],
limit: 1,
offset: 1
}))
.then(result => {
expect(result.length).to.equal(1);
expect(result[0].name).to.equal('post3');
});
});
it('supports required many-to-one association with multiple nested associations with where clause', function () {
return this.sequelize.sync({ force: true })
.then(() => Promise.join(
this.Comment.bulkCreate(build('comment0', 'comment1', 'comment2', 'comment3', 'comment4', 'comment5')),
this.Post.bulkCreate(build('post0', 'post1', 'post2', 'post3', 'post4')),
this.User.bulkCreate(build('Alice', 'Bob')),
this.Tag.bulkCreate(build('tag0', 'tag1'))
))
.spread((comments, posts, users, tags) => Promise.join(
comments[0].setPost(posts[0]),
comments[1].setPost(posts[1]),
comments[3].setPost(posts[2]),
comments[4].setPost(posts[3]),
comments[5].setPost(posts[4]),
posts[0].addTag(tags[0]),
posts[3].addTag(tags[0]),
posts[4].addTag(tags[0]),
posts[1].addTag(tags[1]),
posts[0].setUser(users[0]),
posts[2].setUser(users[0]),
posts[4].setUser(users[0]),
posts[1].setUser(users[1])
))
.then(() => this.Comment.findAll({
include: [{
model: this.Post,
required: true,
include: [{
model: this.User,
where: {
name: 'Alice'
}
}, {
model: this.Tag,
where: {
name: 'tag0'
}
}]
}],
order: ['name'],
limit: 1,
offset: 1
}))
.then(result => {
expect(result.length).to.equal(1);
expect(result[0].name).to.equal('comment5');
});
});
it('supports required many-to-one association with nested one-to-many association with where clause', function () {
return this.sequelize.sync({ force: true })
.then(() => Promise.join(
this.Comment.bulkCreate(build('comment0', 'comment1', 'comment2')),
this.Post.bulkCreate(build('post0', 'post1', 'post2')),
this.Footnote.bulkCreate(build('footnote0', 'footnote1', 'footnote2'))
))
.spread((comments, posts, footnotes) => Promise.join(
comments[0].setPost(posts[0]),
comments[1].setPost(posts[1]),
comments[2].setPost(posts[2]),
posts[0].addFootnote(footnotes[0]),
posts[1].addFootnote(footnotes[1]),
posts[2].addFootnote(footnotes[2])
))
.then(() => this.Comment.findAll({
include: [{
model: this.Post,
required: true,
include: [{
model: this.Footnote,
where: {
[Op.or]: [{
name: 'footnote0'
}, {
name: 'footnote2'
}]
}
}]
}],
order: ['name'],
limit: 1,
offset: 1
}))
.then(result => {
expect(result.length).to.equal(1);
expect(result[0].name).to.equal('comment2');
});
});
});
});
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!