diff --git a/packages/twenty-server/src/database/commands/upgrade-version-command/2-18/2-18-upgrade-version-command.module.ts b/packages/twenty-server/src/database/commands/upgrade-version-command/2-18/2-18-upgrade-version-command.module.ts index 515548774f..a184edc960 100644 --- a/packages/twenty-server/src/database/commands/upgrade-version-command/2-18/2-18-upgrade-version-command.module.ts +++ b/packages/twenty-server/src/database/commands/upgrade-version-command/2-18/2-18-upgrade-version-command.module.ts @@ -3,10 +3,12 @@ import { Module } from '@nestjs/common'; import { WorkspaceIteratorModule } from 'src/database/commands/command-runners/workspace-iterator.module'; import { AddMessageIsDraftFieldCommand } from 'src/database/commands/upgrade-version-command/2-18/2-18-workspace-command-1810000005000-add-message-is-draft-field.command'; import { NormalizeLegacyIndexNamesCommand } from 'src/database/commands/upgrade-version-command/2-18/2-18-workspace-command-1799200000000-normalize-legacy-index-names.command'; +import { RecomputeSearchVectorsCommand } from 'src/database/commands/upgrade-version-command/2-18/2-18-workspace-command-1799200001000-recompute-search-vectors.command'; import { ApplicationModule } from 'src/engine/core-modules/application/application.module'; import { WorkspaceSchemaManagerModule } from 'src/engine/twenty-orm/workspace-schema-manager/workspace-schema-manager.module'; import { WorkspaceCacheModule } from 'src/engine/workspace-cache/workspace-cache.module'; import { WorkspaceMigrationModule } from 'src/engine/workspace-manager/workspace-migration/workspace-migration.module'; +import { WorkspaceMigrationRunnerModule } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/workspace-migration-runner.module'; @Module({ imports: [ @@ -15,7 +17,12 @@ import { WorkspaceMigrationModule } from 'src/engine/workspace-manager/workspace WorkspaceIteratorModule, WorkspaceMigrationModule, WorkspaceSchemaManagerModule, + WorkspaceMigrationRunnerModule, + ], + providers: [ + AddMessageIsDraftFieldCommand, + NormalizeLegacyIndexNamesCommand, + RecomputeSearchVectorsCommand, ], - providers: [AddMessageIsDraftFieldCommand, NormalizeLegacyIndexNamesCommand], }) export class V2_18_UpgradeVersionCommandModule {} diff --git a/packages/twenty-server/src/database/commands/upgrade-version-command/2-18/2-18-workspace-command-1799200001000-recompute-search-vectors.command.ts b/packages/twenty-server/src/database/commands/upgrade-version-command/2-18/2-18-workspace-command-1799200001000-recompute-search-vectors.command.ts new file mode 100644 index 0000000000..3099400d74 --- /dev/null +++ b/packages/twenty-server/src/database/commands/upgrade-version-command/2-18/2-18-workspace-command-1799200001000-recompute-search-vectors.command.ts @@ -0,0 +1,90 @@ +import { Command } from 'nest-commander'; +import { FieldMetadataType } from 'twenty-shared/types'; +import { isDefined } from 'twenty-shared/utils'; + +import { ActiveOrSuspendedWorkspaceCommandRunner } from 'src/database/commands/command-runners/active-or-suspended-workspace.command-runner'; +import { WorkspaceIteratorService } from 'src/database/commands/command-runners/workspace-iterator.service'; +import { type RunOnWorkspaceArgs } from 'src/database/commands/command-runners/workspace.command-runner'; +import { RegisteredWorkspaceCommand } from 'src/engine/core-modules/upgrade/decorators/registered-workspace-command.decorator'; +import { TWENTY_STANDARD_APPLICATION } from 'src/engine/workspace-manager/twenty-standard-application/constants/twenty-standard-applications'; +import { WorkspaceCacheService } from 'src/engine/workspace-cache/services/workspace-cache.service'; +import { type UniversalUpdateFieldAction } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/field/types/workspace-migration-field-action'; +import { WORKSPACE_MIGRATION_ACTION_TYPE } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/constants/workspace-migration-action-type.constant'; +import { WorkspaceMigrationRunnerService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/services/workspace-migration-runner.service'; + +@RegisteredWorkspaceCommand('2.18.0', 1799200001000) +@Command({ + name: 'upgrade:2-18:recompute-search-vectors', + description: + 'Recompute every TS_VECTOR (searchVector) column from its searchFieldMetadata rows, recreate its GIN index, and clear the deprecated cached TS_VECTOR settings. Idempotent.', +}) +export class RecomputeSearchVectorsCommand extends ActiveOrSuspendedWorkspaceCommandRunner { + constructor( + protected readonly workspaceIteratorService: WorkspaceIteratorService, + private readonly workspaceCacheService: WorkspaceCacheService, + private readonly workspaceMigrationRunnerService: WorkspaceMigrationRunnerService, + ) { + super(workspaceIteratorService); + } + + override async runOnWorkspace({ + workspaceId, + options, + }: RunOnWorkspaceArgs): Promise { + const isDryRun = options.dryRun ?? false; + + const { flatFieldMetadataMaps } = + await this.workspaceCacheService.getOrRecompute(workspaceId, [ + 'flatFieldMetadataMaps', + ]); + + const tsVectorFlatFieldMetadatas = Object.values( + flatFieldMetadataMaps.byUniversalIdentifier, + ) + .filter(isDefined) + .filter( + (flatFieldMetadata) => + flatFieldMetadata.type === FieldMetadataType.TS_VECTOR, + ); + + if (tsVectorFlatFieldMetadatas.length === 0) { + this.logger.log( + `No TS_VECTOR fields for workspace ${workspaceId}, skipping`, + ); + + return; + } + + this.logger.log( + `${isDryRun ? '[DRY RUN] ' : ''}Recomputing ${tsVectorFlatFieldMetadatas.length} search vector(s) and clearing their settings for workspace ${workspaceId}`, + ); + + if (isDryRun) { + return; + } + + const actions: UniversalUpdateFieldAction[] = tsVectorFlatFieldMetadatas.map( + (flatFieldMetadata) => ({ + type: WORKSPACE_MIGRATION_ACTION_TYPE.update, + metadataName: 'fieldMetadata', + universalIdentifier: flatFieldMetadata.universalIdentifier, + update: { universalSettings: null }, + rebuildSearchVector: true, + }), + ); + + await this.workspaceMigrationRunnerService.run({ + workspaceMigration: { + // Cross-app actions; this is only the runner's existence gate, not a scope filter. + applicationUniversalIdentifier: + TWENTY_STANDARD_APPLICATION.universalIdentifier, + actions, + }, + workspaceId, + }); + + this.logger.log( + `Successfully recomputed ${tsVectorFlatFieldMetadatas.length} search vector(s) for workspace ${workspaceId}`, + ); + } +}