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
37 changes: 37 additions & 0 deletions lib/postgresql.js
Original file line number Diff line number Diff line change
Expand Up @@ -584,6 +584,43 @@ PostgreSQL.prototype.buildExpression = function(columnName, operator,
operatorValue);
case 'match':
return new ParameterizedSQL(`to_tsvector(${columnName}) @@ to_tsquery(?)`, [operatorValue]);
// Map the abstract 'jsonContains' operator to PostgreSQL's JSONB containment operator (@>),
// parsing or normalizing string/array inputs to build parameterized clauses connected with OR.
case 'jsonContains':
let values = operatorValue;
if (typeof values === 'string') {
try {
values = JSON.parse(values);
} catch (error) {
values = [values];
}
}
if (!Array.isArray(values)) { values = [values]; }

const itemType = propertyDefinition.type[0];
const postgresTypeMap = new Map([
[Number, 'INTEGER'],
[String, 'TEXT'],
]);
const arrayItemType = postgresTypeMap.get(itemType);

if (!arrayItemType) {
throw new Error(`Unsupported array item type: ${itemType ? itemType.name : 'unknown'}`);
}
const isArray = propertyDefinition.postgresql.dataType === 'ARRAY';
const conditions = [];
const params = [];

values.forEach(value => {
if (isArray) {
conditions.push(`${columnName} @> ARRAY[?]::${arrayItemType}[]`);
params.push(value);
} else {
conditions.push(`${columnName} @> CAST(? AS JSONB)`);
params.push(JSON.stringify([value]));
}
});
return new ParameterizedSQL(`(${conditions.join(' OR ')})`, params);
default:
// invoke the base implementation of `buildExpression`
return this.invokeSuper('buildExpression', columnName, operator,
Expand Down
85 changes: 85 additions & 0 deletions test/postgresql.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -983,6 +983,91 @@ describe('postgresql connector', function() {
done();
});
});

context('jsonContains operator', function() {
let ItemWithJson, ItemWithIntegerArray, ItemWithStringArray;

before(function(done) {
db = global.getDataSource();
// Define a test model utilizing a jsonb column type to test JSON containment filters.
ItemWithJson = db.define('ItemWithJson', {
name: {type: String},
tags: {
type: [String],
postgresql: {
dataType: 'jsonb',
},
},
});
ItemWithIntegerArray = db.define('ItemWithIntegerArray', {
name: {type: String},
tags: {
type: [Number],
postgresql: {
dataType: 'jsonb',
},
},
});
ItemWithStringArray = db.define('ItemWithStringArray', {
name: {type: String},
tags: {
type: [String],
postgresql: {
dataType: 'jsonb',
},
},
});
db.automigrate(['ItemWithJson', 'ItemWithIntegerArray', 'ItemWithStringArray'], done);
});

beforeEach(function(done) {
ItemWithJson.destroyAll();
ItemWithIntegerArray.destroyAll();
ItemWithStringArray.destroyAll(done);
});

after(function(done) {
ItemWithJson.destroyAll();
ItemWithIntegerArray.destroyAll();
ItemWithStringArray.destroyAll(done);
});

it('should support unit-level SQL building verification for jsonContains', function() {
const where = db.connector.buildWhere(
'ItemWithJson',
{tags: {jsonContains: ['bar', 'foo']}},
);

where.toJSON().should.eql({
sql: 'WHERE ("tags" @> CAST(? AS JSONB) OR "tags" @> CAST(? AS JSONB))',
params: ['["bar"]', '["foo"]'],
});
});

it('should support unit-level SQL building verification for jsonContains with integer array', function() {
const where = db.connector.buildWhere(
'ItemWithIntegerArray',
{tags: {jsonContains: [1, 2]}},
);

where.toJSON().should.eql({
sql: 'WHERE ("tags" @> CAST(? AS JSONB) OR "tags" @> CAST(? AS JSONB))',
params: ['[1]', '[2]'],
});
});

it('should support unit-level SQL building verification for jsonContains with string array', function() {
const where = db.connector.buildWhere(
'ItemWithStringArray',
{tags: {jsonContains: ['bar', 'foo']}},
);

where.toJSON().should.eql({
sql: 'WHERE ("tags" @> CAST(? AS JSONB) OR "tags" @> CAST(? AS JSONB))',
params: ['["bar"]', '["foo"]'],
});
});
});
});

describe('Serial properties', function() {
Expand Down
Loading