Skip to content

Commit 5b5d05e

Browse files
author
Agnes Lin
authored
Merge pull request #458 from QuentinLB/ondelete
Add on delete options on FK constraints
2 parents 233c8a5 + 5bfb1e2 commit 5b5d05e

File tree

4 files changed

+39
-5
lines changed

4 files changed

+39
-5
lines changed

README.md

+6-2
Original file line numberDiff line numberDiff line change
@@ -713,6 +713,8 @@ export class Customer extends Entity {
713713
entity: 'Customer',
714714
entityKey: 'id',
715715
foreignKey: 'customerId',
716+
onDelete: 'CASCADE',
717+
onUpdate: 'SET NULL'
716718
},
717719
},
718720
})
@@ -764,7 +766,9 @@ export class Order extends Entity {
764766
"name": "fk_order_customerId",
765767
"entity": "Customer",
766768
"entityKey": "id",
767-
"foreignKey": "customerId"
769+
"foreignKey": "customerId",
770+
"onDelete": "CASCADE",
771+
"onUpdate": "SET NULL"
768772
}
769773
}
770774
},
@@ -787,7 +791,7 @@ export class Order extends Entity {
787791
</details>
788792
<br>
789793
{% include tip.html content="
790-
Removing or updating the value of `foreignKeys` will be updated or delete or update the constraints in the db tables. If there is a reference to an object being deleted then the `DELETE` will fail. Likewise if there is a create with an invalid FK id then the `POST` will fail.
794+
Removing or updating the value of `foreignKeys` will be updated or delete or update the constraints in the db tables. If there is a reference to an object being deleted then the `DELETE` will fail. Likewise if there is a create with an invalid FK id then the `POST` will fail. The `onDelete` and `onUpdate` properties are optional and will default to `NO ACTION`.
791795
" %}
792796
793797
### Auto-generated ids

lib/discovery.js

+6
Original file line numberDiff line numberDiff line change
@@ -263,6 +263,7 @@ function mixinDiscovery(PostgreSQL) {
263263
let sql =
264264
'SELECT tc.table_schema AS "fkOwner", tc.constraint_name AS "fkName", tc.table_name AS "fkTableName",'
265265
+ ' kcu.column_name AS "fkColumnName", kcu.ordinal_position AS "keySeq",'
266+
+ ' rcu.delete_rule AS "onDelete", rcu.update_rule AS "onUpdate",'
266267
+ ' ccu.table_schema AS "pkOwner",'
267268
+ ' (SELECT constraint_name'
268269
+ ' FROM information_schema.table_constraints tc2'
@@ -273,6 +274,8 @@ function mixinDiscovery(PostgreSQL) {
273274
+ ' ON tc.constraint_schema = kcu.constraint_schema AND tc.constraint_name = kcu.constraint_name'
274275
+ ' JOIN information_schema.constraint_column_usage ccu'
275276
+ ' ON ccu.constraint_schema = tc.constraint_schema AND ccu.constraint_name = tc.constraint_name'
277+
+ ' JOIN information_schema.referential_constraints AS rcu'
278+
+ ' ON rcu.constraint_name = tc.constraint_name'
276279
+ ' WHERE tc.constraint_type = \'FOREIGN KEY\'';
277280
if (owner) {
278281
sql += ' AND tc.table_schema=\'' + owner + '\'';
@@ -300,6 +303,7 @@ function mixinDiscovery(PostgreSQL) {
300303
PostgreSQL.prototype.buildQueryExportedForeignKeys = function(owner, table) {
301304
let sql = 'SELECT kcu.constraint_name AS "fkName", kcu.table_schema AS "fkOwner", kcu.table_name AS "fkTableName",'
302305
+ ' kcu.column_name AS "fkColumnName", kcu.ordinal_position AS "keySeq",'
306+
+ ' rcu.delete_rule AS "onDelete", rcu.update_rule AS "onUpdate",'
303307
+ ' (SELECT constraint_name'
304308
+ ' FROM information_schema.table_constraints tc2'
305309
+ ' WHERE tc2.constraint_type = \'PRIMARY KEY\' and tc2.table_name=ccu.table_name limit 1) AS "pkName",'
@@ -308,6 +312,8 @@ function mixinDiscovery(PostgreSQL) {
308312
+ ' information_schema.constraint_column_usage ccu'
309313
+ ' JOIN information_schema.key_column_usage kcu'
310314
+ ' ON ccu.constraint_schema = kcu.constraint_schema AND ccu.constraint_name = kcu.constraint_name'
315+
+ ' JOIN information_schema.referential_constraints AS rcu'
316+
+ ' ON rcu.constraint_name = tc.constraint_name'
311317
+ ' WHERE kcu.position_in_unique_constraint IS NOT NULL';
312318
if (owner) {
313319
sql += ' and ccu.table_schema=\'' + owner + '\'';

lib/migration.js

+21-3
Original file line numberDiff line numberDiff line change
@@ -574,7 +574,9 @@ function mixinMigration(PostgreSQL) {
574574
const fkRefTable = self.table(fkEntityName);
575575
needsToDrop = !isCaseInsensitiveEqual(fkCol, fk.fkColumnName) ||
576576
!isCaseInsensitiveEqual(fkRefKey, fk.pkColumnName) ||
577-
!isCaseInsensitiveEqual(fkRefTable, fk.pkTableName);
577+
!isCaseInsensitiveEqual(fkRefTable, fk.pkTableName) ||
578+
parseAction(newFk.onDelete) != fk.onDelete ||
579+
parseAction(newFk.onUpdate) != fk.onUpdate;
578580
} else {
579581
// FK will be dropped if column is removed
580582
// only if FK is in model properties then need to drop
@@ -636,13 +638,29 @@ function mixinMigration(PostgreSQL) {
636638
// verify that the other model in the same DB
637639
if (this._models[fkEntityName]) {
638640
return 'CONSTRAINT ' + this.escapeName(fk.name) + ' ' +
639-
'FOREIGN KEY (' + this.escapeName(fk.foreignKey) + ') ' +
640-
'REFERENCES ' + this.tableEscaped(fkEntityName) + '(' + fk.entityKey + ')';
641+
'FOREIGN KEY (' + this.escapeName(fk.foreignKey) + ') ' +
642+
'REFERENCES ' + this.tableEscaped(fkEntityName) + '(' + fk.entityKey + ') ' +
643+
'ON DELETE ' + parseAction(fk.onDelete) + ' ' +
644+
'ON UPDATE ' + parseAction(fk.onUpdate);
641645
}
642646
}
643647
return '';
644648
};
645649

650+
/*!
651+
* Process model settings foreign key action,
652+
* if action is not a valid sql action return 'NO ACTION'
653+
* @param {Any} action
654+
*/
655+
function parseAction(action) {
656+
if (typeof action !== 'string') return 'NO ACTION';
657+
const _action = action.toUpperCase();
658+
if (['RESTRICT', 'CASCADE', 'SET NULL', 'SET DEFAULT'].includes(_action))
659+
return _action;
660+
else
661+
return 'NO ACTION';
662+
}
663+
646664
/*!
647665
* Case insensitive comparison of two strings
648666
* @param {String} val1

test/postgresql.autoupdate.test.js

+6
Original file line numberDiff line numberDiff line change
@@ -469,6 +469,8 @@ describe('autoupdate', function() {
469469
'entity': 'Product',
470470
'entityKey': 'id',
471471
'foreignKey': 'productId',
472+
'onDelete': 'CASCADE',
473+
'onUpdate': 'SET NULL',
472474
},
473475
},
474476
},
@@ -578,6 +580,8 @@ describe('autoupdate', function() {
578580
assert.equal(foreignKeys[0].pkTableName, 'customer_test2');
579581
assert.equal(foreignKeys[0].fkColumnName, 'customerId');
580582
assert.equal(foreignKeys[0].fkName, 'fk_ordertest_customerId');
583+
assert.equal(foreignKeys[0].onDelete, 'NO ACTION');
584+
assert.equal(foreignKeys[0].onUpdate, 'NO ACTION');
581585

582586
// update the fk of model OrderTest from customerId to productId
583587
// productId refers to model Product
@@ -596,6 +600,8 @@ describe('autoupdate', function() {
596600
assert.equal(foreignKeys[0].pkTableName, 'product_test');
597601
assert.equal(foreignKeys[0].fkColumnName, 'productId');
598602
assert.equal(foreignKeys[0].fkName, 'fk_ordertest_productId');
603+
assert.equal(foreignKeys[0].onDelete, 'CASCADE');
604+
assert.equal(foreignKeys[0].onUpdate, 'SET NULL');
599605

600606
// remove fk from model OrderTest
601607
ds.createModel(orderTest_schema_v3.name, orderTest_schema_v3.properties,

0 commit comments

Comments
 (0)