Skip to content
Closed
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 README.md
Original file line number Diff line number Diff line change
Expand Up @@ -545,6 +545,43 @@ CustomerRepository.find({
});
```

## Partial updates of JSON fields (CONCAT)

By default, updating a property mapped to the PostgreSQL 'jsonb' data type will replace the entire JSON value.
With this enhancement, you can now perform partial updates (merges) of jsonb columns using the PostgreSQL JSONB concatenation operator (||).

### How it works

If you set the value of a property to an object containing a special CONCAT key, the connector will:

- Generate an UPDATE statement using || ?::jsonb

- Merge the object specified in CONCAT into the existing JSONB column value, overriding fields with the same key but leaving others unchanged.

Assuming a model property such as this:

```ts
@property({
type: 'object',
postgresql: {
dataType: 'jsonb'
},
})
address?: object;
```

Now perform a partial update to change only the city leaving the street intact:

```ts
await customerRepository.updateById(customerId, {
address: {
CONCAT: {
city: 'New City'
}
}
});
```

## Extended operators

PostgreSQL supports the following PostgreSQL-specific operators:
Expand Down
25 changes: 23 additions & 2 deletions lib/postgresql.js
Original file line number Diff line number Diff line change
Expand Up @@ -788,7 +788,7 @@ PostgreSQL.prototype._buildWhere = function(model, where) {
* @param {boolean} isWhereClause
* @returns {*} The escaped value of DB column
*/
PostgreSQL.prototype.toColumnValue = function(prop, val, isWhereClause) {
PostgreSQL.prototype.toColumnValue = function(prop, val, isWhereClause, fieldName) {
if (val == null) {
// PostgreSQL complains with NULLs in not null columns
// If we have an autoincrement value, return DEFAULT instead
Expand Down Expand Up @@ -857,7 +857,28 @@ PostgreSQL.prototype.toColumnValue = function(prop, val, isWhereClause) {
}
}
}

/* The above code is checking if a property in a PostgreSQL database table is of type 'jsonb'. If the
property is of type 'jsonb', it then attempts to parse a JSON string value (val) and checks if it
contains a key named 'CONCAT'. If the 'CONCAT' key is found in the parsed JSON data, it constructs
a ParameterizedSQL object with a SQL query that concatenates the existing value of the property
with the value of 'CONCAT'. The SQL query uses the '||' operator to concatenate the values and
includes the JSON type in the query. The */
if (prop.postgresql && prop.postgresql.dataType === 'jsonb') {
// check for any json operator for updates
const jsonType = prop.postgresql.dataType;
try {
const rawData = JSON.parse(val);
if (rawData['CONCAT']) {
// If the property is a json type and partial update is enabled,
return new ParameterizedSQL({
sql: `${fieldName} || ?::${jsonType}`,
params: [JSON.stringify(rawData['CONCAT'])],
});
}
} catch (e) {
// do nothing
}
}
return val;
};

Expand Down
42 changes: 40 additions & 2 deletions test/postgresql.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -848,8 +848,13 @@ describe('postgresql connector', function() {
dataType: 'json',
},
},
metadata: {
type: 'object',
postgresql: {
dataType: 'jsonb',
},
},
});

db.automigrate(function(err) {
if (err) return done(err);
Customer.createAll([{
Expand All @@ -871,7 +876,6 @@ describe('postgresql connector', function() {
});
});
});

it('allows querying for nested json properties', function(done) {
Customer.find({
where: {
Expand Down Expand Up @@ -963,6 +967,40 @@ describe('postgresql connector', function() {
done();
});
});
it('should support partial update of json data type using CONCAT', function(done) {
Customer.create({
address: {
city: 'Old City',
street: {
number: 100,
name: 'Old Street',
},
},
metadata: {
extenalid: '123',
isactive: true,
},
}, function(err, customer) {
if (err) return done(err);
const partialUpdate = {
metadata: {
CONCAT: {
isactive: false,
},
},
};

Customer.updateAll({id: customer.id}, partialUpdate, function(err) {
if (err) return done(err);
Customer.findById(customer.id, function(err, updatedCustomer) {
if (err) return done(err);
updatedCustomer.metadata.isactive.should.equal(false);
updatedCustomer.metadata.extenalid.should.equal('123');
done();
});
});
});
});
});

it('should return array of models with id column value for createAll()', function(done) {
Expand Down