Skip to content

Commit 61ac962

Browse files
akshatdubeysfdhmlau
authored andcommitted
feat(operators): add containedBy and containsAnyOf operators
Co-authored-by: Hazım Dikenli <[email protected]> Signed-off-by: akshatdubeysf <[email protected]>
1 parent 555d902 commit 61ac962

File tree

3 files changed

+138
-14
lines changed

3 files changed

+138
-14
lines changed

README.md

+84
Original file line numberDiff line numberDiff line change
@@ -550,6 +550,8 @@ CustomerRepository.find({
550550
PostgreSQL supports the following PostgreSQL-specific operators:
551551
552552
- [`contains`](#operator-contains)
553+
- [`containedBy`](#operator-containedby)
554+
- [`containsAny`](#operator-containsany)
553555
- [`match`](#operator-match)
554556
555557
Please note extended operators are disabled by default, you must enable
@@ -597,6 +599,88 @@ const posts = await postRepository.find({
597599
});
598600
```
599601
602+
### Operator `containedBy`
603+
604+
Inverse of the `contains` operator, the `containedBy` operator allow you to query array properties and pick only
605+
rows where the all the items in the stored value are contained by the query.
606+
607+
The operator is implemented using PostgreSQL [array operator
608+
`<@`](https://www.postgresql.org/docs/current/functions-array.html).
609+
610+
**Note** The fields you are querying must be setup to use the postgresql array data type - see [Defining models](#defining-models) above.
611+
612+
Assuming a model such as this:
613+
614+
```ts
615+
@model({
616+
settings: {
617+
allowExtendedOperators: true,
618+
}
619+
})
620+
class Post {
621+
@property({
622+
type: ['string'],
623+
postgresql: {
624+
dataType: 'varchar[]',
625+
},
626+
})
627+
categories?: string[];
628+
}
629+
```
630+
631+
You can query the tags fields as follows:
632+
633+
```ts
634+
const posts = await postRepository.find({
635+
where: {
636+
{
637+
categories: {'containedBy': ['AA']},
638+
}
639+
}
640+
});
641+
```
642+
643+
### Operator `containsAnyOf`
644+
645+
The `containsAnyOf` operator allow you to query array properties and pick only
646+
rows where the any of the items in the stored value matches any of the items in the query.
647+
648+
The operator is implemented using PostgreSQL [array overlap operator
649+
`&&`](https://www.postgresql.org/docs/current/functions-array.html).
650+
651+
**Note** The fields you are querying must be setup to use the postgresql array data type - see [Defining models](#defining-models) above.
652+
653+
Assuming a model such as this:
654+
655+
```ts
656+
@model({
657+
settings: {
658+
allowExtendedOperators: true,
659+
}
660+
})
661+
class Post {
662+
@property({
663+
type: ['string'],
664+
postgresql: {
665+
dataType: 'varchar[]',
666+
},
667+
})
668+
categories?: string[];
669+
}
670+
```
671+
672+
You can query the tags fields as follows:
673+
674+
```ts
675+
const posts = await postRepository.find({
676+
where: {
677+
{
678+
categories: {'containsAnyOf': ['AA']},
679+
}
680+
}
681+
});
682+
```
683+
600684
### Operator `match`
601685
602686
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.

lib/postgresql.js

+8
Original file line numberDiff line numberDiff line change
@@ -565,6 +565,14 @@ PostgreSQL.prototype.buildExpression = function(columnName, operator,
565565
return new ParameterizedSQL(columnName + ' @> array[' + operatorValue.map(() => '?') + ']::'
566566
+ propertyDefinition.postgresql.dataType,
567567
operatorValue);
568+
case 'containedBy':
569+
return new ParameterizedSQL(columnName + ' <@ array[' + operatorValue.map(() => '?') + ']::'
570+
+ propertyDefinition.postgresql.dataType,
571+
operatorValue);
572+
case 'containsAnyOf':
573+
return new ParameterizedSQL(columnName + ' && array[' + operatorValue.map(() => '?') + ']::'
574+
+ propertyDefinition.postgresql.dataType,
575+
operatorValue);
568576
case 'match':
569577
return new ParameterizedSQL(`to_tsvector(${columnName}) @@ to_tsquery(?)`, [operatorValue]);
570578
default:

test/postgresql.test.js

+46-14
Original file line numberDiff line numberDiff line change
@@ -273,22 +273,54 @@ describe('postgresql connector', function() {
273273
});
274274
});
275275

276-
it('should support where filter for array type field', async () => {
277-
await Post.create({
278-
title: 'LoopBack Participates in Hacktoberfest',
279-
categories: ['LoopBack', 'Announcements'],
276+
context('array operators', () => {
277+
before(deleteTestFixtures);
278+
before(createTestFixtures);
279+
it('should support contains filter for array type field', async () => {
280+
const found = await Post.find({where: {and: [
281+
{
282+
categories: {'contains': ['LoopBack', 'Community']},
283+
},
284+
]}});
285+
found.map(p => p.title).should.deepEqual(['Growing LoopBack Community']);
280286
});
281-
await Post.create({
282-
title: 'Growing LoopBack Community',
283-
categories: ['LoopBack', 'Community'],
287+
it('should support containedBy filter for array type field', async () => {
288+
const found = await Post.find({where: {and: [
289+
{
290+
categories: {'containedBy': ['LoopBack', 'Community', 'SomethingElse']},
291+
},
292+
]}});
293+
found.map(p => p.title).should.deepEqual(['Growing LoopBack Community']);
284294
});
285-
286-
const found = await Post.find({where: {and: [
287-
{
288-
categories: {'contains': ['LoopBack', 'Community']},
289-
},
290-
]}});
291-
found.map(p => p.title).should.deepEqual(['Growing LoopBack Community']);
295+
it('should support containsAnyOf filter for array type field', async () => {
296+
const found = await Post.find({where: {and: [
297+
{
298+
categories: {'containsAnyOf': ['LoopBack']},
299+
},
300+
]}});
301+
found.map(p => p.title).should.deepEqual([
302+
'LoopBack Participates in Hacktoberfest',
303+
'Growing LoopBack Community',
304+
]);
305+
});
306+
function deleteTestFixtures(done) {
307+
Post.destroyAll(function(err) {
308+
should.not.exist(err);
309+
done();
310+
});
311+
}
312+
function createTestFixtures(done) {
313+
Post.createAll([
314+
{
315+
title: 'LoopBack Participates in Hacktoberfest',
316+
categories: ['LoopBack', 'Hacktoberfest'],
317+
},
318+
{
319+
title: 'Growing LoopBack Community',
320+
categories: ['LoopBack', 'Community'],
321+
},
322+
], done);
323+
}
292324
});
293325

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

0 commit comments

Comments
 (0)