diff --git a/packages/@n8n/db/src/entities/workflow-entity.ts b/packages/@n8n/db/src/entities/workflow-entity.ts index fc78500ed4d..7d1469e3fa3 100644 --- a/packages/@n8n/db/src/entities/workflow-entity.ts +++ b/packages/@n8n/db/src/entities/workflow-entity.ts @@ -100,6 +100,9 @@ export class WorkflowEntity extends WithTimestampsAndStringId implements IWorkfl @Column({ length: 36 }) versionId: string; + @Column({ default: 1 }) + versionCounter: number; + @Column({ default: 0 }) triggerCount: number; diff --git a/packages/@n8n/db/src/migrations/mysqldb/1761047826451-AddWorkflowVersionColumn.ts b/packages/@n8n/db/src/migrations/mysqldb/1761047826451-AddWorkflowVersionColumn.ts new file mode 100644 index 00000000000..041d9534ffd --- /dev/null +++ b/packages/@n8n/db/src/migrations/mysqldb/1761047826451-AddWorkflowVersionColumn.ts @@ -0,0 +1,40 @@ +import type { MigrationContext, ReversibleMigration } from '../migration-types'; + +/** + * MySQL-specific migration to add versionCounter column and trigger for auto-incrementing. + */ +export class AddWorkflowVersionColumn1761047826451 implements ReversibleMigration { + async up({ queryRunner, tablePrefix }: MigrationContext) { + const tableName = `${tablePrefix}workflow_entity`; + const triggerName = `${tablePrefix}workflow_version_increment`; + + // Add versionCounter column + await queryRunner.query( + `ALTER TABLE \`${tableName}\` ADD COLUMN \`versionCounter\` int NOT NULL DEFAULT 1`, + ); + + // Create trigger that increments version counter before update. + // NOTE: we're modifying the NEW record before the update happens, so we do it BEFORE the update. + await queryRunner.query(` + CREATE TRIGGER \`${triggerName}\` + BEFORE UPDATE ON \`${tableName}\` + FOR EACH ROW + BEGIN + IF OLD.versionCounter = NEW.versionCounter THEN + SET NEW.versionCounter = OLD.versionCounter + 1; + END IF; + END; + `); + } + + async down({ queryRunner, tablePrefix }: MigrationContext) { + const tableName = `${tablePrefix}workflow_entity`; + const triggerName = `${tablePrefix}workflow_version_increment`; + + // Drop trigger + await queryRunner.query(`DROP TRIGGER IF EXISTS \`${triggerName}\``); + + // Drop column + await queryRunner.query(`ALTER TABLE \`${tableName}\` DROP COLUMN \`versionCounter\``); + } +} diff --git a/packages/@n8n/db/src/migrations/mysqldb/index.ts b/packages/@n8n/db/src/migrations/mysqldb/index.ts index 17f776db6c6..8900f535336 100644 --- a/packages/@n8n/db/src/migrations/mysqldb/index.ts +++ b/packages/@n8n/db/src/migrations/mysqldb/index.ts @@ -2,7 +2,6 @@ import { DropUnusedChatHubColumns1760965142113 } from './1760965142113-DropUnuse import { AddAudienceColumnToApiKeys1758731786132 } from '../common/1758731786132-AddAudienceColumnToApiKey'; import { CreateWorkflowDependencyTable1760314000000 } from '../common/1760314000000-CreateWorkflowDependencyTable'; import { AddMfaColumns1690000000030 } from './../common/1690000000040-AddMfaColumns'; -import { LinkRoleToProjectRelationTable1753953244168 } from './../common/1753953244168-LinkRoleToProjectRelationTable'; import { InitialMigration1588157391238 } from './1588157391238-InitialMigration'; import { WebhookModel1592447867632 } from './1592447867632-WebhookModel'; import { CreateIndexStoppedAt1594902918301 } from './1594902918301-CreateIndexStoppedAt'; @@ -50,6 +49,7 @@ import { CreateTestMetricTable1732271325258 } from './1732271325258-CreateTestMe import { AddStatsColumnsToTestRun1736172058779 } from './1736172058779-AddStatsColumnsToTestRun'; import { FixTestDefinitionPrimaryKey1739873751194 } from './1739873751194-FixTestDefinitionPrimaryKey'; import { AddProjectIdToVariableTable1758794506893 } from './1758794506893-AddProjectIdToVariableTable'; +import { AddWorkflowVersionColumn1761047826451 } from './1761047826451-AddWorkflowVersionColumn'; import { CreateLdapEntities1674509946020 } from '../common/1674509946020-CreateLdapEntities'; import { PurgeInvalidWorkflowConnections1675940580449 } from '../common/1675940580449-PurgeInvalidWorkflowConnections'; import { RemoveResetPasswordColumns1690000000030 } from '../common/1690000000030-RemoveResetPasswordColumns'; @@ -106,6 +106,7 @@ import { CreateChatHubTables1760019379982 } from '../common/1760019379982-Create import { UniqueRoleNames1760020838000 } from '../common/1760020838000-UniqueRoleNames'; import type { Migration } from '../migration-types'; import { UpdateParentFolderIdColumn1740445074052 } from '../mysqldb/1740445074052-UpdateParentFolderIdColumn'; +import { LinkRoleToProjectRelationTable1753953244168 } from './../common/1753953244168-LinkRoleToProjectRelationTable'; export const mysqlMigrations: Migration[] = [ InitialMigration1588157391238, @@ -215,4 +216,5 @@ export const mysqlMigrations: Migration[] = [ UniqueRoleNames1760020838000, CreateWorkflowDependencyTable1760314000000, DropUnusedChatHubColumns1760965142113, + AddWorkflowVersionColumn1761047826451, ]; diff --git a/packages/@n8n/db/src/migrations/postgresdb/1761047826451-AddWorkflowVersionColumn.ts b/packages/@n8n/db/src/migrations/postgresdb/1761047826451-AddWorkflowVersionColumn.ts new file mode 100644 index 00000000000..61d820db450 --- /dev/null +++ b/packages/@n8n/db/src/migrations/postgresdb/1761047826451-AddWorkflowVersionColumn.ts @@ -0,0 +1,52 @@ +import type { MigrationContext, ReversibleMigration } from '../migration-types'; + +/** + * PostgreSQL-specific migration to add versionCounter column and trigger for auto-incrementing. + */ +export class AddWorkflowVersionColumn1761047826451 implements ReversibleMigration { + async up({ queryRunner, tablePrefix }: MigrationContext) { + const tableName = `${tablePrefix}workflow_entity`; + const triggerName = `${tablePrefix}workflow_version_increment`; + const functionName = `${tablePrefix}increment_workflow_version`; + + // Add versionCounter column + await queryRunner.query( + `ALTER TABLE ${tableName} ADD COLUMN "versionCounter" integer NOT NULL DEFAULT 1`, + ); + + // Create function that increments version counter. + // NOTE: we're modifying the NEW record before the update happens, so we do it BEFORE the update. + await queryRunner.query(` + CREATE OR REPLACE FUNCTION ${functionName}() + RETURNS TRIGGER AS $$ + BEGIN + IF NEW."versionCounter" IS NOT DISTINCT FROM OLD."versionCounter" THEN + NEW."versionCounter" = OLD."versionCounter" + 1; + END IF; + RETURN NEW; + END; + $$ LANGUAGE plpgsql; + `); + + // Create trigger that calls the function before update + await queryRunner.query(` + CREATE TRIGGER ${triggerName} + BEFORE UPDATE ON ${tableName} + FOR EACH ROW + EXECUTE FUNCTION ${functionName}(); + `); + } + + async down({ queryRunner, tablePrefix }: MigrationContext) { + const tableName = `${tablePrefix}workflow_entity`; + const triggerName = `${tablePrefix}workflow_version_increment`; + const functionName = `${tablePrefix}increment_workflow_version`; + + // Drop trigger and function + await queryRunner.query(`DROP TRIGGER IF EXISTS ${triggerName} ON ${tableName}`); + await queryRunner.query(`DROP FUNCTION IF EXISTS ${functionName}`); + + // Drop column + await queryRunner.query(`ALTER TABLE ${tableName} DROP COLUMN "versionCounter"`); + } +} diff --git a/packages/@n8n/db/src/migrations/postgresdb/index.ts b/packages/@n8n/db/src/migrations/postgresdb/index.ts index 4242a170df1..73a832a257b 100644 --- a/packages/@n8n/db/src/migrations/postgresdb/index.ts +++ b/packages/@n8n/db/src/migrations/postgresdb/index.ts @@ -46,6 +46,7 @@ import { FixExecutionMetadataSequence1721377157740 } from './1721377157740-FixEx import { MigrateTestDefinitionKeyToString1731582748663 } from './1731582748663-MigrateTestDefinitionKeyToString'; import { UpdateParentFolderIdColumn1740445074052 } from './1740445074052-UpdateParentFolderIdColumn'; import { AddProjectIdToVariableTable1758794506893 } from './1758794506893-AddProjectIdToVariableTable'; +import { AddWorkflowVersionColumn1761047826451 } from './1761047826451-AddWorkflowVersionColumn'; import { CreateLdapEntities1674509946020 } from '../common/1674509946020-CreateLdapEntities'; import { PurgeInvalidWorkflowConnections1675940580449 } from '../common/1675940580449-PurgeInvalidWorkflowConnections'; import { RemoveResetPasswordColumns1690000000030 } from '../common/1690000000030-RemoveResetPasswordColumns'; @@ -213,4 +214,5 @@ export const postgresMigrations: Migration[] = [ UniqueRoleNames1760020838000, CreateWorkflowDependencyTable1760314000000, DropUnusedChatHubColumns1760965142113, + AddWorkflowVersionColumn1761047826451, ]; diff --git a/packages/@n8n/db/src/migrations/sqlite/1761047826451-AddWorkflowVersionColumn.ts b/packages/@n8n/db/src/migrations/sqlite/1761047826451-AddWorkflowVersionColumn.ts new file mode 100644 index 00000000000..f02fb7b39e8 --- /dev/null +++ b/packages/@n8n/db/src/migrations/sqlite/1761047826451-AddWorkflowVersionColumn.ts @@ -0,0 +1,41 @@ +import type { MigrationContext, ReversibleMigration } from '../migration-types'; + +/** + * SQLite-specific migration to add versionCounter column and trigger for auto-incrementing. + */ +export class AddWorkflowVersionColumn1761047826451 implements ReversibleMigration { + async up({ queryRunner, tablePrefix }: MigrationContext) { + const tableName = `${tablePrefix}workflow_entity`; + const triggerName = `${tablePrefix}workflow_version_increment`; + + // Add versionCounter column + await queryRunner.query( + `ALTER TABLE ${tableName} ADD COLUMN "versionCounter" integer NOT NULL DEFAULT 1`, + ); + + // Create trigger that increments version counter on update. + // NOTE: we perform the version counter bump AFTER so it isn't overwritten by the original update. + await queryRunner.query(` + CREATE TRIGGER ${triggerName} + AFTER UPDATE ON ${tableName} + FOR EACH ROW + WHEN OLD."versionCounter" = NEW."versionCounter" + BEGIN + UPDATE ${tableName} + SET "versionCounter" = "versionCounter" + 1 + WHERE id = NEW.id; + END; + `); + } + + async down({ queryRunner, tablePrefix }: MigrationContext) { + const tableName = `${tablePrefix}workflow_entity`; + const triggerName = `${tablePrefix}workflow_version_increment`; + + // Drop trigger + await queryRunner.query(`DROP TRIGGER IF EXISTS ${triggerName}`); + + // Drop column + await queryRunner.query(`ALTER TABLE ${tableName} DROP COLUMN "versionCounter"`); + } +} diff --git a/packages/@n8n/db/src/migrations/sqlite/index.ts b/packages/@n8n/db/src/migrations/sqlite/index.ts index 09c6567dbb9..7fdc6c09978 100644 --- a/packages/@n8n/db/src/migrations/sqlite/index.ts +++ b/packages/@n8n/db/src/migrations/sqlite/index.ts @@ -44,6 +44,7 @@ import { MigrateTestDefinitionKeyToString1731582748663 } from './1731582748663-M import { CreateFolderTable1738709609940 } from './1738709609940-CreateFolderTable'; import { UpdateParentFolderIdColumn1740445074052 } from './1740445074052-UpdateParentFolderIdColumn'; import { AddScopesColumnToApiKeys1742918400000 } from './1742918400000-AddScopesColumnToApiKeys'; +import { AddWorkflowVersionColumn1761047826451 } from './1761047826451-AddWorkflowVersionColumn'; import { UniqueWorkflowNames1620821879465 } from '../common/1620821879465-UniqueWorkflowNames'; import { UpdateWorkflowCredentials1630330987096 } from '../common/1630330987096-UpdateWorkflowCredentials'; import { AddNodeIds1658930531669 } from '../common/1658930531669-AddNodeIds'; @@ -207,6 +208,7 @@ const sqliteMigrations: Migration[] = [ UniqueRoleNames1760020838000, CreateWorkflowDependencyTable1760314000000, DropUnusedChatHubColumns1760965142113, + AddWorkflowVersionColumn1761047826451, ]; export { sqliteMigrations };