From 3d8c27ff1b16feda32adea8d06c21b13067b10e2 Mon Sep 17 00:00:00 2001 From: Junyi Date: Fri, 14 Mar 2025 00:06:18 +0800 Subject: [PATCH] fix(plugin-workflow-manual): fix migration (#6445) * fix(plugin-workflow-manual): fix migration * fix(plugin-workflow-manual): fix constraints key name * test(plugin-workflow-manual): add test case and fix * fix(plugin-workflow-manual): add migration to delete m2m fields * fix(plugin-workflow-manual): fix esists check * fix(plugin-workflow-manual): fix drop primary key in mysql * fix(plugin-workflow-manual): ignore sqlite * test(plugin-workflow-manual): check mysql * test(plugin-workflow-manual): use isolate level on transaction * test(plugin-workflow-manual): use described columns to check --- .../database/src/__tests__/collection.test.ts | 19 ++++++ .../20250312100512-change-table-name.test.ts | 48 ++++++++++++++ .../20250312100512-change-table-name.ts | 62 ++++++++++++++++--- .../20250313133421-remove-m2m-fields.ts | 38 ++++++++++++ 4 files changed, 159 insertions(+), 8 deletions(-) create mode 100644 packages/plugins/@nocobase/plugin-workflow-manual/src/server/migrations/20250313133421-remove-m2m-fields.ts diff --git a/packages/core/database/src/__tests__/collection.test.ts b/packages/core/database/src/__tests__/collection.test.ts index 81b719bd9d..dbe8dd1ceb 100644 --- a/packages/core/database/src/__tests__/collection.test.ts +++ b/packages/core/database/src/__tests__/collection.test.ts @@ -447,4 +447,23 @@ describe('collection sync', () => { expect(error).toBeInstanceOf(IdentifierError); }); + + test('paranoid', async () => { + const postCollection = db.collection({ + name: 'posts', + fields: [{ type: 'string', name: 'title' }], + paranoid: true, + }); + + await db.sync(); + + const p1 = await postCollection.repository.create({ values: { title: 't1' } }); + await p1.destroy(); + + const p2 = await postCollection.repository.findOne({ filterByTk: p1.id }); + expect(p2).toBeNull(); + + const p3 = await postCollection.repository.findOne({ filterByTk: p1.id, paranoid: false }); + expect(p3).not.toBeNull(); + }); }); diff --git a/packages/plugins/@nocobase/plugin-workflow-manual/src/server/__tests__/migrations/20250312100512-change-table-name.test.ts b/packages/plugins/@nocobase/plugin-workflow-manual/src/server/__tests__/migrations/20250312100512-change-table-name.test.ts index dbaa6093ea..af8a96f156 100644 --- a/packages/plugins/@nocobase/plugin-workflow-manual/src/server/__tests__/migrations/20250312100512-change-table-name.test.ts +++ b/packages/plugins/@nocobase/plugin-workflow-manual/src/server/__tests__/migrations/20250312100512-change-table-name.test.ts @@ -12,6 +12,8 @@ import { describe, test } from 'vitest'; import workflowManualTasks from '../../collections/workflowManualTasks'; import Migration from '../../migrations/20250312100512-change-table-name'; +const skipSqlite = process.env.DB_DIALECT === 'sqlite' ? test.skip : test; + const matrix: [string, string][] = [ // schema, tablePrefix [undefined, undefined], @@ -80,4 +82,50 @@ describe('20250225175712-change-table-name.test', () => { await app.destroy(); }); + + skipSqlite(`multiple primary keys`, async () => { + const app = await createMockServer(); + await app.version.update('1.5.0'); + // mock m2m collections + app.db.collection({ + ...workflowManualTasks, + name: 'users_jobs', + }); + app.db.collection({ + name: 'users', + fields: [{ name: 'id', type: 'bigInt', primaryKey: true }], + }); + app.db.collection({ + name: 'jobs', + fields: [ + { name: 'id', type: 'bigInt', primaryKey: true }, + { + type: 'belongsToMany', + name: 'users', + through: 'users_jobs', + }, + ], + }); + await app.db.sync(); + + const migration = new Migration({ db: app.db, app } as any); + await migration.up(); + + app.db.collection({ + ...workflowManualTasks, + }); + app.db.removeCollection('jobs'); + app.db.collection({ + name: 'jobs', + fields: [{ name: 'id', type: 'bigInt', primaryKey: true }], + }); + await app.db.sync(); + const columns = await app.db.sequelize + .getQueryInterface() + .describeTable(app.db.getCollection(workflowManualTasks.name).getTableNameWithSchema()); + const primaryKeys = Object.values(columns).filter((c) => c.primaryKey); + expect(primaryKeys.length).toBe(1); + + await app.destroy(); + }); }); diff --git a/packages/plugins/@nocobase/plugin-workflow-manual/src/server/migrations/20250312100512-change-table-name.ts b/packages/plugins/@nocobase/plugin-workflow-manual/src/server/migrations/20250312100512-change-table-name.ts index 13f6e3d281..08e5b00aed 100644 --- a/packages/plugins/@nocobase/plugin-workflow-manual/src/server/migrations/20250312100512-change-table-name.ts +++ b/packages/plugins/@nocobase/plugin-workflow-manual/src/server/migrations/20250312100512-change-table-name.ts @@ -7,7 +7,9 @@ * For more information, please refer to: https://www.nocobase.com/agreement. */ +import { Transaction } from 'sequelize'; import { Migration } from '@nocobase/server'; +import workflowManualTasks from '../collections/workflowManualTasks'; export default class extends Migration { appVersion = '<1.7.0'; @@ -16,24 +18,55 @@ export default class extends Migration { const { db } = this.context; const queryInterface = db.sequelize.getQueryInterface(); const usersJobsCollection = db.collection({ + ...workflowManualTasks, name: 'users_jobs', - fields: [{ name: 'id', type: 'bigInt' }], }); const workflowManualTasksCollection = db.collection({ - name: 'workflowManualTasks', - fields: [{ name: 'id', type: 'bigInt' }], + ...workflowManualTasks, }); const oldTableName = usersJobsCollection.getTableNameWithSchema(); const oldTableNameWithQuotes = usersJobsCollection.getRealTableName(true); const newTableName = workflowManualTasksCollection.getTableNameWithSchema(); const newTableNameWithQuotes = workflowManualTasksCollection.getRealTableName(true); - await db.sequelize.transaction(async (transaction) => { - const exists = await queryInterface.tableExists(oldTableName, { transaction }); - if (exists) { + + const exists = await queryInterface.tableExists(oldTableName); + if (!exists) { + return; + } + // @ts-ignore + const constraints: any = await queryInterface.showConstraint(oldTableName); + // PG: + // { + // constraintCatalog: 'nocobase_test', + // constraintSchema: 'public', + // constraintName: 'posts_tags_pkey', + // tableCatalog: 'nocobase_test', + // tableSchema: 'public', + // tableName: 'posts_tags', + // constraintType: 'PRIMARY KEY', // use this to determine + // isDeferrable: 'NO', + // initiallyDeferred: 'NO' + // } + // MYSQL: + // { + // constraintCatalog: 'def', + // constraintName: 'PRIMARY', + // constraintSchema: 'nocobase_test', + // constraintType: 'PRIMARY KEY', // use this to determine + // tableName: 'posts_tags', + // tableSchema: 'nocobase_test' + // } + + await db.sequelize.transaction( + { + isolationLevel: Transaction.ISOLATION_LEVELS.SERIALIZABLE, + }, + async (transaction) => { const newExists = await queryInterface.tableExists(newTableName, { transaction }); if (newExists) { await queryInterface.dropTable(newTableName, { transaction }); } + if (this.db.isPostgresCompatibleDialect()) { await db.sequelize.query( `ALTER TABLE ${oldTableNameWithQuotes} RENAME TO "${db.options.tablePrefix || ''}${ @@ -47,6 +80,19 @@ export default class extends Migration { await queryInterface.renameTable(oldTableName, newTableName, { transaction }); } + if (this.db.isPostgresCompatibleDialect()) { + const primaryKeys = constraints.filter((item) => item.constraintType === 'PRIMARY KEY'); + if (primaryKeys.length) { + for (const primaryKey of primaryKeys) { + await queryInterface.removeConstraint(newTableName, primaryKey.constraintName, { transaction }); + } + } + } else if (this.db.isMySQLCompatibleDialect()) { + await db.sequelize.query(`ALTER TABLE ${newTableNameWithQuotes} DROP PRIMARY KEY, ADD PRIMARY KEY (id)`, { + transaction, + }); + } + const indexes: any = await queryInterface.showIndex(newTableName, { transaction }); const oldIndexPrefix = `${db.options.tablePrefix || ''}users_jobs`; @@ -71,8 +117,8 @@ export default class extends Migration { } } } - } - }); + }, + ); db.removeCollection('users_jobs'); db.removeCollection('workflowManualTasks'); } diff --git a/packages/plugins/@nocobase/plugin-workflow-manual/src/server/migrations/20250313133421-remove-m2m-fields.ts b/packages/plugins/@nocobase/plugin-workflow-manual/src/server/migrations/20250313133421-remove-m2m-fields.ts new file mode 100644 index 0000000000..036a8336ed --- /dev/null +++ b/packages/plugins/@nocobase/plugin-workflow-manual/src/server/migrations/20250313133421-remove-m2m-fields.ts @@ -0,0 +1,38 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +import { Migration } from '@nocobase/server'; + +export default class extends Migration { + appVersion = '<1.7.0'; + on = 'afterLoad'; + async up() { + const { db } = this.context; + await db.sequelize.transaction(async (transaction) => { + const FieldRepo = db.getCollection('fields').repository; + await FieldRepo.destroy({ + filter: { + collectionName: 'users', + name: 'jobs', + }, + transaction, + }); + + await FieldRepo.destroy({ + filter: { + collectionName: 'jobs', + name: 'users', + }, + transaction, + }); + + db.removeCollection('fields'); + }); + } +}