diff --git a/packages/twenty-server/src/database/commands/upgrade-version-command/2-24/2-24-instance-command-fast-1784897347051-repair-key-value-pair-application-id.ts b/packages/twenty-server/src/database/commands/upgrade-version-command/2-24/2-24-instance-command-fast-1784897347051-repair-key-value-pair-application-id.ts new file mode 100644 index 0000000000..94e5d1cd6c --- /dev/null +++ b/packages/twenty-server/src/database/commands/upgrade-version-command/2-24/2-24-instance-command-fast-1784897347051-repair-key-value-pair-application-id.ts @@ -0,0 +1,57 @@ +import { type QueryRunner } from 'typeorm'; + +import { RegisteredInstanceCommand } from 'src/engine/core-modules/upgrade/decorators/registered-instance-command.decorator'; +import { type FastInstanceCommand } from 'src/engine/core-modules/upgrade/interfaces/fast-instance-command.interface'; + +// Repairs instances that upgraded from 2.23.x, where +// AddApplicationIdToKeyValuePairFastInstanceCommand was registered under the +// already-released 2.23.0 segment and got skipped by the forward-only upgrade +// cursor, leaving core.keyValuePair without applicationId. The DDL mirrors that +// command and is fully idempotent, so it is a no-op on healthy instances. +@RegisteredInstanceCommand('2.24.0', 1784897347051) +export class RepairKeyValuePairApplicationIdFastInstanceCommand + implements FastInstanceCommand +{ + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `ALTER TYPE "core"."keyValuePair_type_enum" ADD VALUE IF NOT EXISTS 'APPLICATION_VARIABLE'`, + ); + await queryRunner.query( + 'ALTER TABLE "core"."keyValuePair" ADD COLUMN IF NOT EXISTS "applicationId" uuid', + ); + await queryRunner.query( + 'ALTER TABLE "core"."keyValuePair" DROP CONSTRAINT IF EXISTS "FK_e31d245e30cd82307e5416450fc"', + ); + await queryRunner.query( + 'ALTER TABLE "core"."keyValuePair" ADD CONSTRAINT "FK_e31d245e30cd82307e5416450fc" FOREIGN KEY ("applicationId") REFERENCES "core"."application"("id") ON DELETE CASCADE ON UPDATE NO ACTION', + ); + await queryRunner.query( + 'CREATE INDEX IF NOT EXISTS "IDX_KEY_VALUE_PAIR_APPLICATION_ID" ON "core"."keyValuePair" ("applicationId")', + ); + + await queryRunner.query( + 'DROP INDEX IF EXISTS "core"."IDX_KEY_VALUE_PAIR_KEY_WORKSPACE_ID_NULL_USER_ID_UNIQUE"', + ); + await queryRunner.query( + 'CREATE UNIQUE INDEX "IDX_KEY_VALUE_PAIR_KEY_WORKSPACE_ID_NULL_USER_ID_UNIQUE" ON "core"."keyValuePair" ("key", "workspaceId") WHERE "userId" IS NULL AND "applicationId" IS NULL', + ); + await queryRunner.query( + 'DROP INDEX IF EXISTS "core"."IDX_KEY_VALUE_PAIR_KEY_NULL_USER_ID_NULL_WORKSPACE_ID_UNIQUE"', + ); + await queryRunner.query( + 'CREATE UNIQUE INDEX "IDX_KEY_VALUE_PAIR_KEY_NULL_USER_ID_NULL_WORKSPACE_ID_UNIQUE" ON "core"."keyValuePair" ("key") WHERE "userId" IS NULL AND "workspaceId" IS NULL AND "applicationId" IS NULL', + ); + + await queryRunner.query( + 'CREATE UNIQUE INDEX IF NOT EXISTS "IDX_KEY_VALUE_PAIR_KEY_APPLICATION_ID_WORKSPACE_UNIQUE" ON "core"."keyValuePair" ("key", "applicationId") WHERE "applicationId" IS NOT NULL AND "workspaceId" IS NOT NULL', + ); + await queryRunner.query( + 'CREATE UNIQUE INDEX IF NOT EXISTS "IDX_KEY_VALUE_PAIR_KEY_APPLICATION_ID_GLOBAL_UNIQUE" ON "core"."keyValuePair" ("key", "applicationId") WHERE "applicationId" IS NOT NULL AND "workspaceId" IS NULL', + ); + } + + // No-op: the applicationId column lifecycle is owned by the 2.23.0 + // introduction command. This command only repairs instances that skipped it, + // so rolling it back must not drop the column or its constraints. + public async down(): Promise {} +} diff --git a/packages/twenty-server/src/database/commands/upgrade-version-command/instance-commands.constant.ts b/packages/twenty-server/src/database/commands/upgrade-version-command/instance-commands.constant.ts index ebcd291fd0..7ab2c46eb0 100644 --- a/packages/twenty-server/src/database/commands/upgrade-version-command/instance-commands.constant.ts +++ b/packages/twenty-server/src/database/commands/upgrade-version-command/instance-commands.constant.ts @@ -123,6 +123,7 @@ import { UnlistUnclaimedNpmApplicationRegistrationsSlowInstanceCommand } from '. import { AddStatusesToBillingSubscriptionIndexSlowInstanceCommand } from './2-23/2-23-instance-command-slow-1784650048045-add-statuses-to-billing-subscription-index'; import { AddOnConnectLogicFunctionToConnectionProviderFastInstanceCommand } from './2-24/2-24-instance-command-fast-1784712843602-add-on-connect-logic-function-to-connection-provider'; import { AddAgentForeignKeyToRoleTargetFastInstanceCommand } from 'src/database/commands/upgrade-version-command/2-24/2-24-instance-command-fast-1784820332810-add-agent-foreign-key-to-role-target'; +import { RepairKeyValuePairApplicationIdFastInstanceCommand } from './2-24/2-24-instance-command-fast-1784897347051-repair-key-value-pair-application-id'; export const INSTANCE_COMMANDS = [ AddViewFieldGroupIdIndexOnViewFieldFastInstanceCommand, @@ -248,4 +249,5 @@ export const INSTANCE_COMMANDS = [ AddStatusesToBillingSubscriptionIndexSlowInstanceCommand, AddOnConnectLogicFunctionToConnectionProviderFastInstanceCommand, AddAgentForeignKeyToRoleTargetFastInstanceCommand, + RepairKeyValuePairApplicationIdFastInstanceCommand, ]; diff --git a/packages/twenty-server/src/engine/core-modules/key-value-pair/key-value-pair.entity.ts b/packages/twenty-server/src/engine/core-modules/key-value-pair/key-value-pair.entity.ts index fde51565c5..9eb7940190 100644 --- a/packages/twenty-server/src/engine/core-modules/key-value-pair/key-value-pair.entity.ts +++ b/packages/twenty-server/src/engine/core-modules/key-value-pair/key-value-pair.entity.ts @@ -108,7 +108,7 @@ export class KeyValuePairEntity { @Column({ nullable: true, type: 'uuid' }) @WasIntroducedInUpgrade({ upgradeCommandName: - '2.23.0_AddApplicationIdToKeyValuePairFastInstanceCommand_1784659343818', + '2.24.0_RepairKeyValuePairApplicationIdFastInstanceCommand_1784897347051', }) applicationId: string | null;