From 0dabdfe77d0813a9eb9f7011b66483ca7c1b4b81 Mon Sep 17 00:00:00 2001 From: autologie Date: Tue, 16 Dec 2025 12:01:03 +0100 Subject: [PATCH 1/5] chore: set foreign key constraint on agentId --- .../1765801023649-AddAgentIdForeignKeys.ts | 50 +++++++++++++++++++ .../@n8n/db/src/migrations/mysqldb/index.ts | 2 + .../db/src/migrations/postgresdb/index.ts | 2 + .../@n8n/db/src/migrations/sqlite/index.ts | 2 + 4 files changed, 56 insertions(+) create mode 100644 packages/@n8n/db/src/migrations/common/1765801023649-AddAgentIdForeignKeys.ts diff --git a/packages/@n8n/db/src/migrations/common/1765801023649-AddAgentIdForeignKeys.ts b/packages/@n8n/db/src/migrations/common/1765801023649-AddAgentIdForeignKeys.ts new file mode 100644 index 0000000000000..53bd6645f0eef --- /dev/null +++ b/packages/@n8n/db/src/migrations/common/1765801023649-AddAgentIdForeignKeys.ts @@ -0,0 +1,50 @@ +import type { MigrationContext, ReversibleMigration } from '../migration-types'; + +const table = { + agents: 'chat_hub_agents', + sessions: 'chat_hub_sessions', + messages: 'chat_hub_messages', +} as const; + +export class AddAgentIdForeignKeys1765801023649 implements ReversibleMigration { + async up({ schemaBuilder: { addForeignKey }, runQuery, escape }: MigrationContext) { + // Clean up orphaned agentId references before adding foreign key constraint + await runQuery( + `UPDATE ${escape.tableName(table.sessions)} SET "agentId" = NULL WHERE "agentId" IS NOT NULL AND "agentId" NOT IN (SELECT id FROM ${escape.tableName(table.agents)})`, + ); + await runQuery( + `UPDATE ${escape.tableName(table.messages)} SET "agentId" = NULL WHERE "agentId" IS NOT NULL AND "agentId" NOT IN (SELECT id FROM ${escape.tableName(table.agents)})`, + ); + + // Add foreign key constraint for agentId in sessions table + await addForeignKey( + table.sessions, + 'agentId', + [table.agents, 'id'], + 'FK_chat_hub_sessions_agentId', + 'SET NULL', + ); + await addForeignKey( + table.messages, + 'agentId', + [table.agents, 'id'], + 'FK_chat_hub_messages_agentId', + 'SET NULL', + ); + } + + async down({ schemaBuilder: { dropForeignKey } }: MigrationContext) { + await dropForeignKey( + table.messages, + 'agentId', + [table.agents, 'id'], + 'FK_chat_hub_messages_agentId', + ); + await dropForeignKey( + table.sessions, + 'agentId', + [table.agents, 'id'], + 'FK_chat_hub_sessions_agentId', + ); + } +} diff --git a/packages/@n8n/db/src/migrations/mysqldb/index.ts b/packages/@n8n/db/src/migrations/mysqldb/index.ts index 92ce829841a47..7388d868fdb9f 100644 --- a/packages/@n8n/db/src/migrations/mysqldb/index.ts +++ b/packages/@n8n/db/src/migrations/mysqldb/index.ts @@ -126,6 +126,7 @@ import { AddDynamicCredentialEntryTable1764689388394 } from '../common/176468938 import { BackfillMissingWorkflowHistoryRecords1765448186933 } from '../common/1765448186933-BackfillMissingWorkflowHistoryRecords'; import { AddResolvableFieldsToCredentials1765459448000 } from '../common/1765459448000-AddResolvableFieldsToCredentials'; import { AddIconToAgentTable1765788427674 } from '../common/1765788427674-AddIconToAgentTable'; +import { AddAgentIdForeignKeys1765801023649 } from '../common/1765801023649-AddAgentIdForeignKeys'; import type { Migration } from '../migration-types'; export const mysqlMigrations: Migration[] = [ @@ -257,4 +258,5 @@ export const mysqlMigrations: Migration[] = [ BackfillMissingWorkflowHistoryRecords1765448186933, AddResolvableFieldsToCredentials1765459448000, AddIconToAgentTable1765788427674, + AddAgentIdForeignKeys1765801023649, ]; diff --git a/packages/@n8n/db/src/migrations/postgresdb/index.ts b/packages/@n8n/db/src/migrations/postgresdb/index.ts index ebca1b497f788..ed2732a427730 100644 --- a/packages/@n8n/db/src/migrations/postgresdb/index.ts +++ b/packages/@n8n/db/src/migrations/postgresdb/index.ts @@ -127,6 +127,7 @@ import { AddDynamicCredentialEntryTable1764689388394 } from '../common/176468938 import { BackfillMissingWorkflowHistoryRecords1765448186933 } from '../common/1765448186933-BackfillMissingWorkflowHistoryRecords'; import { AddResolvableFieldsToCredentials1765459448000 } from '../common/1765459448000-AddResolvableFieldsToCredentials'; import { AddIconToAgentTable1765788427674 } from '../common/1765788427674-AddIconToAgentTable'; +import { AddAgentIdForeignKeys1765801023649 } from '../common/1765801023649-AddAgentIdForeignKeys'; import type { Migration } from '../migration-types'; export const postgresMigrations: Migration[] = [ @@ -259,4 +260,5 @@ export const postgresMigrations: Migration[] = [ AddResolvableFieldsToCredentials1765459448000, AddIconToAgentTable1765788427674, ConvertAgentIdToUuid1765804780000, + AddAgentIdForeignKeys1765801023649, ]; diff --git a/packages/@n8n/db/src/migrations/sqlite/index.ts b/packages/@n8n/db/src/migrations/sqlite/index.ts index 226599dda03f0..7cf33c209c852 100644 --- a/packages/@n8n/db/src/migrations/sqlite/index.ts +++ b/packages/@n8n/db/src/migrations/sqlite/index.ts @@ -122,6 +122,7 @@ import { CreateDynamicCredentialResolverTable1764682447000 } from '../common/176 import { AddDynamicCredentialEntryTable1764689388394 } from '../common/1764689388394-AddDynamicCredentialEntryTable'; import { BackfillMissingWorkflowHistoryRecords1765448186933 } from '../common/1765448186933-BackfillMissingWorkflowHistoryRecords'; import { AddIconToAgentTable1765788427674 } from '../common/1765788427674-AddIconToAgentTable'; +import { AddAgentIdForeignKeys1765801023649 } from '../common/1765801023649-AddAgentIdForeignKeys'; import type { Migration } from '../migration-types'; const sqliteMigrations: Migration[] = [ @@ -249,6 +250,7 @@ const sqliteMigrations: Migration[] = [ BackfillMissingWorkflowHistoryRecords1765448186933, AddResolvableFieldsToCredentials1764689448000, AddIconToAgentTable1765788427674, + AddAgentIdForeignKeys1765801023649, ]; export { sqliteMigrations }; From 80e8aadb7fd9277705b16ac4517ac1427bd32d22 Mon Sep 17 00:00:00 2001 From: autologie Date: Tue, 16 Dec 2025 13:03:00 +0100 Subject: [PATCH 2/5] fix: don't cause cascading delete when running down migration --- .../migrations/common/1765801023649-AddAgentIdForeignKeys.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/@n8n/db/src/migrations/common/1765801023649-AddAgentIdForeignKeys.ts b/packages/@n8n/db/src/migrations/common/1765801023649-AddAgentIdForeignKeys.ts index 53bd6645f0eef..497288dbfe21e 100644 --- a/packages/@n8n/db/src/migrations/common/1765801023649-AddAgentIdForeignKeys.ts +++ b/packages/@n8n/db/src/migrations/common/1765801023649-AddAgentIdForeignKeys.ts @@ -7,6 +7,8 @@ const table = { } as const; export class AddAgentIdForeignKeys1765801023649 implements ReversibleMigration { + transaction = false as const; + async up({ schemaBuilder: { addForeignKey }, runQuery, escape }: MigrationContext) { // Clean up orphaned agentId references before adding foreign key constraint await runQuery( From d299d2435426688e428b79c2a8a1c6d86f6057c3 Mon Sep 17 00:00:00 2001 From: autologie Date: Tue, 16 Dec 2025 13:07:45 +0100 Subject: [PATCH 3/5] update migration timestamp --- ...dForeignKeys.ts => 1765886667897-AddAgentIdForeignKeys.ts} | 2 +- packages/@n8n/db/src/migrations/mysqldb/index.ts | 4 ++-- packages/@n8n/db/src/migrations/postgresdb/index.ts | 4 ++-- packages/@n8n/db/src/migrations/sqlite/index.ts | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) rename packages/@n8n/db/src/migrations/common/{1765801023649-AddAgentIdForeignKeys.ts => 1765886667897-AddAgentIdForeignKeys.ts} (95%) diff --git a/packages/@n8n/db/src/migrations/common/1765801023649-AddAgentIdForeignKeys.ts b/packages/@n8n/db/src/migrations/common/1765886667897-AddAgentIdForeignKeys.ts similarity index 95% rename from packages/@n8n/db/src/migrations/common/1765801023649-AddAgentIdForeignKeys.ts rename to packages/@n8n/db/src/migrations/common/1765886667897-AddAgentIdForeignKeys.ts index 497288dbfe21e..30d022fdb6409 100644 --- a/packages/@n8n/db/src/migrations/common/1765801023649-AddAgentIdForeignKeys.ts +++ b/packages/@n8n/db/src/migrations/common/1765886667897-AddAgentIdForeignKeys.ts @@ -6,7 +6,7 @@ const table = { messages: 'chat_hub_messages', } as const; -export class AddAgentIdForeignKeys1765801023649 implements ReversibleMigration { +export class AddAgentIdForeignKeys1765886667897 implements ReversibleMigration { transaction = false as const; async up({ schemaBuilder: { addForeignKey }, runQuery, escape }: MigrationContext) { diff --git a/packages/@n8n/db/src/migrations/mysqldb/index.ts b/packages/@n8n/db/src/migrations/mysqldb/index.ts index 7388d868fdb9f..a4892c7a4c613 100644 --- a/packages/@n8n/db/src/migrations/mysqldb/index.ts +++ b/packages/@n8n/db/src/migrations/mysqldb/index.ts @@ -126,7 +126,7 @@ import { AddDynamicCredentialEntryTable1764689388394 } from '../common/176468938 import { BackfillMissingWorkflowHistoryRecords1765448186933 } from '../common/1765448186933-BackfillMissingWorkflowHistoryRecords'; import { AddResolvableFieldsToCredentials1765459448000 } from '../common/1765459448000-AddResolvableFieldsToCredentials'; import { AddIconToAgentTable1765788427674 } from '../common/1765788427674-AddIconToAgentTable'; -import { AddAgentIdForeignKeys1765801023649 } from '../common/1765801023649-AddAgentIdForeignKeys'; +import { AddAgentIdForeignKeys1765886667897 } from '../common/1765886667897-AddAgentIdForeignKeys'; import type { Migration } from '../migration-types'; export const mysqlMigrations: Migration[] = [ @@ -258,5 +258,5 @@ export const mysqlMigrations: Migration[] = [ BackfillMissingWorkflowHistoryRecords1765448186933, AddResolvableFieldsToCredentials1765459448000, AddIconToAgentTable1765788427674, - AddAgentIdForeignKeys1765801023649, + AddAgentIdForeignKeys1765886667897, ]; diff --git a/packages/@n8n/db/src/migrations/postgresdb/index.ts b/packages/@n8n/db/src/migrations/postgresdb/index.ts index ed2732a427730..8d811e1cca7cf 100644 --- a/packages/@n8n/db/src/migrations/postgresdb/index.ts +++ b/packages/@n8n/db/src/migrations/postgresdb/index.ts @@ -127,7 +127,7 @@ import { AddDynamicCredentialEntryTable1764689388394 } from '../common/176468938 import { BackfillMissingWorkflowHistoryRecords1765448186933 } from '../common/1765448186933-BackfillMissingWorkflowHistoryRecords'; import { AddResolvableFieldsToCredentials1765459448000 } from '../common/1765459448000-AddResolvableFieldsToCredentials'; import { AddIconToAgentTable1765788427674 } from '../common/1765788427674-AddIconToAgentTable'; -import { AddAgentIdForeignKeys1765801023649 } from '../common/1765801023649-AddAgentIdForeignKeys'; +import { AddAgentIdForeignKeys1765886667897 } from '../common/1765886667897-AddAgentIdForeignKeys'; import type { Migration } from '../migration-types'; export const postgresMigrations: Migration[] = [ @@ -260,5 +260,5 @@ export const postgresMigrations: Migration[] = [ AddResolvableFieldsToCredentials1765459448000, AddIconToAgentTable1765788427674, ConvertAgentIdToUuid1765804780000, - AddAgentIdForeignKeys1765801023649, + AddAgentIdForeignKeys1765886667897, ]; diff --git a/packages/@n8n/db/src/migrations/sqlite/index.ts b/packages/@n8n/db/src/migrations/sqlite/index.ts index 7cf33c209c852..38049a5f33ba2 100644 --- a/packages/@n8n/db/src/migrations/sqlite/index.ts +++ b/packages/@n8n/db/src/migrations/sqlite/index.ts @@ -122,7 +122,7 @@ import { CreateDynamicCredentialResolverTable1764682447000 } from '../common/176 import { AddDynamicCredentialEntryTable1764689388394 } from '../common/1764689388394-AddDynamicCredentialEntryTable'; import { BackfillMissingWorkflowHistoryRecords1765448186933 } from '../common/1765448186933-BackfillMissingWorkflowHistoryRecords'; import { AddIconToAgentTable1765788427674 } from '../common/1765788427674-AddIconToAgentTable'; -import { AddAgentIdForeignKeys1765801023649 } from '../common/1765801023649-AddAgentIdForeignKeys'; +import { AddAgentIdForeignKeys1765886667897 } from '../common/1765886667897-AddAgentIdForeignKeys'; import type { Migration } from '../migration-types'; const sqliteMigrations: Migration[] = [ @@ -250,7 +250,7 @@ const sqliteMigrations: Migration[] = [ BackfillMissingWorkflowHistoryRecords1765448186933, AddResolvableFieldsToCredentials1764689448000, AddIconToAgentTable1765788427674, - AddAgentIdForeignKeys1765801023649, + AddAgentIdForeignKeys1765886667897, ]; export { sqliteMigrations }; From f45faaad80882deee8477a4abaee7eabd945d671 Mon Sep 17 00:00:00 2001 From: autologie Date: Tue, 16 Dec 2025 13:25:41 +0100 Subject: [PATCH 4/5] fix: set transaction = false for SQLite only --- .../migrations/common/1765886667897-AddAgentIdForeignKeys.ts | 2 -- .../migrations/sqlite/1765886667897-AddAgentIdForeignKeys.ts | 5 +++++ packages/@n8n/db/src/migrations/sqlite/index.ts | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) create mode 100644 packages/@n8n/db/src/migrations/sqlite/1765886667897-AddAgentIdForeignKeys.ts diff --git a/packages/@n8n/db/src/migrations/common/1765886667897-AddAgentIdForeignKeys.ts b/packages/@n8n/db/src/migrations/common/1765886667897-AddAgentIdForeignKeys.ts index 30d022fdb6409..05ba7b00cc202 100644 --- a/packages/@n8n/db/src/migrations/common/1765886667897-AddAgentIdForeignKeys.ts +++ b/packages/@n8n/db/src/migrations/common/1765886667897-AddAgentIdForeignKeys.ts @@ -7,8 +7,6 @@ const table = { } as const; export class AddAgentIdForeignKeys1765886667897 implements ReversibleMigration { - transaction = false as const; - async up({ schemaBuilder: { addForeignKey }, runQuery, escape }: MigrationContext) { // Clean up orphaned agentId references before adding foreign key constraint await runQuery( diff --git a/packages/@n8n/db/src/migrations/sqlite/1765886667897-AddAgentIdForeignKeys.ts b/packages/@n8n/db/src/migrations/sqlite/1765886667897-AddAgentIdForeignKeys.ts new file mode 100644 index 0000000000000..d0dd16ca94ca0 --- /dev/null +++ b/packages/@n8n/db/src/migrations/sqlite/1765886667897-AddAgentIdForeignKeys.ts @@ -0,0 +1,5 @@ +import { AddAgentIdForeignKeys1765886667897 as BaseMigration } from '../common/1765886667897-AddAgentIdForeignKeys'; + +export class AddAgentIdForeignKeys1765886667897 extends BaseMigration { + transaction = false as const; +} diff --git a/packages/@n8n/db/src/migrations/sqlite/index.ts b/packages/@n8n/db/src/migrations/sqlite/index.ts index 38049a5f33ba2..2e4a6383c2b25 100644 --- a/packages/@n8n/db/src/migrations/sqlite/index.ts +++ b/packages/@n8n/db/src/migrations/sqlite/index.ts @@ -48,6 +48,7 @@ import { AddWorkflowVersionColumn1761047826451 } from './1761047826451-AddWorkfl import { ChangeDependencyInfoToJson1761655473000 } from './1761655473000-ChangeDependencyInfoToJson'; import { AddCreatorIdToProjectTable1764276827837 } from './1764276827837-AddCreatorIdToProjectTable'; import { AddResolvableFieldsToCredentials1764689448000 } from './1764689448000-AddResolvableFieldsToCredentials'; +import { AddAgentIdForeignKeys1765886667897 } from './1765886667897-AddAgentIdForeignKeys'; import { UniqueWorkflowNames1620821879465 } from '../common/1620821879465-UniqueWorkflowNames'; import { UpdateWorkflowCredentials1630330987096 } from '../common/1630330987096-UpdateWorkflowCredentials'; import { AddNodeIds1658930531669 } from '../common/1658930531669-AddNodeIds'; @@ -122,7 +123,6 @@ import { CreateDynamicCredentialResolverTable1764682447000 } from '../common/176 import { AddDynamicCredentialEntryTable1764689388394 } from '../common/1764689388394-AddDynamicCredentialEntryTable'; import { BackfillMissingWorkflowHistoryRecords1765448186933 } from '../common/1765448186933-BackfillMissingWorkflowHistoryRecords'; import { AddIconToAgentTable1765788427674 } from '../common/1765788427674-AddIconToAgentTable'; -import { AddAgentIdForeignKeys1765886667897 } from '../common/1765886667897-AddAgentIdForeignKeys'; import type { Migration } from '../migration-types'; const sqliteMigrations: Migration[] = [ From 43c5d106de2994078af2083d1cbf63e929b7d991 Mon Sep 17 00:00:00 2001 From: autologie Date: Tue, 16 Dec 2025 14:01:50 +0100 Subject: [PATCH 5/5] escape column name --- .../common/1765886667897-AddAgentIdForeignKeys.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/@n8n/db/src/migrations/common/1765886667897-AddAgentIdForeignKeys.ts b/packages/@n8n/db/src/migrations/common/1765886667897-AddAgentIdForeignKeys.ts index 05ba7b00cc202..77a4483de71c0 100644 --- a/packages/@n8n/db/src/migrations/common/1765886667897-AddAgentIdForeignKeys.ts +++ b/packages/@n8n/db/src/migrations/common/1765886667897-AddAgentIdForeignKeys.ts @@ -8,12 +8,14 @@ const table = { export class AddAgentIdForeignKeys1765886667897 implements ReversibleMigration { async up({ schemaBuilder: { addForeignKey }, runQuery, escape }: MigrationContext) { + const escapedAgentIdColumn = escape.columnName('agentId'); + // Clean up orphaned agentId references before adding foreign key constraint await runQuery( - `UPDATE ${escape.tableName(table.sessions)} SET "agentId" = NULL WHERE "agentId" IS NOT NULL AND "agentId" NOT IN (SELECT id FROM ${escape.tableName(table.agents)})`, + `UPDATE ${escape.tableName(table.sessions)} SET ${escapedAgentIdColumn} = NULL WHERE ${escapedAgentIdColumn} IS NOT NULL AND ${escapedAgentIdColumn} NOT IN (SELECT id FROM ${escape.tableName(table.agents)})`, ); await runQuery( - `UPDATE ${escape.tableName(table.messages)} SET "agentId" = NULL WHERE "agentId" IS NOT NULL AND "agentId" NOT IN (SELECT id FROM ${escape.tableName(table.agents)})`, + `UPDATE ${escape.tableName(table.messages)} SET ${escapedAgentIdColumn} = NULL WHERE ${escapedAgentIdColumn} IS NOT NULL AND ${escapedAgentIdColumn} NOT IN (SELECT id FROM ${escape.tableName(table.agents)})`, ); // Add foreign key constraint for agentId in sessions table