Skip to content

Commit 8a670f0

Browse files
committed
fix: enable inq filters for referancesMany relations
Signed-off-by: Muhammad Aaqil <aaqilcs102@gmail.com>
1 parent 26bf0bd commit 8a670f0

2 files changed

Lines changed: 213 additions & 121 deletions

File tree

lib/sql.js

Lines changed: 173 additions & 115 deletions
Original file line numberDiff line numberDiff line change
@@ -912,7 +912,7 @@ SQLConnector.prototype._buildWhereObjById = function(model, id, data) {
912912
*/
913913
SQLConnector.prototype.buildUpdate = function(model, where, data, options) {
914914
const fields = this.buildFieldsForUpdate(model, data);
915-
return this._constructUpdateQuery(model, where, fields);
915+
return this._constructUpdateQuery(model, where, fields, options);
916916
};
917917

918918
/**
@@ -936,9 +936,9 @@ SQLConnector.prototype.buildReplace = function(model, where, data, options) {
936936
* @returns {Object} update query Constructed update query.
937937
* @private
938938
*/
939-
SQLConnector.prototype._constructUpdateQuery = function(model, where, fields) {
939+
SQLConnector.prototype._constructUpdateQuery = function(model, where, fields, options) {
940940
const updateClause = new ParameterizedSQL('UPDATE ' + this.tableEscaped(model));
941-
const whereClause = this.buildWhere(model, where);
941+
const whereClause = this.buildWhere(model, where, options);
942942
updateClause.merge([fields, whereClause]);
943943
return this.parameterize(updateClause);
944944
};
@@ -1007,8 +1007,25 @@ Connector.defineAliases(SQLConnector.prototype, 'replace', ['replaceAll']);
10071007
* @param {object} where An object for the where conditions
10081008
* @returns {ParameterizedSQL} The SQL WHERE clause
10091009
*/
1010-
SQLConnector.prototype.buildWhere = function(model, where) {
1011-
const whereClause = this._buildWhere(model, where);
1010+
SQLConnector.prototype.buildWhere = function(model, where, options) {
1011+
let relationType = '';
1012+
let relationKeyFrom = '';
1013+
1014+
if (options && options['model'] && options['model']['definition']) {
1015+
const {relations} = options['model']['definition'];
1016+
if (relations) {
1017+
const relationKeys = Object.keys(relations);
1018+
for (let relationIndex = 0; relationIndex < relationKeys.length; relationIndex++) {
1019+
const relationName = relationKeys[relationIndex];
1020+
const relation = relations[relationName];
1021+
relationType = relation.type;
1022+
relationKeyFrom = relation.keyFrom;
1023+
if (relationType === 'referencesMany') break;
1024+
}
1025+
}
1026+
}
1027+
const whereClause = this._buildWhere(model, where, relationType, relationKeyFrom);
1028+
10121029
if (whereClause.sql) {
10131030
whereClause.sql = 'WHERE ' + whereClause.sql;
10141031
}
@@ -1024,7 +1041,8 @@ SQLConnector.prototype.buildWhere = function(model, where) {
10241041
* @returns {ParameterizedSQL} The SQL expression
10251042
*/
10261043
SQLConnector.prototype.buildExpression =
1027-
function(columnName, operator, columnValue, propertyValue) {
1044+
function(relationDetails, columnName, operator, columnValue, propertyValue) {
1045+
const {relationType, relationKeyFrom} = relationDetails;
10281046
function buildClause(columnValue, separator, grouping) {
10291047
const values = [];
10301048
for (let i = 0, n = columnValue.length; i < n; i++) {
@@ -1067,8 +1085,24 @@ function(columnName, operator, columnValue, propertyValue) {
10671085
clause = buildClause(columnValue, ' AND ', false);
10681086
break;
10691087
case 'inq':
1070-
sqlExp += ' IN ';
1071-
clause = buildClause(columnValue, ',', true);
1088+
if (relationType === 'referencesMany' && `\`${relationKeyFrom}\`` === columnName) {
1089+
sqlExp = '';
1090+
if (columnValue.length === 1) {
1091+
sqlExp = `JSON_CONTAINS(${columnName}, CAST(${columnValue[0]} as JSON))`;
1092+
} else {
1093+
columnValue.forEach(value => {
1094+
sqlExp += `JSON_CONTAINS(${columnName}, CAST(${value} as JSON)) OR `;
1095+
});
1096+
const trimmed = sqlExp.trimEnd();
1097+
if (trimmed.endsWith('OR')) {
1098+
sqlExp = trimmed.slice(0, trimmed.lastIndexOf('OR'));
1099+
}
1100+
}
1101+
clause = null;
1102+
} else {
1103+
sqlExp += ' IN ';
1104+
clause = buildClause(columnValue, ',', true);
1105+
}
10721106
break;
10731107
case 'nin':
10741108
sqlExp += ' NOT IN ';
@@ -1102,125 +1136,149 @@ function(columnName, operator, columnValue, propertyValue) {
11021136
* @param where
11031137
* @returns {ParameterizedSQL}
11041138
*/
1105-
SQLConnector.prototype._buildWhere = function(model, where) {
1106-
let columnValue, sqlExp;
1107-
if (!where) {
1108-
return new ParameterizedSQL('');
1109-
}
1110-
if (typeof where !== 'object' || Array.isArray(where)) {
1111-
debug('Invalid value for where: %j', where);
1112-
return new ParameterizedSQL('');
1113-
}
1114-
const self = this;
1115-
const props = self.getModelDefinition(model).properties;
1116-
1117-
const whereStmts = [];
1118-
for (const key in where) {
1119-
const stmt = new ParameterizedSQL('', []);
1120-
// Handle and/or operators
1121-
if (key === 'and' || key === 'or') {
1122-
const branches = [];
1123-
let branchParams = [];
1124-
const clauses = where[key];
1125-
if (Array.isArray(clauses)) {
1126-
for (let i = 0, n = clauses.length; i < n; i++) {
1127-
const stmtForClause = self._buildWhere(model, clauses[i]);
1128-
if (stmtForClause.sql) {
1129-
stmtForClause.sql = '(' + stmtForClause.sql + ')';
1130-
branchParams = branchParams.concat(stmtForClause.params);
1131-
branches.push(stmtForClause.sql);
1139+
SQLConnector.prototype._buildWhere =
1140+
function(model, where, relationType, relationKeyFrom) {
1141+
let columnValue, sqlExp;
1142+
if (!where) {
1143+
return new ParameterizedSQL('');
1144+
}
1145+
if (typeof where !== 'object' || Array.isArray(where)) {
1146+
debug('Invalid value for where: %j', where);
1147+
return new ParameterizedSQL('');
1148+
}
1149+
const self = this;
1150+
const props = self.getModelDefinition(model).properties;
1151+
1152+
const whereStmts = [];
1153+
for (const key in where) {
1154+
const stmt = new ParameterizedSQL('', []);
1155+
// Handle and/or operators
1156+
if (key === 'and' || key === 'or') {
1157+
const branches = [];
1158+
let branchParams = [];
1159+
const clauses = where[key];
1160+
if (Array.isArray(clauses)) {
1161+
for (let i = 0, n = clauses.length; i < n; i++) {
1162+
const stmtForClause = self
1163+
._buildWhere(model, clauses[i], relationType, relationKeyFrom);
1164+
if (stmtForClause.sql) {
1165+
stmtForClause.sql = '(' + stmtForClause.sql + ')';
1166+
branchParams = branchParams.concat(stmtForClause.params);
1167+
branches.push(stmtForClause.sql);
1168+
}
11321169
}
1170+
stmt.merge({
1171+
sql: '(' + branches.join(' ' + key.toUpperCase() + ' ') + ')',
1172+
params: branchParams,
1173+
});
1174+
whereStmts.push(stmt);
1175+
continue;
11331176
}
1134-
stmt.merge({
1135-
sql: '(' + branches.join(' ' + key.toUpperCase() + ' ') + ')',
1136-
params: branchParams,
1137-
});
1138-
whereStmts.push(stmt);
1177+
// The value is not an array, fall back to regular fields
1178+
}
1179+
const p = props[key];
1180+
if (p == null) {
1181+
// Unknown property, ignore it
1182+
debug('Unknown property %s is skipped for model %s', key, model);
11391183
continue;
11401184
}
1141-
// The value is not an array, fall back to regular fields
1142-
}
1143-
const p = props[key];
1144-
if (p == null) {
1145-
// Unknown property, ignore it
1146-
debug('Unknown property %s is skipped for model %s', key, model);
1147-
continue;
1148-
}
1149-
// eslint-disable one-var
1150-
let expression = where[key];
1151-
const columnName = self.columnEscaped(model, key);
1152-
// eslint-enable one-var
1153-
if (expression === null || expression === undefined) {
1154-
stmt.merge(columnName + ' IS NULL');
1155-
} else if (expression && expression.constructor === Object) {
1156-
const operator = Object.keys(expression)[0];
1157-
// Get the expression without the operator
1158-
expression = expression[operator];
1159-
if (operator === 'inq' || operator === 'nin' || operator === 'between') {
1160-
columnValue = [];
1161-
if (Array.isArray(expression)) {
1162-
// Column value is a list
1163-
for (let j = 0, m = expression.length; j < m; j++) {
1164-
columnValue.push(this.toColumnValue(p, expression[j]));
1185+
// eslint-disable one-var
1186+
let expression = where[key];
1187+
const columnName = self.columnEscaped(model, key);
1188+
// eslint-enable one-var
1189+
if (expression === null || expression === undefined) {
1190+
stmt.merge(columnName + ' IS NULL');
1191+
} else if (expression && expression.constructor === Object) {
1192+
const operator = Object.keys(expression)[0];
1193+
// Get the expression without the operator
1194+
expression = expression[operator];
1195+
if (operator === 'inq' || operator === 'nin' || operator === 'between') {
1196+
columnValue = [];
1197+
if (Array.isArray(expression)) {
1198+
// Column value is a list
1199+
for (let j = 0, m = expression.length; j < m; j++) {
1200+
columnValue.push(this.toColumnValue(p, expression[j]));
1201+
}
1202+
} else {
1203+
columnValue.push(this.toColumnValue(p, expression));
11651204
}
1205+
if (operator === 'between') {
1206+
// BETWEEN v1 AND v2
1207+
const v1 = columnValue[0] === undefined ? null : columnValue[0];
1208+
const v2 = columnValue[1] === undefined ? null : columnValue[1];
1209+
columnValue = [v1, v2];
1210+
} else {
1211+
// IN (v1,v2,v3) or NOT IN (v1,v2,v3)
1212+
if (columnValue.length === 0) {
1213+
if (operator === 'inq') {
1214+
columnValue = [null];
1215+
} else {
1216+
// nin () is true
1217+
continue;
1218+
}
1219+
}
1220+
}
1221+
} else if (operator === 'regexp' && expression instanceof RegExp) {
1222+
// do not coerce RegExp based on property definitions
1223+
columnValue = expression;
11661224
} else {
1167-
columnValue.push(this.toColumnValue(p, expression));
1225+
columnValue = this.toColumnValue(p, expression);
11681226
}
1169-
if (operator === 'between') {
1170-
// BETWEEN v1 AND v2
1171-
const v1 = columnValue[0] === undefined ? null : columnValue[0];
1172-
const v2 = columnValue[1] === undefined ? null : columnValue[1];
1173-
columnValue = [v1, v2];
1227+
sqlExp = self
1228+
.buildExpression(
1229+
{relationType, relationKeyFrom},
1230+
columnName, operator, columnValue,
1231+
p,
1232+
);
1233+
if (
1234+
relationType === 'referencesMany' &&
1235+
`\`${relationKeyFrom}\`` === columnName
1236+
) {
1237+
stmt.merge(sqlExp, columnValue);
11741238
} else {
1175-
// IN (v1,v2,v3) or NOT IN (v1,v2,v3)
1176-
if (columnValue.length === 0) {
1177-
if (operator === 'inq') {
1178-
columnValue = [null];
1179-
} else {
1180-
// nin () is true
1181-
continue;
1182-
}
1183-
}
1239+
stmt.merge(sqlExp);
11841240
}
1185-
} else if (operator === 'regexp' && expression instanceof RegExp) {
1186-
// do not coerce RegExp based on property definitions
1187-
columnValue = expression;
1188-
} else {
1189-
columnValue = this.toColumnValue(p, expression);
1190-
}
1191-
sqlExp = self.buildExpression(columnName, operator, columnValue, p);
1192-
stmt.merge(sqlExp);
1193-
} else {
1194-
// The expression is the field value, not a condition
1195-
columnValue = self.toColumnValue(p, expression);
1196-
if (columnValue === null) {
1197-
stmt.merge(columnName + ' IS NULL');
11981241
} else {
1199-
if (columnValue instanceof ParameterizedSQL) {
1200-
stmt.merge(columnName + '=').merge(columnValue);
1242+
// The expression is the field value, not a condition
1243+
columnValue = self.toColumnValue(p, expression);
1244+
if (columnValue === null) {
1245+
stmt.merge(columnName + ' IS NULL');
12011246
} else {
1202-
stmt.merge({
1203-
sql: columnName + '=?',
1204-
params: [columnValue],
1205-
});
1247+
if (columnValue instanceof ParameterizedSQL) {
1248+
stmt.merge(columnName + '=').merge(columnValue);
1249+
} else {
1250+
if (
1251+
relationType === 'referencesMany' &&
1252+
`\`${relationKeyFrom}\`` === columnName
1253+
) {
1254+
stmt.merge({
1255+
sql: `JSON_CONTAINS(${columnName}, CAST(? as JSON))`,
1256+
params: [columnValue],
1257+
});
1258+
} else {
1259+
stmt.merge({
1260+
sql: columnName + '=?',
1261+
params: [columnValue],
1262+
});
1263+
}
1264+
}
12061265
}
12071266
}
1267+
whereStmts.push(stmt);
12081268
}
1209-
whereStmts.push(stmt);
1210-
}
1211-
let params = [];
1212-
const sqls = [];
1213-
for (let k = 0, s = whereStmts.length; k < s; k++) {
1214-
if (!whereStmts[k].sql) continue;
1215-
sqls.push(whereStmts[k].sql);
1216-
params = params.concat(whereStmts[k].params);
1217-
}
1218-
const whereStmt = new ParameterizedSQL({
1219-
sql: sqls.join(' AND '),
1220-
params: params,
1221-
});
1222-
return whereStmt;
1223-
};
1269+
let params = [];
1270+
const sqls = [];
1271+
for (let k = 0, s = whereStmts.length; k < s; k++) {
1272+
if (!whereStmts[k].sql) continue;
1273+
sqls.push(whereStmts[k].sql);
1274+
params = params.concat(whereStmts[k].params);
1275+
}
1276+
const whereStmt = new ParameterizedSQL({
1277+
sql: sqls.join(' AND '),
1278+
params: params,
1279+
});
1280+
return whereStmt;
1281+
};
12241282

12251283
/**
12261284
* Build the ORDER BY clause
@@ -1453,7 +1511,7 @@ SQLConnector.prototype.buildSelect = function(model, filter, options) {
14531511

14541512
if (filter) {
14551513
if (filter.where) {
1456-
const whereStmt = this.buildWhere(model, filter.where);
1514+
const whereStmt = this.buildWhere(model, filter.where, options);
14571515
selectStmt.merge(whereStmt);
14581516
}
14591517

@@ -1592,7 +1650,7 @@ SQLConnector.prototype.count = function(model, where, options, cb) {
15921650

15931651
let stmt = new ParameterizedSQL('SELECT count(*) as "cnt" FROM ' +
15941652
this.tableEscaped(model));
1595-
stmt = stmt.merge(this.buildWhere(model, where));
1653+
stmt = stmt.merge(this.buildWhere(model, where, options));
15961654
stmt = this.parameterize(stmt);
15971655
this.execute(stmt.sql, stmt.params, options,
15981656
function(err, res) {

0 commit comments

Comments
 (0)