From bb13b4aed09013f90d7fd77138b5e2d6780cdf28 Mon Sep 17 00:00:00 2001 From: Weiko Date: Thu, 26 Jun 2025 15:47:58 +0200 Subject: [PATCH] Add fix schema array type command (#12887) Following this fix https://github.com/twentyhq/twenty/pull/12874 we now need to add a command to retroactively fix existing columns --------- Co-authored-by: Paul Rastoin <45004772+prastoin@users.noreply.github.com> Co-authored-by: prastoin --- .../1-1/1-1-fix-schema-array-type.command.ts | 102 ++++++++++++++++++ .../1-1/1-1-upgrade-version-command.module.ts | 15 ++- .../upgrade.command.ts | 5 +- .../workspace-health.module.ts | 2 +- 4 files changed, 120 insertions(+), 4 deletions(-) create mode 100644 packages/twenty-server/src/database/commands/upgrade-version-command/1-1/1-1-fix-schema-array-type.command.ts diff --git a/packages/twenty-server/src/database/commands/upgrade-version-command/1-1/1-1-fix-schema-array-type.command.ts b/packages/twenty-server/src/database/commands/upgrade-version-command/1-1/1-1-fix-schema-array-type.command.ts new file mode 100644 index 000000000..46ff838c4 --- /dev/null +++ b/packages/twenty-server/src/database/commands/upgrade-version-command/1-1/1-1-fix-schema-array-type.command.ts @@ -0,0 +1,102 @@ +import { InjectRepository } from '@nestjs/typeorm'; + +import { Command } from 'nest-commander'; +import { FieldMetadataType } from 'twenty-shared/types'; +import { Repository } from 'typeorm'; + +import { + ActiveOrSuspendedWorkspacesMigrationCommandRunner, + RunOnWorkspaceArgs, +} from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner'; +import { TypeORMService } from 'src/database/typeorm/typeorm.service'; +import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; +import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; +import { computeColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-column-name.util'; +import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; +import { computeObjectTargetTable } from 'src/engine/utils/compute-object-target-table.util'; +import { WorkspaceDataSourceService } from 'src/engine/workspace-datasource/workspace-datasource.service'; +import { DatabaseStructureService } from 'src/engine/workspace-manager/workspace-health/services/database-structure.service'; + +@Command({ + name: 'upgrade:1-1:fix-schema-array-type', + description: 'Fix columns for ARRAY fields to be text[] in the DB schema', +}) +export class FixSchemaArrayTypeCommand extends ActiveOrSuspendedWorkspacesMigrationCommandRunner { + constructor( + @InjectRepository(Workspace, 'core') + protected readonly workspaceRepository: Repository, + protected readonly twentyORMGlobalManager: TwentyORMGlobalManager, + private readonly databaseStructureService: DatabaseStructureService, + private readonly workspaceDataSourceService: WorkspaceDataSourceService, + private readonly typeORMService: TypeORMService, + @InjectRepository(FieldMetadataEntity, 'core') + private readonly fieldMetadataRepository: Repository, + ) { + super(workspaceRepository, twentyORMGlobalManager); + } + + override async runOnWorkspace({ + workspaceId, + options, + }: RunOnWorkspaceArgs): Promise { + this.logger.log(`Fixing ARRAY field columns for workspace ${workspaceId}`); + + const arrayFields: FieldMetadataEntity[] = + await this.fieldMetadataRepository.find({ + where: { + workspaceId, + type: FieldMetadataType.ARRAY, + isCustom: true, + }, + relations: ['object'], + }); + + if (arrayFields.length === 0) { + this.logger.log('No ARRAY fields found for this workspace.'); + + return; + } + + for (const field of arrayFields) { + const object = field.object; + + const tableName = computeObjectTargetTable(object); + const schemaName = + this.workspaceDataSourceService.getSchemaName(workspaceId); + const columns = + await this.databaseStructureService.getWorkspaceTableColumns( + schemaName, + tableName, + ); + const columnName = computeColumnName(field); + const dbColumn = columns.find((col) => col.columnName === columnName); + + if (!dbColumn) { + this.logger.warn( + `Column ${columnName} not found in table ${schemaName}.${tableName}`, + ); + continue; + } + if (dbColumn.dataType === 'text[]' && dbColumn.isArray) { + continue; + } + this.logger.log( + `Altering column ${schemaName}.${tableName}.${columnName} to type text[] (was ${dbColumn.dataType})`, + ); + if (!options.dryRun) { + const queryRunner = this.typeORMService + .getMainDataSource() + .createQueryRunner(); + + await queryRunner.connect(); + try { + await queryRunner.query( + `ALTER TABLE "${schemaName}"."${tableName}" ALTER COLUMN "${columnName}" TYPE text[] USING "${columnName}"::text[];`, + ); + } finally { + await queryRunner.release(); + } + } + } + } +} diff --git a/packages/twenty-server/src/database/commands/upgrade-version-command/1-1/1-1-upgrade-version-command.module.ts b/packages/twenty-server/src/database/commands/upgrade-version-command/1-1/1-1-upgrade-version-command.module.ts index 1afff9a08..df7db703c 100644 --- a/packages/twenty-server/src/database/commands/upgrade-version-command/1-1/1-1-upgrade-version-command.module.ts +++ b/packages/twenty-server/src/database/commands/upgrade-version-command/1-1/1-1-upgrade-version-command.module.ts @@ -1,7 +1,9 @@ import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; +import { FixSchemaArrayTypeCommand } from 'src/database/commands/upgrade-version-command/1-1/1-1-fix-schema-array-type.command'; import { FixUpdateStandardFieldsIsLabelSyncedWithName } from 'src/database/commands/upgrade-version-command/1-1/1-1-fix-update-standard-field-is-label-synced-with-name.command'; +import { TypeORMModule } from 'src/database/typeorm/typeorm.module'; import { AppToken } from 'src/engine/core-modules/app-token/app-token.entity'; import { UserWorkspace } from 'src/engine/core-modules/user-workspace/user-workspace.entity'; import { User } from 'src/engine/core-modules/user/user.entity'; @@ -10,6 +12,7 @@ import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/ import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; import { WorkspaceMetadataVersionModule } from 'src/engine/metadata-modules/workspace-metadata-version/workspace-metadata-version.module'; import { WorkspaceDataSourceModule } from 'src/engine/workspace-datasource/workspace-datasource.module'; +import { WorkspaceHealthModule } from 'src/engine/workspace-manager/workspace-health/workspace-health.module'; import { WorkspaceMigrationRunnerModule } from 'src/engine/workspace-manager/workspace-migration-runner/workspace-migration-runner.module'; @Module({ @@ -28,8 +31,16 @@ import { WorkspaceMigrationRunnerModule } from 'src/engine/workspace-manager/wor WorkspaceDataSourceModule, WorkspaceMigrationRunnerModule, WorkspaceMetadataVersionModule, + WorkspaceHealthModule, + TypeORMModule, + ], + providers: [ + FixUpdateStandardFieldsIsLabelSyncedWithName, + FixSchemaArrayTypeCommand, + ], + exports: [ + FixUpdateStandardFieldsIsLabelSyncedWithName, + FixSchemaArrayTypeCommand, ], - providers: [FixUpdateStandardFieldsIsLabelSyncedWithName], - exports: [FixUpdateStandardFieldsIsLabelSyncedWithName], }) export class V1_1_UpgradeVersionCommandModule {} diff --git a/packages/twenty-server/src/database/commands/upgrade-version-command/upgrade.command.ts b/packages/twenty-server/src/database/commands/upgrade-version-command/upgrade.command.ts index bdda45933..2b5e5ce02 100644 --- a/packages/twenty-server/src/database/commands/upgrade-version-command/upgrade.command.ts +++ b/packages/twenty-server/src/database/commands/upgrade-version-command/upgrade.command.ts @@ -21,6 +21,7 @@ import { FixStandardSelectFieldsPositionCommand } from 'src/database/commands/up import { LowercaseUserAndInvitationEmailsCommand } from 'src/database/commands/upgrade-version-command/0-54/0-54-lowercase-user-and-invitation-emails.command'; import { MigrateDefaultAvatarUrlToUserWorkspaceCommand } from 'src/database/commands/upgrade-version-command/0-54/0-54-migrate-default-avatar-url-to-user-workspace.command'; import { DeduplicateIndexedFieldsCommand } from 'src/database/commands/upgrade-version-command/0-55/0-55-deduplicate-indexed-fields.command'; +import { FixSchemaArrayTypeCommand } from 'src/database/commands/upgrade-version-command/1-1/1-1-fix-schema-array-type.command'; import { FixUpdateStandardFieldsIsLabelSyncedWithName } from 'src/database/commands/upgrade-version-command/1-1/1-1-fix-update-standard-field-is-label-synced-with-name.command'; import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service'; import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; @@ -137,7 +138,8 @@ export class UpgradeCommand extends UpgradeCommandRunner { // 0.55 Commands protected readonly deduplicateIndexedFieldsCommand: DeduplicateIndexedFieldsCommand, - // 1.1 Commands + // 1.1 Commands + protected readonly fixSchemaArrayTypeCommand: FixSchemaArrayTypeCommand, protected readonly fixUpdateStandardFieldsIsLabelSyncedWithNameCommand: FixUpdateStandardFieldsIsLabelSyncedWithName, ) { super( @@ -182,6 +184,7 @@ export class UpgradeCommand extends UpgradeCommandRunner { const commands_110: VersionCommands = { beforeSyncMetadata: [ this.fixUpdateStandardFieldsIsLabelSyncedWithNameCommand, + this.fixSchemaArrayTypeCommand ], afterSyncMetadata: [], }; diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-health/workspace-health.module.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-health/workspace-health.module.ts index 983b45bc4..421cdbfe8 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-health/workspace-health.module.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-health/workspace-health.module.ts @@ -32,6 +32,6 @@ import { WorkspaceFixService } from './services/workspace-fix.service'; FieldMetadataHealthService, WorkspaceFixService, ], - exports: [WorkspaceHealthService], + exports: [WorkspaceHealthService, DatabaseStructureService], }) export class WorkspaceHealthModule {}