diff --git a/packages/twenty-server/src/database/commands/command-runners/workspace-iterator.service.ts b/packages/twenty-server/src/database/commands/command-runners/workspace-iterator.service.ts index ebd92496c5..5f761d9725 100644 --- a/packages/twenty-server/src/database/commands/command-runners/workspace-iterator.service.ts +++ b/packages/twenty-server/src/database/commands/command-runners/workspace-iterator.service.ts @@ -90,6 +90,15 @@ export class WorkspaceIteratorService { ? await this.globalWorkspaceOrmManager.getGlobalWorkspaceDataSource() : undefined; + if (!isDefined(dataSource)) { + this.logger.warn( + `Could not retrieve a workspace data source for workspace ${workspaceId} ` + + `(index ${index + 1}/${workspaceIdsToProcess.length}): ` + + `workspaceRowFound=${isDefined(workspace)}, ` + + `databaseSchema=${JSON.stringify(workspace?.databaseSchema ?? null)}`, + ); + } + await callback({ workspaceId, dataSource, diff --git a/packages/twenty-server/src/database/commands/upgrade-version-command/2-21/2-21-instance-command-slow-1783934147089-backfill-workspace-database-schema.ts b/packages/twenty-server/src/database/commands/upgrade-version-command/2-21/2-21-instance-command-slow-1783934147089-backfill-workspace-database-schema.ts new file mode 100644 index 0000000000..9ae3ccab99 --- /dev/null +++ b/packages/twenty-server/src/database/commands/upgrade-version-command/2-21/2-21-instance-command-slow-1783934147089-backfill-workspace-database-schema.ts @@ -0,0 +1,55 @@ +import { DataSource, QueryRunner } from 'typeorm'; + +import { RegisteredInstanceCommand } from 'src/engine/core-modules/upgrade/decorators/registered-instance-command.decorator'; +import { SlowInstanceCommand } from 'src/engine/core-modules/upgrade/interfaces/slow-instance-command.interface'; +import { getWorkspaceSchemaName } from 'src/engine/workspace-datasource/utils/get-workspace-schema-name.util'; + +@RegisteredInstanceCommand('2.21.0', 1783934147089, { type: 'slow' }) +export class BackfillWorkspaceDatabaseSchemaSlowInstanceCommand + implements SlowInstanceCommand +{ + // Schema names are deterministic from the workspace id, so we backfill them + // unconditionally — even if the schema doesn't exist yet in Postgres — which + // lets up() add the check constraint fully validated. + async runDataMigration(dataSource: DataSource): Promise { + const workspacesWithoutSchema: { id: string }[] = await dataSource.query( + `SELECT id FROM "core"."workspace" + WHERE ("databaseSchema" IS NULL OR "databaseSchema" = '') + AND "activationStatus" NOT IN ('PENDING_CREATION', 'ONGOING_CREATION')`, + ); + + if (workspacesWithoutSchema.length === 0) { + return; + } + + await dataSource.query( + `UPDATE "core"."workspace" AS w + SET "databaseSchema" = data.schema_name + FROM ( + SELECT UNNEST($1::uuid[]) AS id, UNNEST($2::text[]) AS schema_name + ) AS data + WHERE w.id = data.id`, + [ + workspacesWithoutSchema.map((workspace) => workspace.id), + workspacesWithoutSchema.map((workspace) => + getWorkspaceSchemaName(workspace.id), + ), + ], + ); + } + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `ALTER TABLE "core"."workspace" DROP CONSTRAINT IF EXISTS "workspace_requires_database_schema"`, + ); + await queryRunner.query( + `ALTER TABLE "core"."workspace" ADD CONSTRAINT "workspace_requires_database_schema" CHECK ("activationStatus" IN ('PENDING_CREATION', 'ONGOING_CREATION') OR ("databaseSchema" IS NOT NULL AND "databaseSchema" <> ''))`, + ); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `ALTER TABLE "core"."workspace" DROP CONSTRAINT IF EXISTS "workspace_requires_database_schema"`, + ); + } +} 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 b17594410d..c219c8da66 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 @@ -108,6 +108,7 @@ import { CreateWorkflowCoreTableFastInstanceCommand } from './2-20/2-20-instance import { AddGalleryImagesToApplicationRegistrationFastInstanceCommand } from './2-20/2-20-instance-command-fast-1783615890055-add-gallery-images-to-application-registration'; import { BackfillGalleryImagesOnApplicationRegistrationSlowInstanceCommand } from './2-20/2-20-instance-command-slow-1783615890056-backfill-gallery-images-on-application-registration'; import { AddWorkflowVersionSyncableColumnsFastInstanceCommand } from './2-20/2-20-instance-command-fast-1783603454480-add-workflow-version-syncable-columns'; +import { BackfillWorkspaceDatabaseSchemaSlowInstanceCommand } from './2-21/2-21-instance-command-slow-1783934147089-backfill-workspace-database-schema'; export const INSTANCE_COMMANDS = [ AddViewFieldGroupIdIndexOnViewFieldFastInstanceCommand, @@ -218,4 +219,5 @@ export const INSTANCE_COMMANDS = [ AddGalleryImagesToApplicationRegistrationFastInstanceCommand, BackfillGalleryImagesOnApplicationRegistrationSlowInstanceCommand, AddWorkflowVersionSyncableColumnsFastInstanceCommand, + BackfillWorkspaceDatabaseSchemaSlowInstanceCommand, ]; diff --git a/packages/twenty-server/src/engine/core-modules/workspace/workspace.entity.ts b/packages/twenty-server/src/engine/core-modules/workspace/workspace.entity.ts index 8c2a42bd59..141e09abb8 100644 --- a/packages/twenty-server/src/engine/core-modules/workspace/workspace.entity.ts +++ b/packages/twenty-server/src/engine/core-modules/workspace/workspace.entity.ts @@ -67,6 +67,10 @@ registerEnumType(WorkspaceDiscoverability, { 'onboarded_workspace_requires_default_role', `"activationStatus" IN ('PENDING_CREATION', 'ONGOING_CREATION') OR "defaultRoleId" IS NOT NULL`, ) +@Check( + 'workspace_requires_database_schema', + `"activationStatus" IN ('PENDING_CREATION', 'ONGOING_CREATION') OR ("databaseSchema" IS NOT NULL AND "databaseSchema" <> '')`, +) @Entity({ name: 'workspace', schema: 'core' }) @ObjectType('Workspace') export class WorkspaceEntity { diff --git a/packages/twenty-server/src/engine/workspace-datasource/workspace-datasource.service.ts b/packages/twenty-server/src/engine/workspace-datasource/workspace-datasource.service.ts index 3cd4cafd84..7411b4bb78 100644 --- a/packages/twenty-server/src/engine/workspace-datasource/workspace-datasource.service.ts +++ b/packages/twenty-server/src/engine/workspace-datasource/workspace-datasource.service.ts @@ -37,7 +37,7 @@ export class WorkspaceDataSourceService { } } - public async checkSchemaExists(workspaceId: string) { + public async checkSchemaExists(workspaceId: string): Promise { const workspace = await this.workspaceRepository.findOne({ select: ['databaseSchema'], where: { id: workspaceId },