Skip to content

Commit

Permalink
feat(operators): add containedBy and containsAnyOf operators
Browse files Browse the repository at this point in the history
Co-authored-by: Hazım Dikenli <[email protected]>

Signed-off-by: akshatdubeysf <[email protected]>
  • Loading branch information
akshatdubeysf authored and dhmlau committed Apr 5, 2024
1 parent 555d902 commit 61ac962
Show file tree
Hide file tree
Showing 3 changed files with 138 additions and 14 deletions.
84 changes: 84 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -550,6 +550,8 @@ CustomerRepository.find({
PostgreSQL supports the following PostgreSQL-specific operators:
- [`contains`](#operator-contains)
- [`containedBy`](#operator-containedby)
- [`containsAny`](#operator-containsany)
- [`match`](#operator-match)
Please note extended operators are disabled by default, you must enable
Expand Down Expand Up @@ -597,6 +599,88 @@ const posts = await postRepository.find({
});
```
### Operator `containedBy`
Inverse of the `contains` operator, the `containedBy` operator allow you to query array properties and pick only
rows where the all the items in the stored value are contained by the query.
The operator is implemented using PostgreSQL [array operator
`<@`](https://www.postgresql.org/docs/current/functions-array.html).
**Note** The fields you are querying must be setup to use the postgresql array data type - see [Defining models](#defining-models) above.
Assuming a model such as this:
```ts
@model({
settings: {
allowExtendedOperators: true,
}
})
class Post {
@property({
type: ['string'],
postgresql: {
dataType: 'varchar[]',
},
})
categories?: string[];
}
```
You can query the tags fields as follows:
```ts
const posts = await postRepository.find({
where: {
{
categories: {'containedBy': ['AA']},
}
}
});
```
### Operator `containsAnyOf`
The `containsAnyOf` operator allow you to query array properties and pick only
rows where the any of the items in the stored value matches any of the items in the query.
The operator is implemented using PostgreSQL [array overlap operator
`&&`](https://www.postgresql.org/docs/current/functions-array.html).
**Note** The fields you are querying must be setup to use the postgresql array data type - see [Defining models](#defining-models) above.
Assuming a model such as this:
```ts
@model({
settings: {
allowExtendedOperators: true,
}
})
class Post {
@property({
type: ['string'],
postgresql: {
dataType: 'varchar[]',
},
})
categories?: string[];
}
```
You can query the tags fields as follows:
```ts
const posts = await postRepository.find({
where: {
{
categories: {'containsAnyOf': ['AA']},
}
}
});
```
### Operator `match`
The `match` operator allows you to perform a [full text search using the `@@` operator](https://www.postgresql.org/docs/10/textsearch-tables.html#TEXTSEARCH-TABLES-SEARCH) in PostgreSQL.
Expand Down
8 changes: 8 additions & 0 deletions lib/postgresql.js
Original file line number Diff line number Diff line change
Expand Up @@ -565,6 +565,14 @@ PostgreSQL.prototype.buildExpression = function(columnName, operator,
return new ParameterizedSQL(columnName + ' @> array[' + operatorValue.map(() => '?') + ']::'
+ propertyDefinition.postgresql.dataType,
operatorValue);
case 'containedBy':
return new ParameterizedSQL(columnName + ' <@ array[' + operatorValue.map(() => '?') + ']::'
+ propertyDefinition.postgresql.dataType,
operatorValue);
case 'containsAnyOf':
return new ParameterizedSQL(columnName + ' && array[' + operatorValue.map(() => '?') + ']::'
+ propertyDefinition.postgresql.dataType,
operatorValue);
case 'match':
return new ParameterizedSQL(`to_tsvector(${columnName}) @@ to_tsquery(?)`, [operatorValue]);
default:
Expand Down
60 changes: 46 additions & 14 deletions test/postgresql.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -273,22 +273,54 @@ describe('postgresql connector', function() {
});
});

it('should support where filter for array type field', async () => {
await Post.create({
title: 'LoopBack Participates in Hacktoberfest',
categories: ['LoopBack', 'Announcements'],
context('array operators', () => {
before(deleteTestFixtures);
before(createTestFixtures);
it('should support contains filter for array type field', async () => {
const found = await Post.find({where: {and: [
{
categories: {'contains': ['LoopBack', 'Community']},
},
]}});
found.map(p => p.title).should.deepEqual(['Growing LoopBack Community']);
});
await Post.create({
title: 'Growing LoopBack Community',
categories: ['LoopBack', 'Community'],
it('should support containedBy filter for array type field', async () => {
const found = await Post.find({where: {and: [
{
categories: {'containedBy': ['LoopBack', 'Community', 'SomethingElse']},
},
]}});
found.map(p => p.title).should.deepEqual(['Growing LoopBack Community']);
});

const found = await Post.find({where: {and: [
{
categories: {'contains': ['LoopBack', 'Community']},
},
]}});
found.map(p => p.title).should.deepEqual(['Growing LoopBack Community']);
it('should support containsAnyOf filter for array type field', async () => {
const found = await Post.find({where: {and: [
{
categories: {'containsAnyOf': ['LoopBack']},
},
]}});
found.map(p => p.title).should.deepEqual([
'LoopBack Participates in Hacktoberfest',
'Growing LoopBack Community',
]);
});
function deleteTestFixtures(done) {
Post.destroyAll(function(err) {
should.not.exist(err);
done();
});
}
function createTestFixtures(done) {
Post.createAll([
{
title: 'LoopBack Participates in Hacktoberfest',
categories: ['LoopBack', 'Hacktoberfest'],
},
{
title: 'Growing LoopBack Community',
categories: ['LoopBack', 'Community'],
},
], done);
}
});

it('should support full text search for text type fields using simple string query', async () => {
Expand Down

0 comments on commit 61ac962

Please sign in to comment.