diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/field/services/update-field-action-handler.service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/field/services/update-field-action-handler.service.ts index fd42d0f5fc..fb5b86cfde 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/field/services/update-field-action-handler.service.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/field/services/update-field-action-handler.service.ts @@ -9,6 +9,7 @@ import { isDefined } from 'twenty-shared/utils'; import { ColumnType, type QueryRunner } from 'typeorm'; import { computeMorphOrRelationFieldJoinColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-morph-or-relation-field-join-column-name.util'; +import { createIndexInWorkspaceSchema } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/index/utils/index-action-handler.utils'; import { WorkspaceMigrationRunnerActionHandler } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/interfaces/workspace-migration-runner-action-handler-service.interface'; import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; @@ -17,6 +18,7 @@ import { getCompositeTypeOrThrow } from 'src/engine/metadata-modules/field-metad import { findFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps-or-throw.util'; import { findManyFlatEntityByIdInFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/utils/find-many-flat-entity-by-id-in-flat-entity-maps.util'; import { findFlatEntityByUniversalIdentifierOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-universal-identifier-or-throw.util'; +import { findFieldRelatedIndexes } from 'src/engine/metadata-modules/flat-field-metadata/utils/find-field-related-index.util'; import { FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type'; import { isCompositeFlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/utils/is-composite-flat-field-metadata.util'; import { isEnumFlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/utils/is-enum-flat-field-metadata.util'; @@ -168,6 +170,7 @@ export class UpdateFieldActionHandlerService extends WorkspaceMigrationRunnerAct flatObjectMetadataMaps, flatFieldMetadataMaps, flatSearchFieldMetadataMaps, + flatIndexMaps, }, workspaceId, } = context; @@ -368,6 +371,23 @@ export class UpdateFieldActionHandlerService extends WorkspaceMigrationRunnerAct tableName, columnDefinitions, }); + + const [searchVectorFlatIndexMetadata] = findFieldRelatedIndexes({ + flatFieldMetadata: optimisticFlatFieldMetadata, + flatObjectMetadata, + flatIndexMaps, + }); + + if (isDefined(searchVectorFlatIndexMetadata)) { + await createIndexInWorkspaceSchema({ + flatIndexMetadata: searchVectorFlatIndexMetadata, + flatObjectMetadata, + flatFieldMetadataMaps, + workspaceSchemaManagerService: this.workspaceSchemaManagerService, + queryRunner, + workspaceId, + }); + } } } diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/services/workspace-migration-runner.service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/services/workspace-migration-runner.service.ts index ba59817138..c61caac76d 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/services/workspace-migration-runner.service.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/services/workspace-migration-runner.service.ts @@ -230,6 +230,17 @@ export class WorkspaceMigrationRunnerService { const actionMetadataNames = [ ...new Set(actions.flatMap((action) => action.metadataName)), ]; + + const hasSearchVectorRebuildAction = actions.some( + (action) => + action.metadataName === 'fieldMetadata' && + action.type === 'update' && + action.rebuildSearchVector === true, + ); + + const searchVectorRebuildMetadataNames: AllMetadataName[] = + hasSearchVectorRebuildAction ? ['index'] : []; + const actionsMetadataAndRelatedMetadataNames: AllMetadataName[] = [ ...new Set([ ...actionMetadataNames, @@ -238,6 +249,7 @@ export class WorkspaceMigrationRunnerService { ...actionMetadataNames.flatMap( getMetadataRelatedMetadataNamesForValidation, ), + ...searchVectorRebuildMetadataNames, ]), ]; const allFlatEntityMapsKeys = actionsMetadataAndRelatedMetadataNames.map( diff --git a/packages/twenty-server/test/integration/metadata/suites/field-metadata/update-one-field-metadata-search-vector-side-effect.integration-spec.ts b/packages/twenty-server/test/integration/metadata/suites/field-metadata/update-one-field-metadata-search-vector-side-effect.integration-spec.ts index a0f63f5c64..44d45a7788 100644 --- a/packages/twenty-server/test/integration/metadata/suites/field-metadata/update-one-field-metadata-search-vector-side-effect.integration-spec.ts +++ b/packages/twenty-server/test/integration/metadata/suites/field-metadata/update-one-field-metadata-search-vector-side-effect.integration-spec.ts @@ -8,10 +8,27 @@ import { updateOneObjectMetadata } from 'test/integration/metadata/suites/object import { jestExpectToBeDefined } from 'test/utils/jest-expect-to-be-defined.util.test'; import { type FieldMetadataDTO } from 'src/engine/metadata-modules/field-metadata/dtos/field-metadata.dto'; +import { computeTableName } from 'src/engine/utils/compute-table-name.util'; // ISO search surface: renaming an indexed field (the name field, the default search field of a // custom object) must rebuild the searchVector column so global search keeps working, // since the generated expression embeds the field's column name. +const TEST_SCHEMA_NAME = 'workspace_1wgvd1injqtife6y4rvfbu3h5'; + +const findSearchVectorGinIndexDefinitions = async ( + tableName: string, +): Promise => { + const rows: { indexdef: string }[] = await global.testDataSource.query( + `SELECT indexdef FROM pg_indexes + WHERE schemaname = $1 AND tablename = $2 + AND indexdef ILIKE '%using gin%' + AND indexdef ILIKE '%searchVector%'`, + [TEST_SCHEMA_NAME, tableName], + ); + + return rows.map((row) => row.indexdef); +}; + describe('Field metadata update - search vector rename rebuild', () => { let testObjectMetadataId: string; let nameFieldMetadataId: string; @@ -19,6 +36,7 @@ describe('Field metadata update - search vector rename rebuild', () => { const OBJECT_NAME_SINGULAR = 'searchVectorRenameObject'; const OBJECT_NAME_PLURAL = 'searchVectorRenameObjects'; + const OBJECT_TABLE_NAME = computeTableName(OBJECT_NAME_SINGULAR, true); const RENAMED_NAME_FIELD = 'searchableLabelColumn'; const RECORD_NAME_VALUE = 'RenameRecomputeSearchToken33'; @@ -89,6 +107,11 @@ describe('Field metadata update - search vector rename rebuild', () => { }); it('should rebuild the search vector and keep search working when the indexed name field is renamed', async () => { + const ginIndexesBeforeRename = + await findSearchVectorGinIndexDefinitions(OBJECT_TABLE_NAME); + + expect(ginIndexesBeforeRename.length).toBe(1); + await updateOneFieldMetadata({ input: { idToUpdate: nameFieldMetadataId, @@ -116,5 +139,10 @@ describe('Field metadata update - search vector rename rebuild', () => { expect(searchResult.data.search.edges[0].node.objectNameSingular).toBe( OBJECT_NAME_SINGULAR, ); + + const ginIndexesAfterRename = + await findSearchVectorGinIndexDefinitions(OBJECT_TABLE_NAME); + + expect(ginIndexesAfterRename).toEqual(ginIndexesBeforeRename); }); });