Skip to content
Open
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
19 changes: 19 additions & 0 deletions lib/mysql.js
Original file line number Diff line number Diff line change
Expand Up @@ -649,6 +649,25 @@ MySQL.prototype.buildExpression = function(columnName, operator, operatorValue,
clause = ` MATCH (${columnName}) AGAINST (?${mode})`;

return new ParameterizedSQL(clause, [operatorValue]);
// Map the abstract 'jsonContains' operator to MySQL's native JSON_CONTAINS function,
// supporting array or string-serialized inputs for array-based containment checks.
case 'jsonContains':
if (typeof operatorValue === 'string') {
try {
operatorValue = JSON.parse(operatorValue);
} catch (e) {
operatorValue = [operatorValue];
}
}
if (!Array.isArray(operatorValue)) {
operatorValue = [operatorValue];
}
const conditions = [];
operatorValue.forEach(value => {
conditions.push(`JSON_CONTAINS(${columnName}, CAST(? as JSON))`);
});
clause = '(' + conditions.join(' OR ') + ')';
return new ParameterizedSQL(clause, operatorValue);
}

// invoke the base implementation of `buildExpression`
Expand Down
53 changes: 53 additions & 0 deletions test/mysql.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -1076,6 +1076,59 @@ describe('mysql', function() {
});
});

context('jsonContains operator', function() {
let Customer;

before(function(done) {
// Define a test model with an array property to mimic referencesMany relation mapping.
Customer = db.define('CustomerWithJson', {
name: {type: String},
ids: {type: [Number]},
}, {
forceId: false,
});
db.automigrate(['CustomerWithJson'], done);
});

beforeEach(function(done) {
Customer.destroyAll(done);
});

after(function(done) {
Customer.destroyAll(done);
});

it('should build where with jsonContains and match records', function(done) {
Customer.createAll([
{name: 'Alice', ids: [1, 2]},
{name: 'Bob', ids: [3, 4]},
], function(err) {
should.not.exist(err);

// Execute a find query leveraging the mapped jsonContains operator.
Customer.find({where: {ids: {jsonContains: [2]}}}, function(err, customers) {
should.not.exist(err);
customers.should.have.property('length', 1);
customers[0].name.should.equal('Alice');
done();
});
});
});

it('should support unit-level SQL building verification for jsonContains', function() {
// Verify that buildWhere constructs the exact MySQL-native JSON_CONTAINS syntax and parameter bindings.
const where = db.connector.buildWhere(
'CustomerWithJson',
{ids: {jsonContains: [1, 2]}},
);

where.toJSON().should.eql({
sql: 'WHERE (JSON_CONTAINS(`ids`, CAST(? as JSON)) OR JSON_CONTAINS(`ids`, CAST(? as JSON)))',
params: [1, 2],
});
});
});

function deleteAllModelInstances() {
const models = [
Post, PostWithStringId, PostWithUniqueTitle, PostWithNumId, Student,
Expand Down
Loading