diff --git a/README.md b/README.md index 52456ef2..0aa11976 100644 --- a/README.md +++ b/README.md @@ -45,7 +45,7 @@ const config = {
For LoopBack 3 users -Use the [Data source generator](http://loopback.io/doc/en/lb3/Data-source-generator.html) to add a PostgreSQL data source to your application. +Use the [Data source generator](http://loopback.io/doc/en/lb3/Data-source-generator.html) to add a PostgreSQL data source to your application. The generator will prompt for the database server hostname, port, and other settings required to connect to a PostgreSQL database. It will also run the `npm install` command above for you. @@ -96,7 +96,7 @@ const config = { 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. -### Properties +### Configuration options @@ -106,7 +106,7 @@ Check out [node-pg-pool](https://github.com/brianc/node-pg-pool) and [node postg - + @@ -176,6 +176,14 @@ Check out [node-pg-pool](https://github.com/brianc/node-pg-pool) and [node postg + + + + +
Description
connector StringBoolean/String Set to false to disable default sorting on id column(s). Set to numericIdOnly to only apply to IDs with a number type id.
allowExtendedOperatorsBooleanSet to true to enable PostgreSQL-specific operators + such as contains. Learn more in + Extended operators below. +
@@ -272,7 +280,7 @@ The model definition consists of the following properties. Description - + name Camel-case of the database table name @@ -412,6 +420,12 @@ details on LoopBack's data types. VARCHAR2
Default length is 1024 + + + String[] + + VARCHAR2[] + Number @@ -531,14 +545,65 @@ CustomerRepository.find({ }); ``` +## Extended operators + +PostgreSQL supports the following PostgreSQL-specific operators: + +- [`contains`](#operator-contains) + +Please note extended operators are disabled by default, you must enable +them at datasource level or model level by setting `allowExtendedOperators` to +`true`. + +### Operator `contains` + +The `contains` operator allow you to query array properties and pick only +rows where the stored value contains all of the items specified 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: {'contains': ['AA']}, + } + } +}); +``` + ## Discovery and auto-migration ### Model discovery The PostgreSQL connector supports _model discovery_ that enables you to create LoopBack models based on an existing database schema. Once you defined your datasource: -- LoopBack 4 users could use the commend [`lb4 discover`](https://loopback.io/doc/en/lb4/Discovering-models.html) to discover models. -- For LB3 users, please check [Discovering models from relational databases](https://loopback.io/doc/en/lb3/Discovering-models-from-relational-databases.html). +- LoopBack 4 users could use the commend [`lb4 discover`](https://loopback.io/doc/en/lb4/Discovering-models.html) to discover models. +- For LB3 users, please check [Discovering models from relational databases](https://loopback.io/doc/en/lb3/Discovering-models-from-relational-databases.html). (See [database discovery API](http://apidocs.strongloop.com/loopback-datasource-juggler/#datasource-prototype-discoverandbuildmodels) for related APIs information) @@ -612,7 +677,7 @@ Here are some limitations and tips: ### Auto-migrate/Auto-update models with foreign keys -Foreign key constraints can be defined in the model definition. +Foreign key constraints can be defined in the model definition. **Note**: The order of table creation is important. A referenced table must exist before creating a foreign key constraint. diff --git a/lib/postgresql.js b/lib/postgresql.js index 0c5f9a05..8673e9f5 100644 --- a/lib/postgresql.js +++ b/lib/postgresql.js @@ -522,6 +522,9 @@ PostgreSQL.prototype.buildExpression = function(columnName, operator, const regexOperator = operatorValue.ignoreCase ? ' ~* ?' : ' ~ ?'; return new ParameterizedSQL(columnName + regexOperator, [operatorValue.source]); + case 'contains': + return new ParameterizedSQL(columnName + ' @> array[' + operatorValue.map((v) => `'${v}'`) + ']::' + + propertyDefinition.postgresql.dataType); default: // invoke the base implementation of `buildExpression` return this.invokeSuper('buildExpression', columnName, operator, diff --git a/test/postgresql.test.js b/test/postgresql.test.js index 08346f51..bd442167 100644 --- a/test/postgresql.test.js +++ b/test/postgresql.test.js @@ -73,7 +73,7 @@ describe('postgresql connector', function() { dataType: 'varchar[]', }, }, - }); + }, {allowExtendedOperators: true}); created = new Date(); }); @@ -218,7 +218,7 @@ describe('postgresql connector', function() { post.should.have.property('tags'); post.tags.should.be.Array(); post.tags.length.should.eql(2); - post.tags.should.eql(['AA', 'AB']); + post.tags.toArray().should.eql(['AA', 'AB']); return Post.updateAll({where: {id: postId}}, {tags: ['AA', 'AC']}); }) .then(()=> { @@ -228,7 +228,7 @@ describe('postgresql connector', function() { post.should.have.property('tags'); post.tags.should.be.Array(); post.tags.length.should.eql(2); - post.tags.should.eql(['AA', 'AC']); + post.tags.toArray().should.eql(['AA', 'AC']); done(); }) .catch((error) => { @@ -245,7 +245,7 @@ describe('postgresql connector', function() { post.should.have.property('categories'); post.categories.should.be.Array(); post.categories.length.should.eql(2); - post.categories.should.eql(['AA', 'AB']); + post.categories.toArray().should.eql(['AA', 'AB']); return Post.updateAll({where: {id: postId}}, {categories: ['AA', 'AC']}); }) .then(()=> { @@ -255,7 +255,7 @@ describe('postgresql connector', function() { post.should.have.property('categories'); post.categories.should.be.Array(); post.categories.length.should.eql(2); - post.categories.should.eql(['AA', 'AC']); + post.categories.toArray().should.eql(['AA', 'AC']); done(); }) .catch((error) => { @@ -263,6 +263,24 @@ 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'], + }); + await Post.create({ + title: 'Growing LoopBack Community', + categories: ['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 boolean types with false value', function(done) { Post.create( {title: 'T2', content: 'C2', approved: false, created: created},