From e566d56c2d75ebe5134055b482112641910add0d Mon Sep 17 00:00:00 2001 From: ChengLei Shao Date: Tue, 28 Mar 2023 12:26:30 +0800 Subject: [PATCH] fix: destroy through table record referencing collections table (#1611) * test: destory through table record referencing collections table * fix: belongs to many fields to reference --- .../database/src/features/ReferencesMap.ts | 5 +- .../src/fields/belongs-to-many-field.ts | 17 +++- .../reference-integerity-check.test.ts | 9 +- .../__tests__/reference-integerity.test.ts | 93 +++++++++++++++++++ 4 files changed, 118 insertions(+), 6 deletions(-) create mode 100644 packages/plugins/collection-manager/src/__tests__/reference-integerity.test.ts diff --git a/packages/core/database/src/features/ReferencesMap.ts b/packages/core/database/src/features/ReferencesMap.ts index 654201f21..ca4ff7364 100644 --- a/packages/core/database/src/features/ReferencesMap.ts +++ b/packages/core/database/src/features/ReferencesMap.ts @@ -1,5 +1,3 @@ -import lodash from 'lodash'; - export interface Reference { sourceCollectionName: string; sourceField: string; @@ -16,6 +14,8 @@ class ReferencesMap { reference.onDelete = 'SET NULL'; } + reference.onDelete = reference.onDelete.toUpperCase(); + const existReference = this.existReference(reference); if (existReference && existReference.onDelete !== reference.onDelete) { @@ -59,6 +59,7 @@ class ReferencesMap { removeReference(reference: Reference) { const references = this.map.get(reference.targetCollectionName); + if (!references) { return; } diff --git a/packages/core/database/src/fields/belongs-to-many-field.ts b/packages/core/database/src/fields/belongs-to-many-field.ts index df3a1dd13..1addf1747 100644 --- a/packages/core/database/src/fields/belongs-to-many-field.ts +++ b/packages/core/database/src/fields/belongs-to-many-field.ts @@ -32,9 +32,21 @@ export class BelongsToManyField extends RelationField { const onDelete = this.options.onDelete || 'CASCADE'; + const targetAssociation = association.toTarget; + + if (association.targetKey) { + targetAssociation.targetKey = association.targetKey; + } + + const sourceAssociation = association.toSource; + + if (association.sourceKey) { + sourceAssociation.targetKey = association.sourceKey; + } + return [ - BelongsToField.toReference(db, association.toSource, onDelete), - BelongsToField.toReference(db, association.toTarget, onDelete), + BelongsToField.toReference(db, targetAssociation, onDelete), + BelongsToField.toReference(db, sourceAssociation, onDelete), ]; } @@ -59,6 +71,7 @@ export class BelongsToManyField extends RelationField { name: through, }; + // set through collection schema if (this.collection.collectionSchema()) { throughCollectionOptions['schema'] = this.collection.collectionSchema(); } diff --git a/packages/plugins/collection-manager/src/__tests__/associations/reference-integerity-check.test.ts b/packages/plugins/collection-manager/src/__tests__/associations/reference-integerity-check.test.ts index 6bc843229..ae2769339 100644 --- a/packages/plugins/collection-manager/src/__tests__/associations/reference-integerity-check.test.ts +++ b/packages/plugins/collection-manager/src/__tests__/associations/reference-integerity-check.test.ts @@ -1,11 +1,16 @@ -import Database, { Collection as DBCollection } from '@nocobase/database'; +import Database from '@nocobase/database'; import { MockServer, mockServer } from '@nocobase/test'; describe('reference integrity check', () => { let db: Database; let app: MockServer; beforeEach(async () => { - app = mockServer(); + app = mockServer({ + database: { + tablePrefix: '', + }, + }); + await app.db.clean({ drop: true }); db = app.db; }); diff --git a/packages/plugins/collection-manager/src/__tests__/reference-integerity.test.ts b/packages/plugins/collection-manager/src/__tests__/reference-integerity.test.ts new file mode 100644 index 000000000..416360069 --- /dev/null +++ b/packages/plugins/collection-manager/src/__tests__/reference-integerity.test.ts @@ -0,0 +1,93 @@ +import Database from '@nocobase/database'; +import Application from '@nocobase/server'; +import { createApp } from './index'; + +describe('reference integrity check', () => { + let db: Database; + let app: Application; + + beforeEach(async () => { + app = await createApp({ + database: { + tablePrefix: '', + }, + }); + db = app.db; + }); + + afterEach(async () => { + await app.destroy(); + }); + + it('should cascade delete on belongs to many relation on collection', async () => { + const users = db.collection({ + name: 'users', + fields: [ + { + type: 'string', + name: 'name', + }, + { + type: 'belongsToMany', + name: 'collections', + target: 'collections', + sourceKey: 'id', + foreignKey: 'user_id', + otherKey: 'collection_name', + targetKey: 'name', + onDelete: 'cascade', + through: 'users_collections', + }, + ], + }); + + db.extendCollection({ + name: 'collections', + fields: [ + { + type: 'belongsToMany', + name: 'users', + target: 'users', + onDelete: 'cascade', + through: 'users_collections', + sourceKey: 'name', + foreignKey: 'collection_name', + otherKey: 'user_id', + targetKey: 'id', + }, + ], + }); + + await db.sync(); + + const postsCollection = await db.getCollection('collections').repository.create({ + values: { + name: 'posts', + }, + }); + + const user1 = await users.repository.create({ + values: { + name: 'foo', + collections: [postsCollection], + }, + }); + + const throughCollection = db.getCollection('users_collections'); + + expect(await throughCollection.repository.count()).toEqual(1); + await user1.destroy(); + expect(await throughCollection.repository.count()).toEqual(0); + + const user2 = await users.repository.create({ + values: { + name: 'bar', + collections: [postsCollection], + }, + }); + + expect(await throughCollection.repository.count()).toEqual(1); + await postsCollection.destroy(); + expect(await throughCollection.repository.count()).toEqual(0); + }); +});