Skip to content

Commit 84723f3

Browse files
committed
fix: enable inq filters for referancesMany relations
Signed-off-by: Muhammad Aaqil <[email protected]>
1 parent 26bf0bd commit 84723f3

File tree

1 file changed

+169
-115
lines changed

1 file changed

+169
-115
lines changed

lib/sql.js

Lines changed: 169 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,23 @@ 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+
if (options && options['model'] && options['model']['definition']) {
1014+
const {relations} = options['model']['definition'];
1015+
if (relations) {
1016+
const relationKeys = Object.keys(relations);
1017+
for (let relationIndex = 0; relationIndex < relationKeys.length; relationIndex++) {
1018+
const relationName = relationKeys[relationIndex];
1019+
const relation = relations[relationName];
1020+
relationType = relation.type;
1021+
relationKeyFrom = relation.keyFrom;
1022+
if (relationType === 'referencesMany') break;
1023+
}
1024+
}
1025+
}
1026+
const whereClause = this._buildWhere(model, where, relationType, relationKeyFrom);
10121027
if (whereClause.sql) {
10131028
whereClause.sql = 'WHERE ' + whereClause.sql;
10141029
}
@@ -1024,7 +1039,8 @@ SQLConnector.prototype.buildWhere = function(model, where) {
10241039
* @returns {ParameterizedSQL} The SQL expression
10251040
*/
10261041
SQLConnector.prototype.buildExpression =
1027-
function(columnName, operator, columnValue, propertyValue) {
1042+
function(relationDetails, columnName, operator, columnValue, propertyValue) {
1043+
const {relationType, relationKeyFrom} = relationDetails;
10281044
function buildClause(columnValue, separator, grouping) {
10291045
const values = [];
10301046
for (let i = 0, n = columnValue.length; i < n; i++) {
@@ -1067,8 +1083,21 @@ function(columnName, operator, columnValue, propertyValue) {
10671083
clause = buildClause(columnValue, ' AND ', false);
10681084
break;
10691085
case 'inq':
1070-
sqlExp += ' IN ';
1071-
clause = buildClause(columnValue, ',', true);
1086+
if (relationType === 'referencesMany' && `\`${relationKeyFrom}\`` === columnName) {
1087+
sqlExp = '';
1088+
if (columnValue.length === 1) {
1089+
sqlExp = `JSON_CONTAINS(${columnName}, CAST(${columnValue[0]} as JSON))`;
1090+
} else {
1091+
columnValue.forEach(value => {
1092+
sqlExp += `JSON_CONTAINS(${columnName}, CAST(${value} as JSON)) OR `;
1093+
});
1094+
sqlExp = sqlExp.replace(/\s+OR\s*$/, '');
1095+
}
1096+
clause = null;
1097+
} else {
1098+
sqlExp += ' IN ';
1099+
clause = buildClause(columnValue, ',', true);
1100+
}
10721101
break;
10731102
case 'nin':
10741103
sqlExp += ' NOT IN ';
@@ -1102,125 +1131,150 @@ function(columnName, operator, columnValue, propertyValue) {
11021131
* @param where
11031132
* @returns {ParameterizedSQL}
11041133
*/
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);
1134+
SQLConnector.prototype._buildWhere =
1135+
function(model, where, relationType, relationKeyFrom) {
1136+
let columnValue, sqlExp;
1137+
if (!where) {
1138+
return new ParameterizedSQL('');
1139+
}
1140+
if (typeof where !== 'object' || Array.isArray(where)) {
1141+
debug('Invalid value for where: %j', where);
1142+
return new ParameterizedSQL('');
1143+
}
1144+
const self = this;
1145+
const props = self.getModelDefinition(model).properties;
1146+
1147+
const whereStmts = [];
1148+
for (const key in where) {
1149+
const stmt = new ParameterizedSQL('', []);
1150+
// Handle and/or operators
1151+
if (key === 'and' || key === 'or') {
1152+
const branches = [];
1153+
let branchParams = [];
1154+
const clauses = where[key];
1155+
if (Array.isArray(clauses)) {
1156+
for (let i = 0, n = clauses.length; i < n; i++) {
1157+
const stmtForClause = self
1158+
._buildWhere(model, clauses[i], relationType, relationKeyFrom);
1159+
if (stmtForClause.sql) {
1160+
stmtForClause.sql = '(' + stmtForClause.sql + ')';
1161+
branchParams = branchParams.concat(stmtForClause.params);
1162+
branches.push(stmtForClause.sql);
1163+
}
11321164
}
1165+
stmt.merge({
1166+
sql: '(' + branches.join(' ' + key.toUpperCase() + ' ') + ')',
1167+
params: branchParams,
1168+
});
1169+
whereStmts.push(stmt);
1170+
continue;
11331171
}
1134-
stmt.merge({
1135-
sql: '(' + branches.join(' ' + key.toUpperCase() + ' ') + ')',
1136-
params: branchParams,
1137-
});
1138-
whereStmts.push(stmt);
1172+
// The value is not an array, fall back to regular fields
1173+
}
1174+
const p = props[key];
1175+
if (p == null) {
1176+
// Unknown property, ignore it
1177+
debug('Unknown property %s is skipped for model %s', key, model);
11391178
continue;
11401179
}
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]));
1180+
// eslint-disable one-var
1181+
let expression = where[key];
1182+
const columnName = self.columnEscaped(model, key);
1183+
// eslint-enable one-var
1184+
if (expression === null || expression === undefined) {
1185+
stmt.merge(columnName + ' IS NULL');
1186+
} else if (expression && expression.constructor === Object) {
1187+
const operator = Object.keys(expression)[0];
1188+
// Get the expression without the operator
1189+
expression = expression[operator];
1190+
if (operator === 'inq' || operator === 'nin' || operator === 'between') {
1191+
columnValue = [];
1192+
if (Array.isArray(expression)) {
1193+
// Column value is a list
1194+
for (let j = 0, m = expression.length; j < m; j++) {
1195+
columnValue.push(this.toColumnValue(p, expression[j]));
1196+
}
1197+
} else {
1198+
columnValue.push(this.toColumnValue(p, expression));
11651199
}
1200+
if (operator === 'between') {
1201+
// BETWEEN v1 AND v2
1202+
const v1 = columnValue[0] === undefined ? null : columnValue[0];
1203+
const v2 = columnValue[1] === undefined ? null : columnValue[1];
1204+
columnValue = [v1, v2];
1205+
} else {
1206+
// IN (v1,v2,v3) or NOT IN (v1,v2,v3)
1207+
if (columnValue.length === 0) {
1208+
if (operator === 'inq') {
1209+
columnValue = [null];
1210+
} else {
1211+
// nin () is true
1212+
continue;
1213+
}
1214+
}
1215+
}
1216+
} else if (operator === 'regexp' && expression instanceof RegExp) {
1217+
// do not coerce RegExp based on property definitions
1218+
columnValue = expression;
11661219
} else {
1167-
columnValue.push(this.toColumnValue(p, expression));
1220+
columnValue = this.toColumnValue(p, expression);
11681221
}
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];
1222+
if (`\`${relationKeyFrom}\`` !== columnName) { relationType = ''; }
1223+
sqlExp = self
1224+
.buildExpression(
1225+
{relationType, relationKeyFrom},
1226+
columnName, operator, columnValue,
1227+
p,
1228+
);
1229+
if (
1230+
relationType === 'referencesMany' &&
1231+
`\`${relationKeyFrom}\`` === columnName
1232+
) {
1233+
stmt.merge(sqlExp, columnValue);
11741234
} 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-
}
1235+
stmt.merge(sqlExp);
11841236
}
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');
11981237
} else {
1199-
if (columnValue instanceof ParameterizedSQL) {
1200-
stmt.merge(columnName + '=').merge(columnValue);
1238+
// The expression is the field value, not a condition
1239+
columnValue = self.toColumnValue(p, expression);
1240+
if (columnValue === null) {
1241+
stmt.merge(columnName + ' IS NULL');
12011242
} else {
1202-
stmt.merge({
1203-
sql: columnName + '=?',
1204-
params: [columnValue],
1205-
});
1243+
if (columnValue instanceof ParameterizedSQL) {
1244+
stmt.merge(columnName + '=').merge(columnValue);
1245+
} else {
1246+
if (
1247+
relationType === 'referencesMany' &&
1248+
`\`${relationKeyFrom}\`` === columnName
1249+
) {
1250+
stmt.merge({
1251+
sql: `JSON_CONTAINS(${columnName}, CAST(? as JSON))`,
1252+
params: [columnValue],
1253+
});
1254+
} else {
1255+
stmt.merge({
1256+
sql: columnName + '=?',
1257+
params: [columnValue],
1258+
});
1259+
}
1260+
}
12061261
}
12071262
}
1263+
whereStmts.push(stmt);
12081264
}
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-
};
1265+
let params = [];
1266+
const sqls = [];
1267+
for (let k = 0, s = whereStmts.length; k < s; k++) {
1268+
if (!whereStmts[k].sql) continue;
1269+
sqls.push(whereStmts[k].sql);
1270+
params = params.concat(whereStmts[k].params);
1271+
}
1272+
const whereStmt = new ParameterizedSQL({
1273+
sql: sqls.join(' AND '),
1274+
params: params,
1275+
});
1276+
return whereStmt;
1277+
};
12241278

12251279
/**
12261280
* Build the ORDER BY clause
@@ -1453,7 +1507,7 @@ SQLConnector.prototype.buildSelect = function(model, filter, options) {
14531507

14541508
if (filter) {
14551509
if (filter.where) {
1456-
const whereStmt = this.buildWhere(model, filter.where);
1510+
const whereStmt = this.buildWhere(model, filter.where, options);
14571511
selectStmt.merge(whereStmt);
14581512
}
14591513

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

15931647
let stmt = new ParameterizedSQL('SELECT count(*) as "cnt" FROM ' +
15941648
this.tableEscaped(model));
1595-
stmt = stmt.merge(this.buildWhere(model, where));
1649+
stmt = stmt.merge(this.buildWhere(model, where, options));
15961650
stmt = this.parameterize(stmt);
15971651
this.execute(stmt.sql, stmt.params, options,
15981652
function(err, res) {

0 commit comments

Comments
 (0)