Skip to content

Commit 825eb75

Browse files
committed
added: preliminary support for include.on, include.or and .$ where query support
1 parent b2a3152 commit 825eb75

7 files changed

Lines changed: 107 additions & 8 deletions

File tree

changelog.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
# Next
2+
- [ADDED] Preliminary support for `include.on`.
23
- [FIXED] Partial rollback of datatype validations by hiding it behind the `typeValidation` flag.
34
- [FIXED] Don't try to select the primary key for models without primary key [#4607](https://github.com/sequelize/sequelize/issues/4607)
45
- [FIXED] Apply `attributes` when including a scoped model. [#4625](https://github.com/sequelize/sequelize/issues/4625)

docs/docs/querying.md

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -113,11 +113,11 @@ Post.update({
113113
```js
114114
$and: {a: 5} // AND (a = 5)
115115
$or: [{a: 5}, {a: 6}] // (a = 5 OR a = 6)
116-
$gt: 6, // id > 6
117-
$gte: 6, // id >= 6
118-
$lt: 10, // id < 10
119-
$lte: 10, // id <= 10
120-
$ne: 20, // id != 20
116+
$gt: 6, // > 6
117+
$gte: 6, // >= 6
118+
$lt: 10, // < 10
119+
$lte: 10, // <= 10
120+
$ne: 20, // != 20
121121
$between: [6, 10], // BETWEEN 6 AND 10
122122
$notBetween: [11, 15], // NOT BETWEEN 11 AND 15
123123
$in: [1, 2], // IN [1, 2]
@@ -132,6 +132,8 @@ $overlap: [1, 2] // && [1, 2] (PG array overlap operator)
132132
$contains: [1, 2] // @> [1, 2] (PG array contains operator)
133133
$contained: [1, 2] // <@ [1, 2] (PG array contained by operator)
134134
$any: [2,3] // ANY ARRAY[2, 3]::INTEGER (PG only)
135+
136+
$eq: '$user.organization_id$' // = "user"."organization_id", with dialect specific column identifiers, PG in this example
135137
```
136138

137139
### Combinations

lib/dialects/abstract/query-generator.js

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1621,14 +1621,24 @@ var QueryGenerator = {
16211621

16221622
joinOn += ' = ' + this.quoteIdentifier(asRight) + '.' + this.quoteIdentifier(fieldRight);
16231623

1624+
if (include.on) {
1625+
joinOn = this.whereItemsQuery(include.on, {
1626+
prefix: this.sequelize.literal(this.quoteIdentifier(asRight)),
1627+
model: include.model
1628+
});
1629+
}
16241630

16251631
if (include.where) {
16261632
joinWhere = this.whereItemsQuery(include.where, {
16271633
prefix: this.sequelize.literal(this.quoteIdentifier(asRight)),
16281634
model: include.model
16291635
});
16301636
if (joinWhere) {
1631-
joinOn += ' AND ' + joinWhere;
1637+
if (include.or) {
1638+
joinOn += ' OR ' + joinWhere;
1639+
} else {
1640+
joinOn += ' AND ' + joinWhere;
1641+
}
16321642
}
16331643
}
16341644

@@ -2171,17 +2181,25 @@ var QueryGenerator = {
21712181
comparator = 'IS NOT';
21722182
}
21732183

2174-
value = this.escape(value, field);
2184+
if (Utils.isColString(value)) {
2185+
value = value.substr(1, value.length - 2).split('.').map(this.quoteIdentifier.bind(this)).join('.');
2186+
} else {
2187+
value = this.escape(value, field);
2188+
}
21752189
}
21762190

21772191
if (key) {
2192+
var prefix = true;
21782193
if (key._isSequelizeMethod) {
21792194
key = this.handleSequelizeMethod(key);
2195+
} else if (Utils.isColString(key)) {
2196+
key = key.substr(1, key.length - 2).split('.').map(this.quoteIdentifier.bind(this)).join('.');
2197+
prefix = false;
21802198
} else {
21812199
key = this.quoteIdentifier(key);
21822200
}
21832201

2184-
if (options.prefix) {
2202+
if (options.prefix && prefix) {
21852203
if (options.prefix instanceof Utils.literal) {
21862204
key = [this.handleSequelizeMethod(options.prefix), key].join('.');
21872205
} else {

lib/model.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1300,6 +1300,8 @@ Model.prototype.all = function(options) {
13001300
* @param {String} [options.include[].as] The alias of the relation, in case the model you want to eagerly load is aliassed. For `hasOne` / `belongsTo`, this should be the singular name, and for `hasMany`, it should be the plural
13011301
* @param {Association} [options.include[].association] The association you want to eagerly load. (This can be used instead of providing a model/as pair)
13021302
* @param {Object} [options.include[].where] Where clauses to apply to the child models. Note that this converts the eager load to an inner join, unless you explicitly set `required: false`
1303+
* @param {Boolean} [options.include[].or=false] Whether to bind the ON and WHERE clause together by OR instead of AND.
1304+
* @param {Object} [options.include[].on] Supply your own ON condition for the join.
13031305
* @param {Array<String>} [options.include[].attributes] A list of attributes to select from the child model
13041306
* @param {Boolean} [options.include[].required] If true, converts to an inner join, which means that the parent model will only be loaded if it has any matching children. True if `include.where` is set, false otherwise.
13051307
* @param {Boolean} [options.include[].separate] If true, runs a separate query to fetch the associated instances, only supported for hasMany associations

lib/utils.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,7 @@ var Utils = module.exports = {
125125
if (attributes) {
126126
for (attribute in attributes) {
127127
rawAttribute = Model.rawAttributes[attribute];
128+
128129
if (rawAttribute && rawAttribute.field !== rawAttribute.fieldName) {
129130
attributes[rawAttribute.field] = attributes[attribute];
130131
delete attributes[attribute];
@@ -168,6 +169,11 @@ var Utils = module.exports = {
168169

169170
return values;
170171
},
172+
173+
isColString: function(value) {
174+
return typeof value === 'string' && value.substr(0, 1) === '$' && value.substr(value.length - 1, 1) === '$';
175+
},
176+
171177
argsArePrimaryKeys: function(args, primaryKeys) {
172178
var result = (args.length === Object.keys(primaryKeys).length);
173179
if (result) {

test/unit/sql/join-include-query.test.js

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,9 @@ suite(Support.getTestDialectTeaser('SQL'), function() {
5353
ownerId: {
5454
type: Sequelize.INTEGER,
5555
field: 'owner_id'
56+
},
57+
public: {
58+
type: Sequelize.BOOLEAN
5659
}
5760
}, {
5861
tableName: 'company'
@@ -86,6 +89,19 @@ suite(Support.getTestDialectTeaser('SQL'), function() {
8689
default: "LEFT OUTER JOIN [company] AS [Company] ON [User].[company_id] = [Company].[id]"
8790
});
8891

92+
testsql({
93+
model: User,
94+
subQuery: false,
95+
include: Sequelize.Model.$validateIncludedElements({
96+
model: User,
97+
include: [
98+
{association: User.Company, where: {public: true}, or: true}
99+
]
100+
}).include[0]
101+
}, {
102+
default: "INNER JOIN [company] AS [Company] ON [User].[company_id] = [Company].[id] OR [Company].[public] = true"
103+
});
104+
89105
testsql({
90106
model: User,
91107
subQuery: true,
@@ -243,5 +259,36 @@ suite(Support.getTestDialectTeaser('SQL'), function() {
243259
// The primary key of the main model will be aliased because it's coming from a subquery that the :M join is not a part of
244260
default: "LEFT OUTER JOIN [task] AS [Tasks] ON [User].[id] = [Tasks].[user_id]"
245261
});
262+
263+
testsql({
264+
model: User,
265+
subQuery: false,
266+
include: Sequelize.Model.$validateIncludedElements({
267+
model: User,
268+
include: [
269+
{association: User.Tasks, on: {
270+
$or: [
271+
{'$User.id_user$': '$Tasks.user_id$'},
272+
{'$Tasks.user_id$': 2}
273+
]
274+
}}
275+
]
276+
}).include[0]
277+
}, {
278+
default: "LEFT OUTER JOIN [task] AS [Tasks] ON ([User].[id_user] = [Tasks].[user_id] OR [Tasks].[user_id] = 2)"
279+
});
280+
281+
testsql({
282+
model: User,
283+
subQuery: false,
284+
include: Sequelize.Model.$validateIncludedElements({
285+
model: User,
286+
include: [
287+
{association: User.Tasks, on: {'user_id': '$User.alternative_id$'}}
288+
]
289+
}).include[0]
290+
}, {
291+
default: "LEFT OUTER JOIN [task] AS [Tasks] ON [Tasks].[user_id] = [User].[alternative_id]"
292+
});
246293
});
247294
});

test/unit/sql/where.test.js

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -306,12 +306,35 @@ suite(Support.getTestDialectTeaser('SQL'), function() {
306306
});
307307
});
308308

309+
suite('$col', function () {
310+
testsql('userId', '$user.id$', {
311+
default: '[userId] = [user].[id]'
312+
});
313+
314+
testsql('$or', [
315+
{'ownerId': '$user.id$'},
316+
{'ownerId': '$organization.id$'}
317+
], {
318+
default: '([ownerId] = [user].[id] OR [ownerId] = [organization].[id])'
319+
});
320+
321+
testsql('$organization.id$', '$user.organizationId$', {
322+
default: '[organization].[id] = [user].[organizationId]'
323+
});
324+
});
325+
309326
suite('$gt', function () {
310327
testsql('rank', {
311328
$gt: 2
312329
}, {
313330
default: '[rank] > 2'
314331
});
332+
333+
testsql('created_at', {
334+
$lt: '$updated_at$'
335+
}, {
336+
default: '[created_at] < [updated_at]'
337+
});
315338
});
316339

317340
suite('$raw', function () {

0 commit comments

Comments
 (0)