Fix - Update searchVector when labelIdentifier is updated (#16940)

Fixes https://github.com/twentyhq/twenty/issues/16891

In next PR, validation rules will be added in migration logic
This commit is contained in:
Etienne
2026-01-06 13:55:25 +01:00
committed by GitHub
parent bd9e5986d2
commit 0c6f4021bf
18 changed files with 823 additions and 177 deletions
@@ -171,12 +171,14 @@ export const fromUpdateFieldInputToFlatFieldMetadata = ({
flatViewsToDelete,
flatViewFieldsToDelete,
flatViewsToUpdate,
flatFieldMetadatasToUpdate: flatFieldMetadatasToUpdateFromSideEffect,
} = sideEffectResult.result;
return {
flatFieldMetadatasToUpdate: [
...accumulator.flatFieldMetadatasToUpdate,
toFlatFieldMetadata,
...flatFieldMetadatasToUpdateFromSideEffect,
],
flatIndexMetadatasToUpdate: [
...accumulator.flatIndexMetadatasToUpdate,
@@ -1,6 +1,8 @@
import { type FromTo } from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
import { type AllFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/all-flat-entity-maps.type';
import { findFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps-or-throw.util';
import { type FieldInputTranspilationResult } from 'src/engine/metadata-modules/flat-field-metadata/types/field-input-transpilation-result.type';
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
import { handleEnumFlatFieldMetadataUpdateSideEffects } from 'src/engine/metadata-modules/flat-field-metadata/utils/handle-enum-flat-field-metadata-update-side-effects.util';
@@ -12,6 +14,7 @@ import {
type FieldMetadataUpdateIndexSideEffect,
handleIndexChangesDuringFieldUpdate,
} from 'src/engine/metadata-modules/flat-field-metadata/utils/handle-index-changes-during-field-update.util';
import { handleLabelIdentifierChangesDuringFieldUpdate } from 'src/engine/metadata-modules/flat-field-metadata/utils/handle-label-identifier-changes-during-field-update.util';
import { isEnumFlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/utils/is-enum-flat-field-metadata.util';
import { type FlatViewFiltersToDeleteAndUpdate } from 'src/engine/metadata-modules/flat-field-metadata/utils/recompute-view-filters-on-flat-field-metadata-options-update.util';
import { type FlatViewGroupsToDeleteUpdateAndCreate } from 'src/engine/metadata-modules/flat-field-metadata/utils/recompute-view-groups-on-flat-field-metadata-options-update.util';
@@ -20,7 +23,9 @@ export type FlatFieldMetadataUpdateSideEffects =
FlatViewFiltersToDeleteAndUpdate &
FlatViewGroupsToDeleteUpdateAndCreate &
FieldMetadataUpdateIndexSideEffect &
FieldMetadataDeactivationSideEffect;
FieldMetadataDeactivationSideEffect & {
flatFieldMetadatasToUpdate: FlatFieldMetadata[];
};
type HandleFlatFieldMetadataUpdateSideEffectArgs = FromTo<
FlatFieldMetadata,
@@ -52,6 +57,7 @@ export const FLAT_FIELD_METADATA_UPDATE_EMPTY_SIDE_EFFECTS: FlatFieldMetadataUpd
flatViewsToDelete: [],
flatViewFieldsToDelete: [],
flatViewsToUpdate: [],
flatFieldMetadatasToUpdate: [],
};
export const handleFlatFieldMetadataUpdateSideEffect = ({
@@ -131,6 +137,31 @@ export const handleFlatFieldMetadataUpdateSideEffect = ({
return indexChangesSideEffectResult;
}
const flatObjectMetadata = findFlatEntityByIdInFlatEntityMapsOrThrow({
flatEntityMaps: flatObjectMetadataMaps,
flatEntityId: fromFlatFieldMetadata.objectMetadataId,
});
const isLabelIdentifierFieldMetadata =
flatObjectMetadata.labelIdentifierFieldMetadataId ===
toFlatFieldMetadata.id;
if (isLabelIdentifierFieldMetadata) {
const flatSearchVectorFieldToUpdate =
handleLabelIdentifierChangesDuringFieldUpdate({
fromFlatFieldMetadata,
toFlatFieldMetadata,
flatObjectMetadata,
flatFieldMetadataMaps,
});
if (isDefined(flatSearchVectorFieldToUpdate)) {
sideEffectResult.flatFieldMetadatasToUpdate.push(
flatSearchVectorFieldToUpdate,
);
}
}
const {
flatIndexMetadatasToUpdate,
flatIndexMetadatasToCreate,
@@ -0,0 +1,72 @@
import { type FieldMetadataType, type FromTo } from 'twenty-shared/types';
import { findOrThrow } from 'twenty-shared/utils';
import {
FieldMetadataException,
FieldMetadataExceptionCode,
} from 'src/engine/metadata-modules/field-metadata/field-metadata.exception';
import { type AllFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/all-flat-entity-maps.type';
import { findManyFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-many-flat-entity-by-id-in-flat-entity-maps-or-throw.util';
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
import { SEARCH_VECTOR_FIELD } from 'src/engine/metadata-modules/search-field-metadata/constants/search-vector-field.constants';
import { getTsVectorColumnExpressionFromFields } from 'src/engine/workspace-manager/workspace-sync-metadata/utils/get-ts-vector-column-expression.util';
import { type SearchableFieldType } from 'src/engine/workspace-manager/workspace-sync-metadata/utils/is-searchable-field.util';
type HandleLabelIdentifierChangesDuringFieldUpdateArgs = {
flatObjectMetadata: FlatObjectMetadata;
} & FromTo<FlatFieldMetadata, 'flatFieldMetadata'> &
Pick<AllFlatEntityMaps, 'flatFieldMetadataMaps'>;
export const handleLabelIdentifierChangesDuringFieldUpdate = ({
fromFlatFieldMetadata,
toFlatFieldMetadata,
flatObjectMetadata,
flatFieldMetadataMaps,
}: HandleLabelIdentifierChangesDuringFieldUpdateArgs):
| FlatFieldMetadata<FieldMetadataType.TS_VECTOR>
| undefined => {
const hasNameChanged =
fromFlatFieldMetadata.name !== toFlatFieldMetadata.name;
if (!hasNameChanged) {
return undefined;
}
const objectFlatFieldMetadatas =
findManyFlatEntityByIdInFlatEntityMapsOrThrow({
flatEntityMaps: flatFieldMetadataMaps,
flatEntityIds: flatObjectMetadata.fieldMetadataIds,
});
const searchVectorField = findOrThrow(
objectFlatFieldMetadatas,
(field) => field.name === SEARCH_VECTOR_FIELD.name,
new FieldMetadataException(
`Search vector field not found for object metadata ${flatObjectMetadata.id}`,
FieldMetadataExceptionCode.FIELD_METADATA_NOT_FOUND,
),
) as FlatFieldMetadata<FieldMetadataType.TS_VECTOR>;
try {
const newAsExpression = getTsVectorColumnExpressionFromFields([
{
name: toFlatFieldMetadata.name,
type: toFlatFieldMetadata.type as SearchableFieldType,
},
]);
return {
...searchVectorField,
settings: {
...searchVectorField.settings,
asExpression: newAsExpression,
},
};
} catch {
throw new FieldMetadataException(
`Failed to compute search vector column expression for field ${toFlatFieldMetadata.name}`,
FieldMetadataExceptionCode.INVALID_FIELD_INPUT,
);
}
};