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
This commit is contained in:
Junyi 2025-03-14 00:06:18 +08:00 committed by GitHub
parent 1be324f524
commit 3d8c27ff1b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 159 additions and 8 deletions

View File

@ -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();
});
});

View File

@ -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();
});
});

View File

@ -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');
}

View File

@ -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');
});
}
}