From ed3b19a3dedd073ec823fa48563565a8280a327f Mon Sep 17 00:00:00 2001 From: mfsiega <93014743+mfsiega@users.noreply.github.com> Date: Wed, 15 Oct 2025 14:47:28 +0200 Subject: [PATCH] feat(core): Database migration to add workflow dependency index table (#20723) --- .../@n8n/db/src/entities/abstract-entity.ts | 18 +++-- packages/@n8n/db/src/entities/index.ts | 3 + .../entities/workflow-dependency-entity.ts | 69 +++++++++++++++++++ ...314000000-CreateWorkflowDependencyTable.ts | 36 ++++++++++ packages/@n8n/db/src/migrations/dsl/column.ts | 8 ++- packages/@n8n/db/src/migrations/dsl/table.ts | 5 ++ .../@n8n/db/src/migrations/mysqldb/index.ts | 2 + .../db/src/migrations/postgresdb/index.ts | 2 + .../@n8n/db/src/migrations/sqlite/index.ts | 2 + 9 files changed, 139 insertions(+), 6 deletions(-) create mode 100644 packages/@n8n/db/src/entities/workflow-dependency-entity.ts create mode 100644 packages/@n8n/db/src/migrations/common/1760314000000-CreateWorkflowDependencyTable.ts diff --git a/packages/@n8n/db/src/entities/abstract-entity.ts b/packages/@n8n/db/src/entities/abstract-entity.ts index 1788e16ff23..79f46158205 100644 --- a/packages/@n8n/db/src/entities/abstract-entity.ts +++ b/packages/@n8n/db/src/entities/abstract-entity.ts @@ -61,11 +61,8 @@ function mixinStringId>(base: T) { } // eslint-disable-next-line @typescript-eslint/no-explicit-any -function mixinTimestamps>(base: T) { +function mixinUpdatedAt>(base: T) { class Derived extends base { - @CreateDateColumn(tsColumnOptions) - createdAt: Date; - @UpdateDateColumn(tsColumnOptions) updatedAt: Date; @@ -77,8 +74,19 @@ function mixinTimestamps>(base: T) { return Derived; } +// eslint-disable-next-line @typescript-eslint/no-explicit-any +function mixinCreatedAt>(base: T) { + class Derived extends base { + @CreateDateColumn(tsColumnOptions) + createdAt: Date; + } + return Derived; +} + class BaseEntity {} export const WithStringId = mixinStringId(BaseEntity); -export const WithTimestamps = mixinTimestamps(BaseEntity); +export const WithCreatedAt = mixinCreatedAt(BaseEntity); +export const WithUpdatedAt = mixinUpdatedAt(BaseEntity); +export const WithTimestamps = mixinCreatedAt(mixinUpdatedAt(BaseEntity)); export const WithTimestampsAndStringId = mixinStringId(WithTimestamps); diff --git a/packages/@n8n/db/src/entities/index.ts b/packages/@n8n/db/src/entities/index.ts index 9b423c8744c..fef6abae8ae 100644 --- a/packages/@n8n/db/src/entities/index.ts +++ b/packages/@n8n/db/src/entities/index.ts @@ -26,6 +26,7 @@ import { TestRun } from './test-run.ee'; import { User } from './user'; import { Variables } from './variables'; import { WebhookEntity } from './webhook-entity'; +import { WorkflowDependency } from './workflow-dependency-entity'; import { WorkflowEntity } from './workflow-entity'; import { WorkflowHistory } from './workflow-history'; import { WorkflowStatistics } from './workflow-statistics'; @@ -50,6 +51,7 @@ export { SharedWorkflow, TagEntity, User, + WorkflowDependency, WorkflowEntity, WorkflowStatistics, WorkflowTagMapping, @@ -84,6 +86,7 @@ export const entities = { SharedWorkflow, TagEntity, User, + WorkflowDependency, WorkflowEntity, WorkflowStatistics, WorkflowTagMapping, diff --git a/packages/@n8n/db/src/entities/workflow-dependency-entity.ts b/packages/@n8n/db/src/entities/workflow-dependency-entity.ts new file mode 100644 index 00000000000..23d63359086 --- /dev/null +++ b/packages/@n8n/db/src/entities/workflow-dependency-entity.ts @@ -0,0 +1,69 @@ +import { + Column, + Entity, + Index, + JoinColumn, + ManyToOne, + PrimaryGeneratedColumn, + Relation, +} from '@n8n/typeorm'; + +import { WithCreatedAt } from './abstract-entity'; +import type { WorkflowEntity } from './workflow-entity'; + +export type DependencyType = 'credential' | 'nodeType' | 'webhookPath' | 'workflowCall'; + +@Entity({ name: 'workflow_dependency' }) +export class WorkflowDependency extends WithCreatedAt { + @PrimaryGeneratedColumn() + id: number; + + /** + * The ID of the workflow the dependency belongs to. + */ + @Column({ length: 36 }) + @Index() + workflowId: string; + + /** + * The version ID of the workflow the dependency belongs to. + * Used to ensure consistency between the workflow and dependency tables. + */ + @Column({ type: 'int' }) + workflowVersionId: number; + + /** + * The type of the dependency. + * credential | nodeType | webhookPath | workflowCall + */ + @Column({ length: 32 }) + @Index() + dependencyType: DependencyType; + + /** + * The ID of the dependency, interpreted based on the dependency type. + * E.g., for 'credential' it would be the credential ID, for 'nodeType' the node type name, etc. + */ + @Column({ length: 255 }) + @Index() + dependencyKey: string; + + /** + * Additional information about the dependency, interpreted based on the type. + * E.g., for 'nodeType' it could be the node ID, for 'webhookPath' the webhook ID. + */ + @Column({ type: 'varchar', length: 255, nullable: true }) + dependencyInfo: string | null; + + /** + * The version of the index structure. Used for migrations and updates. + */ + @Column({ type: 'smallint', default: 1 }) + indexVersionId: number; + + @ManyToOne('WorkflowEntity', { + onDelete: 'CASCADE', + }) + @JoinColumn({ name: 'workflowId' }) + workflow: Relation; +} diff --git a/packages/@n8n/db/src/migrations/common/1760314000000-CreateWorkflowDependencyTable.ts b/packages/@n8n/db/src/migrations/common/1760314000000-CreateWorkflowDependencyTable.ts new file mode 100644 index 00000000000..c4bb70be67e --- /dev/null +++ b/packages/@n8n/db/src/migrations/common/1760314000000-CreateWorkflowDependencyTable.ts @@ -0,0 +1,36 @@ +import type { MigrationContext, ReversibleMigration } from '../migration-types'; + +export class CreateWorkflowDependencyTable1760314000000 implements ReversibleMigration { + async up({ schemaBuilder: { createTable, column } }: MigrationContext) { + await createTable('workflow_dependency') + .withColumns( + column('id').int.primary.autoGenerate2, + column('workflowId').varchar(36).notNull, + column('workflowVersionId').int.notNull.comment('Version of the workflow'), + column('dependencyType') + .varchar(32) + .notNull.comment( + 'Type of dependency: "credential", "nodeType", "webhookPath", or "workflowCall"', + ), + column('dependencyKey').varchar(255).notNull.comment('ID or name of the dependency'), + column('dependencyInfo') + .varchar(255) + .comment('Additional info about the dependency, interpreted based on type'), + column('indexVersionId') + .smallint.notNull.default(1) + .comment('Version of the index structure'), + ) + .withForeignKey('workflowId', { + tableName: 'workflow_entity', + columnName: 'id', + onDelete: 'CASCADE', + }) + .withIndexOn(['workflowId']) + .withIndexOn(['dependencyType']) + .withIndexOn(['dependencyKey']).withCreatedAt; + } + + async down({ schemaBuilder: { dropTable } }: MigrationContext) { + await dropTable('workflow_dependency'); + } +} diff --git a/packages/@n8n/db/src/migrations/dsl/column.ts b/packages/@n8n/db/src/migrations/dsl/column.ts index 10ccc3e17e2..6c09bc868cb 100644 --- a/packages/@n8n/db/src/migrations/dsl/column.ts +++ b/packages/@n8n/db/src/migrations/dsl/column.ts @@ -11,7 +11,8 @@ export class Column { | 'timestamp' | 'uuid' | 'double' - | 'bigint'; + | 'bigint' + | 'smallint'; private isGenerated = false; @@ -46,6 +47,11 @@ export class Column { return this; } + get smallint() { + this.type = 'smallint'; + return this; + } + get double() { this.type = 'double'; return this; diff --git a/packages/@n8n/db/src/migrations/dsl/table.ts b/packages/@n8n/db/src/migrations/dsl/table.ts index 112db128979..06d9f790dfe 100644 --- a/packages/@n8n/db/src/migrations/dsl/table.ts +++ b/packages/@n8n/db/src/migrations/dsl/table.ts @@ -41,6 +41,11 @@ export class CreateTable extends TableOperation { return this; } + get withCreatedAt() { + this.columns.push(new Column('createdAt').timestampTimezone().notNull.default('NOW()')); + return this; + } + withIndexOn(columnName: string | string[], isUnique = false) { const columnNames = Array.isArray(columnName) ? columnName : [columnName]; this.indices.add({ columnNames, isUnique }); diff --git a/packages/@n8n/db/src/migrations/mysqldb/index.ts b/packages/@n8n/db/src/migrations/mysqldb/index.ts index feb130f87ec..00939760938 100644 --- a/packages/@n8n/db/src/migrations/mysqldb/index.ts +++ b/packages/@n8n/db/src/migrations/mysqldb/index.ts @@ -1,4 +1,5 @@ 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'; @@ -211,4 +212,5 @@ export const mysqlMigrations: Migration[] = [ ChangeValueTypesForInsights1759399811000, CreateChatHubTables1760019379982, UniqueRoleNames1760020838000, + CreateWorkflowDependencyTable1760314000000, ]; diff --git a/packages/@n8n/db/src/migrations/postgresdb/index.ts b/packages/@n8n/db/src/migrations/postgresdb/index.ts index f399b61c34a..d8e0807e0aa 100644 --- a/packages/@n8n/db/src/migrations/postgresdb/index.ts +++ b/packages/@n8n/db/src/migrations/postgresdb/index.ts @@ -102,6 +102,7 @@ import { AddAudienceColumnToApiKeys1758731786132 } from '../common/1758731786132 import { ChangeValueTypesForInsights1759399811000 } from '../common/1759399811000-ChangeValueTypesForInsights'; import { CreateChatHubTables1760019379982 } from '../common/1760019379982-CreateChatHubTables'; import { UniqueRoleNames1760020838000 } from '../common/1760020838000-UniqueRoleNames'; +import { CreateWorkflowDependencyTable1760314000000 } from '../common/1760314000000-CreateWorkflowDependencyTable'; import type { Migration } from '../migration-types'; export const postgresMigrations: Migration[] = [ @@ -209,4 +210,5 @@ export const postgresMigrations: Migration[] = [ ChangeValueTypesForInsights1759399811000, CreateChatHubTables1760019379982, UniqueRoleNames1760020838000, + CreateWorkflowDependencyTable1760314000000, ]; diff --git a/packages/@n8n/db/src/migrations/sqlite/index.ts b/packages/@n8n/db/src/migrations/sqlite/index.ts index 55c12939e6a..1045cffeb5f 100644 --- a/packages/@n8n/db/src/migrations/sqlite/index.ts +++ b/packages/@n8n/db/src/migrations/sqlite/index.ts @@ -100,6 +100,7 @@ import type { Migration } from '../migration-types'; import { LinkRoleToProjectRelationTable1753953244168 } from './../common/1753953244168-LinkRoleToProjectRelationTable'; import { AddProjectIdToVariableTable1758794506893 } from './1758794506893-AddProjectIdToVariableTable'; import { CreateChatHubTables1760019379982 } from '../common/1760019379982-CreateChatHubTables'; +import { CreateWorkflowDependencyTable1760314000000 } from '../common/1760314000000-CreateWorkflowDependencyTable'; const sqliteMigrations: Migration[] = [ InitialMigration1588102412422, @@ -203,6 +204,7 @@ const sqliteMigrations: Migration[] = [ ChangeValueTypesForInsights1759399811000, CreateChatHubTables1760019379982, UniqueRoleNames1760020838000, + CreateWorkflowDependencyTable1760314000000, ]; export { sqliteMigrations };