Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 57 additions & 19 deletions lib/sql.js
Original file line number Diff line number Diff line change
Expand Up @@ -912,7 +912,7 @@ SQLConnector.prototype._buildWhereObjById = function(model, id, data) {
*/
SQLConnector.prototype.buildUpdate = function(model, where, data, options) {
const fields = this.buildFieldsForUpdate(model, data);
return this._constructUpdateQuery(model, where, fields);
return this._constructUpdateQuery(model, where, fields, options);
};

/**
Expand All @@ -936,9 +936,9 @@ SQLConnector.prototype.buildReplace = function(model, where, data, options) {
* @returns {Object} update query Constructed update query.
* @private
*/
SQLConnector.prototype._constructUpdateQuery = function(model, where, fields) {
SQLConnector.prototype._constructUpdateQuery = function(model, where, fields, options) {
const updateClause = new ParameterizedSQL('UPDATE ' + this.tableEscaped(model));
const whereClause = this.buildWhere(model, where);
const whereClause = this.buildWhere(model, where, options);
updateClause.merge([fields, whereClause]);
return this.parameterize(updateClause);
};
Expand Down Expand Up @@ -1007,8 +1007,9 @@ Connector.defineAliases(SQLConnector.prototype, 'replace', ['replaceAll']);
* @param {object} where An object for the where conditions
* @returns {ParameterizedSQL} The SQL WHERE clause
*/
SQLConnector.prototype.buildWhere = function(model, where) {
const whereClause = this._buildWhere(model, where);
SQLConnector.prototype.buildWhere = function(model, where, options) {
const whereClause = this._buildWhere(model, where, options);

if (whereClause.sql) {
whereClause.sql = 'WHERE ' + whereClause.sql;
}
Expand Down Expand Up @@ -1043,6 +1044,7 @@ function(columnName, operator, columnValue, propertyValue) {
}

let sqlExp = columnName;
let jsonContainsParams = [];
let clause;
if (columnValue instanceof ParameterizedSQL) {
clause = columnValue;
Expand All @@ -1066,6 +1068,29 @@ function(columnName, operator, columnValue, propertyValue) {
sqlExp += ' BETWEEN ';
clause = buildClause(columnValue, ' AND ', false);
break;
case 'jsonContains':
// Handle JSON array containment for referencesMany relations
sqlExp = '';
// If columnValue is a JSON string array, parse it
if (typeof columnValue === 'string') {
try {
columnValue = JSON.parse(columnValue);
} catch (e) {
columnValue = [columnValue];
}
}
if (!Array.isArray(columnValue)) {
columnValue = [columnValue];
}
const conditions = [];
jsonContainsParams = [];
columnValue.forEach(value => {
conditions.push(`JSON_CONTAINS(${columnName}, CAST(? as JSON))`);
jsonContainsParams.push(value);
});
sqlExp = conditions.join(' OR ');
clause = null;
break;
case 'inq':
sqlExp += ' IN ';
clause = buildClause(columnValue, ',', true);
Expand All @@ -1092,6 +1117,9 @@ function(columnName, operator, columnValue, propertyValue) {
sqlExp += ' REGEXP ';
break;
}
if (jsonContainsParams.length) {
return new ParameterizedSQL({sql: sqlExp, params: jsonContainsParams});
}
const stmt = ParameterizedSQL.join([sqlExp, clause], '');
return stmt;
};
Expand All @@ -1102,7 +1130,7 @@ function(columnName, operator, columnValue, propertyValue) {
* @param where
* @returns {ParameterizedSQL}
*/
SQLConnector.prototype._buildWhere = function(model, where) {
SQLConnector.prototype._buildWhere = function(model, where, options) {
let columnValue, sqlExp;
if (!where) {
return new ParameterizedSQL('');
Expand All @@ -1124,7 +1152,7 @@ SQLConnector.prototype._buildWhere = function(model, where) {
const clauses = where[key];
if (Array.isArray(clauses)) {
for (let i = 0, n = clauses.length; i < n; i++) {
const stmtForClause = self._buildWhere(model, clauses[i]);
const stmtForClause = self._buildWhere(model, clauses[i], options);
if (stmtForClause.sql) {
stmtForClause.sql = '(' + stmtForClause.sql + ')';
branchParams = branchParams.concat(stmtForClause.params);
Expand Down Expand Up @@ -1156,7 +1184,12 @@ SQLConnector.prototype._buildWhere = function(model, where) {
const operator = Object.keys(expression)[0];
// Get the expression without the operator
expression = expression[operator];
if (operator === 'inq' || operator === 'nin' || operator === 'between') {
if (
operator === 'inq' ||
operator === 'nin' ||
operator === 'between' ||
operator === 'jsonContains'
) {
columnValue = [];
if (Array.isArray(expression)) {
// Column value is a list
Expand Down Expand Up @@ -1189,23 +1222,28 @@ SQLConnector.prototype._buildWhere = function(model, where) {
columnValue = this.toColumnValue(p, expression);
}
sqlExp = self.buildExpression(columnName, operator, columnValue, p);
stmt.merge(sqlExp);
if (operator === 'jsonContains') {
stmt.merge(sqlExp, columnValue);
} else {
stmt.merge(sqlExp);
}
} else {
// The expression is the field value, not a condition
columnValue = self.toColumnValue(p, expression);
if (columnValue === null) {
stmt.merge(columnName + ' IS NULL');
} else if (columnValue instanceof ParameterizedSQL) {
stmt.merge(columnName + '=').merge(columnValue);
} else {
if (columnValue instanceof ParameterizedSQL) {
stmt.merge(columnName + '=').merge(columnValue);
} else {
stmt.merge({
sql: columnName + '=?',
params: [columnValue],
});
}
stmt.merge({
sql: columnName + '=?',
params: [columnValue],
});
}
}
if (stmt.sql.includes('JSON_CONTAINS')) {
stmt.sql = `(${stmt.sql})`;
}
whereStmts.push(stmt);
}
let params = [];
Expand Down Expand Up @@ -1453,7 +1491,7 @@ SQLConnector.prototype.buildSelect = function(model, filter, options) {

if (filter) {
if (filter.where) {
const whereStmt = this.buildWhere(model, filter.where);
const whereStmt = this.buildWhere(model, filter.where, options);
selectStmt.merge(whereStmt);
}

Expand Down Expand Up @@ -1592,7 +1630,7 @@ SQLConnector.prototype.count = function(model, where, options, cb) {

let stmt = new ParameterizedSQL('SELECT count(*) as "cnt" FROM ' +
this.tableEscaped(model));
stmt = stmt.merge(this.buildWhere(model, where));
stmt = stmt.merge(this.buildWhere(model, where, options));
stmt = this.parameterize(stmt);
this.execute(stmt.sql, stmt.params, options,
function(err, res) {
Expand Down
47 changes: 41 additions & 6 deletions test/sql.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,14 @@ describe('sql connector', function() {
column: 'TOKEN',
},
},
address: String,
address: String, ids: {
type: String,
testdb: {
column: 'IDS',
dataType: 'VARCHAR',
dataLength: 32,
},
},
},
{testdb: {table: 'CUSTOMER'}});
Order = ds.createModel('order',
Expand Down Expand Up @@ -136,6 +143,32 @@ describe('sql connector', function() {
});
});

// jsonContains is operator when we have referencesMany with inq
it('builds where with jsonContains', function() {
const where = connector.buildWhere(
'customer',
{ids: {jsonContains: ['1', '2']}},
);
expect(where.toJSON()).to.eql({
sql:
'WHERE (JSON_CONTAINS(`IDS`, CAST(? as JSON))' +
' OR ' +
'JSON_CONTAINS(`IDS`, CAST(? as JSON)))',
params: ['1', '2'],
});
});

it('builds where with relation options', function() {
const where = connector.buildWhere(
'customer',
{ids: {inq: ['1', '2']}},
);
expect(where.toJSON()).to.eql({
sql: 'WHERE `IDS` IN (?,?)',
params: ['1', '2'],
});
});

it('builds where with null', function() {
const where = connector.buildWhere('customer', {name: null});
expect(where.toJSON()).to.eql({
Expand Down Expand Up @@ -296,13 +329,15 @@ describe('sql connector', function() {
primaryAddress: '1031 NW 7th Ave, Fort Lauderdale, Florida, United States',
});
expect(fields.toJSON()).to.eql({
sql: 'SET `middle_name`=?,`LASTNAME`=?,`VIP`=?,`primary_address`=?,`ADDRESS`=?',
sql: 'SET `middle_name`=?,`LASTNAME`=?,`VIP`=?,`primary_address`=?,' +
'`ADDRESS`=?,`IDS`=?',
params: [
'',
'Libert',
true,
'1031 NW 7th Ave, Fort Lauderdale, Florida, United States',
'1031 NW 7th Ave, Fort Lauderdale, Florida, United States',
null,
],
});
});
Expand All @@ -328,7 +363,7 @@ describe('sql connector', function() {
it('builds column names for SELECT', function() {
const cols = connector.buildColumnNames('customer');
expect(cols).to.eql('`NAME`,`middle_name`,`LASTNAME`,`VIP`,' +
'`primary_address`,`TOKEN`,`ADDRESS`');
'`primary_address`,`TOKEN`,`ADDRESS`,`IDS`');
});

it('builds column names with true fields filter for SELECT', function() {
Expand All @@ -346,7 +381,7 @@ describe('sql connector', function() {
middleName: false,
},
});
expect(cols).to.eql('`VIP`,`ADDRESS`');
expect(cols).to.eql('`VIP`,`ADDRESS`,`IDS`');
});

it('builds column names with array fields filter for SELECT', function() {
Expand Down Expand Up @@ -379,7 +414,7 @@ describe('sql connector', function() {
expect(sql.toJSON()).to.eql({
sql:
'SELECT `NAME`,`middle_name`,`LASTNAME`,`VIP`,`primary_address`,' +
'`TOKEN`,`ADDRESS` FROM `CUSTOMER` WHERE ((`NAME`=$1) OR (`ADDRESS`=$2)) ' +
'`TOKEN`,`ADDRESS`,`IDS` FROM `CUSTOMER` WHERE ((`NAME`=$1) OR (`ADDRESS`=$2)) ' +
'AND `VIP`=$3 ORDER BY `NAME` LIMIT 5',
params: ['Top Cat', 'Trash can', true],
});
Expand All @@ -390,7 +425,7 @@ describe('sql connector', function() {
{order: 'name', limit: 5, where: {name: 'John'}});
expect(sql.toJSON()).to.eql({
sql: 'SELECT `NAME`,`middle_name`,`LASTNAME`,`VIP`,`primary_address`,`TOKEN`,' +
'`ADDRESS` FROM `CUSTOMER`' +
'`ADDRESS`,`IDS` FROM `CUSTOMER`' +
' WHERE `NAME`=$1 ORDER BY `NAME` LIMIT 5',
params: ['John'],
});
Expand Down
Loading