From e5348d7ed675fe77dda214713adb28ba34522d1b Mon Sep 17 00:00:00 2001 From: ChengLei Shao Date: Sun, 23 Jul 2023 17:13:30 +0800 Subject: [PATCH] fix(database): skip reference delete on view collection (#2303) --- .../__tests__/view/view-collection.test.ts | 127 ++++++++++++++++++ .../features/referential-integrity-check.ts | 6 +- 2 files changed, 132 insertions(+), 1 deletion(-) diff --git a/packages/core/database/src/__tests__/view/view-collection.test.ts b/packages/core/database/src/__tests__/view/view-collection.test.ts index a694fdd66..39fc35fc3 100644 --- a/packages/core/database/src/__tests__/view/view-collection.test.ts +++ b/packages/core/database/src/__tests__/view/view-collection.test.ts @@ -18,6 +18,133 @@ pgOnly()('', () => { await db.close(); }); + it('should skip on delete on view collection', async () => { + const Order = db.collection({ + name: 'orders', + fields: [ + { + type: 'string', + name: 'name', + }, + { + type: 'hasMany', + name: 'orderItems', + foreignKey: 'order_id', + target: 'orderItems', + }, + ], + }); + + const OrderItem = db.collection({ + name: 'orderItems', + timestamps: false, + fields: [ + { + type: 'integer', + name: 'count', + }, + { + type: 'belongsTo', + name: 'item', + target: 'items', + foreignKey: 'item_id', + }, + { + type: 'belongsTo', + name: 'order', + target: 'orders', + foreignKey: 'order_id', + onDelete: 'NO ACTION', + }, + ], + }); + + const Item = db.collection({ + name: 'items', + fields: [{ name: 'name', type: 'string' }], + }); + + await db.sync(); + + const viewName = 'order_item_view'; + + const dropViewSQL = `DROP VIEW IF EXISTS ${viewName}`; + await db.sequelize.query(dropViewSQL); + + const viewSQL = `CREATE VIEW ${viewName} as SELECT orders.*, items.name as item_name FROM ${OrderItem.quotedTableName()} as orders INNER JOIN ${Item.quotedTableName()} as items ON orders.item_id = items.id`; + + await db.sequelize.query(viewSQL); + + const OrderItemView = db.collection({ + name: viewName, + view: true, + schema: db.inDialect('postgres') ? 'public' : undefined, + fields: [ + { + type: 'bigInt', + name: 'order_id', + }, + { + type: 'bigInt', + name: 'item_id', + onDelete: 'CASCADE', + }, + ], + }); + + await db.sync(); + + Order.setField('items', { + type: 'belongsToMany', + target: 'orderItems', + through: viewName, + foreignKey: 'order_id', + otherKey: 'item_id', + sourceKey: 'id', + targetKey: 'id', + onDelete: 'CASCADE', + }); + + await db.sync(); + + const order1 = await db.getRepository('orders').create({ + values: { + name: 'order1', + orderItems: [ + { + count: 1, + item: { + name: 'item1', + }, + }, + { + count: 2, + item: { + name: 'item2', + }, + }, + ], + }, + }); + + const item1 = await db.getRepository('items').findOne({ + filter: { + name: 'item1', + }, + }); + + let error; + try { + await db.getRepository('orders').destroy({ + filterByTk: order1.get('id'), + }); + } catch (err) { + error = err; + } + + expect(error).toBeUndefined(); + }); + it('should update view collection', async () => { const UserCollection = db.collection({ name: 'users', diff --git a/packages/core/database/src/features/referential-integrity-check.ts b/packages/core/database/src/features/referential-integrity-check.ts index 3f6de630d..b1b1b278e 100644 --- a/packages/core/database/src/features/referential-integrity-check.ts +++ b/packages/core/database/src/features/referential-integrity-check.ts @@ -1,5 +1,5 @@ -import Database from '../database'; import { Model, Transactionable } from 'sequelize'; +import Database from '../database'; interface ReferentialIntegrityCheckOptions extends Transactionable { db: Database; @@ -24,6 +24,10 @@ export async function referentialIntegrityCheck(options: ReferentialIntegrityChe const sourceCollection = db.collections.get(sourceCollectionName); const sourceRepository = sourceCollection.repository; + if (sourceCollection.isView()) { + continue; + } + const filter = { [sourceField]: referencedInstance[targetField], };