Skip to content

Commit dfb394b

Browse files
authored
Merge pull request #460 from shubhisood/array_type_field_filter
array_type_field_filter: Add new contains filter to search on Array type field
2 parents ced6c28 + 8f2da81 commit dfb394b

File tree

3 files changed

+98
-12
lines changed

3 files changed

+98
-12
lines changed

README.md

+72-7
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ const config = {
4545

4646
<details><summary markdown="span"><strong>For LoopBack 3 users</strong></summary>
4747

48-
Use the [Data source generator](http://loopback.io/doc/en/lb3/Data-source-generator.html) to add a PostgreSQL data source to your application.
48+
Use the [Data source generator](http://loopback.io/doc/en/lb3/Data-source-generator.html) to add a PostgreSQL data source to your application.
4949
The generator will prompt for the database server hostname, port, and other settings
5050
required to connect to a PostgreSQL database. It will also run the `npm install` command above for you.
5151

@@ -96,7 +96,7 @@ const config = {
9696

9797
Check out [node-pg-pool](https://github.com/brianc/node-pg-pool) and [node postgres pooling example](https://github.com/brianc/node-postgres#pooling-example) for more information.
9898

99-
### Properties
99+
### Configuration options
100100

101101
<table>
102102
<thead>
@@ -106,7 +106,7 @@ Check out [node-pg-pool](https://github.com/brianc/node-pg-pool) and [node postg
106106
<th>Description</th>
107107
</tr>
108108
</thead>
109-
<tbody>
109+
<tbody>
110110
<tr>
111111
<td>connector</td>
112112
<td>String</td>
@@ -176,6 +176,14 @@ Check out [node-pg-pool](https://github.com/brianc/node-pg-pool) and [node postg
176176
<td>Boolean/String</td>
177177
<td>Set to <code>false</code> to disable default sorting on <code>id</code> column(s). Set to <code>numericIdOnly</code> to only apply to IDs with a number type <code>id</code>.</td>
178178
</tr>
179+
<tr>
180+
<td>allowExtendedOperators</td>
181+
<td>Boolean</td>
182+
<td>Set to <code>true</code> to enable PostgreSQL-specific operators
183+
such as <code>contains</code>. Learn more in
184+
<a href="#extended-operators">Extended operators</a> below.
185+
</td>
186+
</tr>
179187
</tbody>
180188
</table>
181189

@@ -272,7 +280,7 @@ The model definition consists of the following properties.
272280
<th>Description</th>
273281
</tr>
274282
</thead>
275-
<tbody>
283+
<tbody>
276284
<tr>
277285
<td>name</td>
278286
<td>Camel-case of the database table name</td>
@@ -412,6 +420,12 @@ details on LoopBack's data types.
412420
VARCHAR2<br/>
413421
Default length is 1024
414422
</td>
423+
</tr>
424+
<tr>
425+
<td>String[]</td>
426+
<td>
427+
VARCHAR2[]
428+
</td>
415429
</tr>
416430
<tr>
417431
<td>Number</td>
@@ -531,14 +545,65 @@ CustomerRepository.find({
531545
});
532546
```
533547
548+
## Extended operators
549+
550+
PostgreSQL supports the following PostgreSQL-specific operators:
551+
552+
- [`contains`](#operator-contains)
553+
554+
Please note extended operators are disabled by default, you must enable
555+
them at datasource level or model level by setting `allowExtendedOperators` to
556+
`true`.
557+
558+
### Operator `contains`
559+
560+
The `contains` operator allow you to query array properties and pick only
561+
rows where the stored value contains all of the items specified by the query.
562+
563+
The operator is implemented using PostgreSQL [array operator
564+
`@>`](https://www.postgresql.org/docs/current/functions-array.html).
565+
566+
**Note** The fields you are querying must be setup to use the postgresql array data type - see [Defining models](#defining-models) above.
567+
568+
Assuming a model such as this:
569+
570+
```ts
571+
@model({
572+
settings: {
573+
allowExtendedOperators: true,
574+
}
575+
})
576+
class Post {
577+
@property({
578+
type: ['string'],
579+
postgresql: {
580+
dataType: 'varchar[]',
581+
},
582+
})
583+
categories?: string[];
584+
}
585+
```
586+
587+
You can query the tags fields as follows:
588+
589+
```ts
590+
const posts = await postRepository.find({
591+
where: {
592+
{
593+
categories: {'contains': ['AA']},
594+
}
595+
}
596+
});
597+
```
598+
534599
## Discovery and auto-migration
535600
536601
### Model discovery
537602
538603
The PostgreSQL connector supports _model discovery_ that enables you to create LoopBack models
539604
based on an existing database schema. Once you defined your datasource:
540-
- LoopBack 4 users could use the commend [`lb4 discover`](https://loopback.io/doc/en/lb4/Discovering-models.html) to discover models.
541-
- For LB3 users, please check [Discovering models from relational databases](https://loopback.io/doc/en/lb3/Discovering-models-from-relational-databases.html).
605+
- LoopBack 4 users could use the commend [`lb4 discover`](https://loopback.io/doc/en/lb4/Discovering-models.html) to discover models.
606+
- For LB3 users, please check [Discovering models from relational databases](https://loopback.io/doc/en/lb3/Discovering-models-from-relational-databases.html).
542607
543608
(See [database discovery API](http://apidocs.strongloop.com/loopback-datasource-juggler/#datasource-prototype-discoverandbuildmodels) for related APIs information)
544609
@@ -612,7 +677,7 @@ Here are some limitations and tips:
612677
613678
### Auto-migrate/Auto-update models with foreign keys
614679
615-
Foreign key constraints can be defined in the model definition.
680+
Foreign key constraints can be defined in the model definition.
616681
617682
**Note**: The order of table creation is important. A referenced table must exist before creating a foreign key constraint.
618683

lib/postgresql.js

+3
Original file line numberDiff line numberDiff line change
@@ -522,6 +522,9 @@ PostgreSQL.prototype.buildExpression = function(columnName, operator,
522522
const regexOperator = operatorValue.ignoreCase ? ' ~* ?' : ' ~ ?';
523523
return new ParameterizedSQL(columnName + regexOperator,
524524
[operatorValue.source]);
525+
case 'contains':
526+
return new ParameterizedSQL(columnName + ' @> array[' + operatorValue.map((v) => `'${v}'`) + ']::'
527+
+ propertyDefinition.postgresql.dataType);
525528
default:
526529
// invoke the base implementation of `buildExpression`
527530
return this.invokeSuper('buildExpression', columnName, operator,

test/postgresql.test.js

+23-5
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ describe('postgresql connector', function() {
7373
dataType: 'varchar[]',
7474
},
7575
},
76-
});
76+
}, {allowExtendedOperators: true});
7777
created = new Date();
7878
});
7979

@@ -218,7 +218,7 @@ describe('postgresql connector', function() {
218218
post.should.have.property('tags');
219219
post.tags.should.be.Array();
220220
post.tags.length.should.eql(2);
221-
post.tags.should.eql(['AA', 'AB']);
221+
post.tags.toArray().should.eql(['AA', 'AB']);
222222
return Post.updateAll({where: {id: postId}}, {tags: ['AA', 'AC']});
223223
})
224224
.then(()=> {
@@ -228,7 +228,7 @@ describe('postgresql connector', function() {
228228
post.should.have.property('tags');
229229
post.tags.should.be.Array();
230230
post.tags.length.should.eql(2);
231-
post.tags.should.eql(['AA', 'AC']);
231+
post.tags.toArray().should.eql(['AA', 'AC']);
232232
done();
233233
})
234234
.catch((error) => {
@@ -245,7 +245,7 @@ describe('postgresql connector', function() {
245245
post.should.have.property('categories');
246246
post.categories.should.be.Array();
247247
post.categories.length.should.eql(2);
248-
post.categories.should.eql(['AA', 'AB']);
248+
post.categories.toArray().should.eql(['AA', 'AB']);
249249
return Post.updateAll({where: {id: postId}}, {categories: ['AA', 'AC']});
250250
})
251251
.then(()=> {
@@ -255,14 +255,32 @@ describe('postgresql connector', function() {
255255
post.should.have.property('categories');
256256
post.categories.should.be.Array();
257257
post.categories.length.should.eql(2);
258-
post.categories.should.eql(['AA', 'AC']);
258+
post.categories.toArray().should.eql(['AA', 'AC']);
259259
done();
260260
})
261261
.catch((error) => {
262262
done(error);
263263
});
264264
});
265265

266+
it('should support where filter for array type field', async () => {
267+
await Post.create({
268+
title: 'LoopBack Participates in Hacktoberfest',
269+
categories: ['LoopBack', 'Announcements'],
270+
});
271+
await Post.create({
272+
title: 'Growing LoopBack Community',
273+
categories: ['LoopBack', 'Community'],
274+
});
275+
276+
const found = await Post.find({where: {and: [
277+
{
278+
categories: {'contains': ['LoopBack', 'Community']},
279+
},
280+
]}});
281+
found.map(p => p.title).should.deepEqual(['Growing LoopBack Community']);
282+
});
283+
266284
it('should support boolean types with false value', function(done) {
267285
Post.create(
268286
{title: 'T2', content: 'C2', approved: false, created: created},

0 commit comments

Comments
 (0)