Wire up search field metadata (#21964)

## Part 1 - Exact scope of the current PR (#21964)

close https://github.com/twentyhq/core-team-issues/issues/2586

This PR introduces `searchFieldMetadata` as a first-class flat metadata
entity and migrates the existing search surface onto it, with **no
change to which records are searchable** (ISO with `main`).

In scope (what the PR does):
- New flat entity `searchFieldMetadata` (universalIdentifier,
applicationId, **`position`**, maps, conversions), registered in the
central flat-entity constants and the migration build orchestrator.
- `searchVector.asExpression` is **derived server-side** from
`searchFieldMetadata` rows (validated by `isSafeTsVectorExpression`);
never trusted from client input.
- **Derivation order is deterministic, driven by each row's `position`**
([compute-search-vector-as-expression-from-search-field-metadatas.util.ts](packages/twenty-server/src/engine/metadata-modules/flat-search-field-metadata/utils/compute-search-vector-as-expression-from-search-field-metadatas.util.ts)),
replacing the previous non-deterministic `(createdAt, id)` sort. That
sort collapsed to random UUIDs for standard fields (same `createdAt`),
so any rename/relabel rewrote the `STORED` generated column to a
logically-identical-but-textually-different expression and produced a
permanent per-workspace diff vs the standard definition. Ordering now
equals provisioning order; ties break on `universalIdentifier`.
- Provisioning at object creation mirrors the existing surface exactly
**and seeds `position`**:
- custom objects -> the `name` field only, at `position: 0`
([build-default-search-field-metadatas-for-custom-object.util.ts](packages/twenty-server/src/engine/metadata-modules/object-metadata/utils/build-default-search-field-metadatas-for-custom-object.util.ts))
- standard objects -> their curated `SEARCH_FIELDS_FOR_*` sets,
`position` = the curated index
- Backfill (instance + workspace commands in `2-16`) provisions rows for
existing workspaces with the same surface **and the same positions**
(standard from the curated standard maps, custom `name` = `0`), scoped
to the workspace's own custom application
([build-search-field-metadata-backfill-operations.util.ts](packages/twenty-server/src/database/commands/upgrade-version-command/2-16/utils/build-search-field-metadata-backfill-operations.util.ts)).
The `position` column is added in the same `2-16` fast instance command
as `universalIdentifier`/`applicationId`.
- Field rename of an already-indexed field recomputes `asExpression`
(positions preserved, so order is stable)
([recompute-search-vector-on-field-rename.util.ts](packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/recompute-search-vector-on-field-rename.util.ts)).
- Field delete drops the matching row(s) and recomputes; remaining rows
keep their relative order (no renumber)
([from-delete-field-input-to-flat-field-metadatas-to-delete.util.ts](packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/from-delete-field-input-to-flat-field-metadatas-to-delete.util.ts)).
- Object relabel is **additive** and ISO/regression-fix only: it indexes
the new label identifier **appended last (`position = max(existing) +
1`)** without dropping `name`
([recompute-search-vector-on-label-identifier-update.util.ts](packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/utils/recompute-search-vector-on-label-identifier-update.util.ts)).
This is a deliberate, temporary bridge.

Explicitly OUT of scope (deferred):
- No API to edit `searchFieldMetadata` (no user-facing search-field
configuration, including `position` — it is internal and only written by
provisioning/backfill/recompute).
- No auto-indexing of arbitrary searchable fields. Creating a custom
TEXT/EMAILS/etc. field does NOT add it to search (the
`computeSearchFieldMetadataCreationForFields` behavior was removed in
`e6820ad`).
- No field-type-transition handling (field type is immutable - not in
`FLAT_FIELD_METADATA_EDITABLE_PROPERTIES`, so that path was dead code).
- No `position` validation (uniqueness/range) and no multi-vector /
per-field `weight` config — deferred to the configurable-search
follow-up (#1428).

Net: `searchFieldMetadata` becomes the source of truth for the *same*
surface as `main`. The only intentional divergences from `main` are
"relabel preserves `name`" (additive) and the deterministic
`position`-ordered `asExpression` (a correctness/perf fix that is
byte-identical to provisioning order, so it does not change the
searchable surface).

---------

Co-authored-by: Félix Malfait <felix@twenty.com>
This commit is contained in:
Paul Rastoin
2026-06-23 16:27:13 +02:00
committed by GitHub
parent 99e7c2cde0
commit e9d5d71cd3
102 changed files with 3850 additions and 366 deletions
@@ -46,6 +46,7 @@ describe('fromDeleteFieldInputToFlatFieldMetadatasToDelete', () => {
flatFieldMetadataMaps: buildFlatFieldMetadataMaps([]),
flatObjectMetadataMaps: buildFlatObjectMetadataMaps('obj-1'),
flatIndexMaps: createEmptyFlatEntityMaps(),
flatSearchFieldMetadataMaps: createEmptyFlatEntityMaps(),
}),
).toThrow(
expect.objectContaining({
@@ -69,6 +70,7 @@ describe('fromDeleteFieldInputToFlatFieldMetadatasToDelete', () => {
flatFieldMetadataMaps: buildFlatFieldMetadataMaps([standardField]),
flatObjectMetadataMaps: buildFlatObjectMetadataMaps(objectId),
flatIndexMaps: createEmptyFlatEntityMaps(),
flatSearchFieldMetadataMaps: createEmptyFlatEntityMaps(),
}),
).toThrow(
expect.objectContaining({
@@ -92,6 +94,7 @@ describe('fromDeleteFieldInputToFlatFieldMetadatasToDelete', () => {
flatFieldMetadataMaps: buildFlatFieldMetadataMaps([standardField]),
flatObjectMetadataMaps: buildFlatObjectMetadataMaps(objectId),
flatIndexMaps: createEmptyFlatEntityMaps(),
flatSearchFieldMetadataMaps: createEmptyFlatEntityMaps(),
}),
).toThrow(new RegExp('Cannot delete standard field "city"'));
});
@@ -110,6 +113,7 @@ describe('fromDeleteFieldInputToFlatFieldMetadatasToDelete', () => {
flatFieldMetadataMaps: buildFlatFieldMetadataMaps([customField]),
flatObjectMetadataMaps: buildFlatObjectMetadataMaps(objectId),
flatIndexMaps: createEmptyFlatEntityMaps(),
flatSearchFieldMetadataMaps: createEmptyFlatEntityMaps(),
});
expect(result.flatFieldMetadatasToDelete).toContainEqual(
@@ -132,6 +136,7 @@ describe('fromDeleteFieldInputToFlatFieldMetadatasToDelete', () => {
flatFieldMetadataMaps: buildFlatFieldMetadataMaps([standardField]),
flatObjectMetadataMaps: buildFlatObjectMetadataMaps(objectId),
flatIndexMaps: createEmptyFlatEntityMaps(),
flatSearchFieldMetadataMaps: createEmptyFlatEntityMaps(),
}),
).toThrow(FieldMetadataException);
});
@@ -155,6 +160,7 @@ describe('fromDeleteFieldInputToFlatFieldMetadatasToDelete', () => {
flatFieldMetadataMaps: buildFlatFieldMetadataMaps([standardAppField]),
flatObjectMetadataMaps: buildFlatObjectMetadataMaps(objectId),
flatIndexMaps: createEmptyFlatEntityMaps(),
flatSearchFieldMetadataMaps: createEmptyFlatEntityMaps(),
}),
).toThrow(
expect.objectContaining({
@@ -0,0 +1,89 @@
import { type FieldMetadataType } 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 { findFlatEntityByIdInFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps.util';
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 { recomputeSearchVectorFieldFromSearchFieldMetadatas } from 'src/engine/metadata-modules/flat-object-metadata/utils/recompute-search-vector-field-from-search-field-metadatas.util';
import { type FlatSearchFieldMetadata } from 'src/engine/metadata-modules/flat-search-field-metadata/types/flat-search-field-metadata.type';
import { type UniversalFlatSearchFieldMetadata } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-search-field-metadata.type';
export const computeSearchFieldMetadataDeletionForDeletedFields = ({
flatFieldMetadatasToDelete,
flatObjectMetadataMaps,
flatFieldMetadataMaps,
flatSearchFieldMetadataMaps,
}: {
flatFieldMetadatasToDelete: FlatFieldMetadata[];
} & Pick<
AllFlatEntityMaps,
| 'flatObjectMetadataMaps'
| 'flatFieldMetadataMaps'
| 'flatSearchFieldMetadataMaps'
>): {
searchFieldMetadatasToDelete: UniversalFlatSearchFieldMetadata[];
flatSearchVectorFieldsToUpdate: FlatFieldMetadata<FieldMetadataType.TS_VECTOR>[];
} => {
const deletedFieldIds = new Set(
flatFieldMetadatasToDelete.map((flatFieldMetadata) => flatFieldMetadata.id),
);
const affectedObjectMetadataIds = new Set(
flatFieldMetadatasToDelete.map(
(flatFieldMetadata) => flatFieldMetadata.objectMetadataId,
),
);
const searchFieldMetadatasToDelete: UniversalFlatSearchFieldMetadata[] = [];
const flatSearchVectorFieldsToUpdate: FlatFieldMetadata<FieldMetadataType.TS_VECTOR>[] =
[];
for (const objectMetadataId of affectedObjectMetadataIds) {
const flatObjectMetadata = findFlatEntityByIdInFlatEntityMaps({
flatEntityId: objectMetadataId,
flatEntityMaps: flatObjectMetadataMaps,
});
if (!isDefined(flatObjectMetadata) || !flatObjectMetadata.isSearchable) {
continue;
}
const objectSearchFieldMetadatas =
findManyFlatEntityByIdInFlatEntityMapsOrThrow<FlatSearchFieldMetadata>({
flatEntityMaps: flatSearchFieldMetadataMaps,
flatEntityIds: flatObjectMetadata.searchFieldMetadataIds,
});
const rowsToDelete = objectSearchFieldMetadatas.filter(
(searchFieldMetadata) =>
deletedFieldIds.has(searchFieldMetadata.fieldMetadataId),
);
if (rowsToDelete.length === 0) {
continue;
}
searchFieldMetadatasToDelete.push(...rowsToDelete);
const remainingSearchFieldMetadatas = objectSearchFieldMetadatas.filter(
(searchFieldMetadata) =>
!deletedFieldIds.has(searchFieldMetadata.fieldMetadataId),
);
const flatSearchVectorFieldToUpdate =
recomputeSearchVectorFieldFromSearchFieldMetadatas({
flatObjectMetadata,
flatFieldMetadataMaps,
searchFieldMetadatas: remainingSearchFieldMetadatas,
});
if (isDefined(flatSearchVectorFieldToUpdate)) {
flatSearchVectorFieldsToUpdate.push(flatSearchVectorFieldToUpdate);
}
}
return {
searchFieldMetadatasToDelete,
flatSearchVectorFieldsToUpdate,
};
};
@@ -1,3 +1,4 @@
import { type FieldMetadataType } from 'twenty-shared/types';
import {
isDefined,
trimAndRemoveDuplicatedWhitespacesFromObjectStringProperties,
@@ -12,18 +13,24 @@ import { type AllFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/
import { findFlatEntityByIdInFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/utils/find-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 { findManyFlatEntityByUniversalIdentifierInUniversalFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-many-flat-entity-by-universal-identifier-in-universal-flat-entity-maps-or-throw.util';
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
import { computeFlatFieldMetadataRelatedFlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/utils/compute-flat-field-metadata-related-flat-field-metadata.util';
import { computeSearchFieldMetadataDeletionForDeletedFields } from 'src/engine/metadata-modules/flat-field-metadata/utils/compute-search-field-metadata-deletion-for-deleted-fields.util';
import { type FlatIndexMetadata } from 'src/engine/metadata-modules/flat-index-metadata/types/flat-index-metadata.type';
import { generateFlatIndexMetadataWithNameOrThrow } from 'src/engine/metadata-modules/index-metadata/utils/generate-flat-index.util';
import { belongsToTwentyStandardApp } from 'src/engine/metadata-modules/utils/belongs-to-twenty-standard-app.util';
import { type UniversalFlatFieldMetadata } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-field-metadata.type';
import { type UniversalFlatIndexMetadata } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-index-metadata.type';
import { type UniversalFlatSearchFieldMetadata } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-search-field-metadata.type';
type FromDeleteFieldInputToFlatFieldMetadatasToDeleteArgs = {
deleteOneFieldInput: DeleteOneFieldInput;
} & Pick<
AllFlatEntityMaps,
'flatFieldMetadataMaps' | 'flatIndexMaps' | 'flatObjectMetadataMaps'
| 'flatFieldMetadataMaps'
| 'flatIndexMaps'
| 'flatObjectMetadataMaps'
| 'flatSearchFieldMetadataMaps'
>;
// TODO refactor as a side effect service
export const fromDeleteFieldInputToFlatFieldMetadatasToDelete = ({
@@ -31,10 +38,13 @@ export const fromDeleteFieldInputToFlatFieldMetadatasToDelete = ({
deleteOneFieldInput: rawDeleteOneInput,
flatIndexMaps: existingFlatIndexMaps,
flatFieldMetadataMaps: existingFlatFieldMetadataMaps,
flatSearchFieldMetadataMaps: existingFlatSearchFieldMetadataMaps,
}: FromDeleteFieldInputToFlatFieldMetadatasToDeleteArgs): {
flatFieldMetadatasToDelete: UniversalFlatFieldMetadata[];
flatIndexesToUpdate: UniversalFlatIndexMetadata[];
flatIndexesToDelete: UniversalFlatIndexMetadata[];
searchFieldMetadatasToDelete: UniversalFlatSearchFieldMetadata[];
flatSearchVectorFieldsToUpdate: FlatFieldMetadata<FieldMetadataType.TS_VECTOR>[];
} => {
const { id: fieldMetadataToDeleteId } =
trimAndRemoveDuplicatedWhitespacesFromObjectStringProperties(
@@ -177,9 +187,19 @@ export const fromDeleteFieldInputToFlatFieldMetadatasToDelete = ({
},
);
const { searchFieldMetadatasToDelete, flatSearchVectorFieldsToUpdate } =
computeSearchFieldMetadataDeletionForDeletedFields({
flatFieldMetadatasToDelete,
flatObjectMetadataMaps: existingFlatObjectMetadataMaps,
flatFieldMetadataMaps: existingFlatFieldMetadataMaps,
flatSearchFieldMetadataMaps: existingFlatSearchFieldMetadataMaps,
});
return {
flatFieldMetadatasToDelete,
flatIndexesToDelete,
flatIndexesToUpdate,
searchFieldMetadatasToDelete,
flatSearchVectorFieldsToUpdate,
};
};
@@ -35,6 +35,7 @@ type FromUpdateFieldInputToFlatFieldMetadataArgs = {
| 'flatViewGroupMaps'
| 'flatViewMaps'
| 'flatViewFieldMaps'
| 'flatSearchFieldMetadataMaps'
>;
type FlatFieldMetadataAndIndexToUpdate = {
@@ -51,6 +52,7 @@ export const fromUpdateFieldInputToFlatFieldMetadata = ({
flatViewGroupMaps,
flatViewMaps,
flatViewFieldMaps,
flatSearchFieldMetadataMaps,
isSystemBuild,
}: FromUpdateFieldInputToFlatFieldMetadataArgs): FieldInputTranspilationResult<FlatFieldMetadataAndIndexToUpdate> => {
const updateFieldInputInformalProperties =
@@ -150,6 +152,7 @@ export const fromUpdateFieldInputToFlatFieldMetadata = ({
toFlatFieldMetadata,
flatViewMaps,
flatViewFieldMaps,
flatSearchFieldMetadataMaps,
flatApplication,
});
@@ -15,8 +15,8 @@ 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 { recomputeSearchVectorOnFieldRename } from 'src/engine/metadata-modules/flat-field-metadata/utils/recompute-search-vector-on-field-rename.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';
@@ -41,6 +41,7 @@ type HandleFlatFieldMetadataUpdateSideEffectArgs = FromTo<
| 'flatViewGroupMaps'
| 'flatViewMaps'
| 'flatViewFieldMaps'
| 'flatSearchFieldMetadataMaps'
> & {
flatApplication: FlatApplication;
};
@@ -71,6 +72,7 @@ export const handleFlatFieldMetadataUpdateSideEffect = ({
flatViewGroupMaps,
flatViewMaps,
flatViewFieldMaps,
flatSearchFieldMetadataMaps,
flatApplication,
}: HandleFlatFieldMetadataUpdateSideEffectArgs): FieldInputTranspilationResult<FlatFieldMetadataUpdateSideEffects> => {
const sideEffectResult = structuredClone(
@@ -143,24 +145,18 @@ export const handleFlatFieldMetadataUpdateSideEffect = ({
flatEntityId: fromFlatFieldMetadata.objectMetadataId,
});
const isLabelIdentifierFieldMetadata =
flatObjectMetadata.labelIdentifierFieldMetadataId ===
toFlatFieldMetadata.id;
const { flatSearchVectorFieldToUpdate } = recomputeSearchVectorOnFieldRename({
fromFlatFieldMetadata,
toFlatFieldMetadata,
flatObjectMetadata,
flatFieldMetadataMaps,
flatSearchFieldMetadataMaps,
});
if (isLabelIdentifierFieldMetadata) {
const flatSearchVectorFieldToUpdate =
handleLabelIdentifierChangesDuringFieldUpdate({
fromFlatFieldMetadata,
toFlatFieldMetadata,
flatObjectMetadata,
flatFieldMetadataMaps,
});
if (isDefined(flatSearchVectorFieldToUpdate)) {
sideEffectResult.flatFieldMetadatasToUpdate.push(
flatSearchVectorFieldToUpdate,
);
}
if (isDefined(flatSearchVectorFieldToUpdate)) {
sideEffectResult.flatFieldMetadatasToUpdate.push(
flatSearchVectorFieldToUpdate,
);
}
const {
@@ -1,71 +0,0 @@
import { type FieldMetadataType, type FromTo } from 'twenty-shared/types';
import { findOrThrow, type SearchableFieldType } 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/utils/get-ts-vector-column-expression.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.fieldIds,
});
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,
universalSettings: {
...searchVectorField.universalSettings,
asExpression: newAsExpression,
},
};
} catch {
throw new FieldMetadataException(
`Failed to compute search vector column expression for field ${toFlatFieldMetadata.name}`,
FieldMetadataExceptionCode.INVALID_FIELD_INPUT,
);
}
};
@@ -0,0 +1,68 @@
import { type FieldMetadataType, type FromTo } from 'twenty-shared/types';
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 { recomputeSearchVectorFieldFromSearchFieldMetadatas } from 'src/engine/metadata-modules/flat-object-metadata/utils/recompute-search-vector-field-from-search-field-metadatas.util';
import { type FlatSearchFieldMetadata } from 'src/engine/metadata-modules/flat-search-field-metadata/types/flat-search-field-metadata.type';
export type FieldMetadataUpdateSearchVectorSideEffect = {
flatSearchVectorFieldToUpdate:
| FlatFieldMetadata<FieldMetadataType.TS_VECTOR>
| undefined;
};
type RecomputeSearchVectorOnFieldRenameArgs = {
flatObjectMetadata: FlatObjectMetadata;
} & FromTo<FlatFieldMetadata, 'flatFieldMetadata'> &
Pick<
AllFlatEntityMaps,
'flatFieldMetadataMaps' | 'flatSearchFieldMetadataMaps'
>;
const EMPTY_SIDE_EFFECT: FieldMetadataUpdateSearchVectorSideEffect = {
flatSearchVectorFieldToUpdate: undefined,
};
export const recomputeSearchVectorOnFieldRename = ({
fromFlatFieldMetadata,
toFlatFieldMetadata,
flatObjectMetadata,
flatFieldMetadataMaps,
flatSearchFieldMetadataMaps,
}: RecomputeSearchVectorOnFieldRenameArgs): FieldMetadataUpdateSearchVectorSideEffect => {
if (!flatObjectMetadata.isSearchable) {
return EMPTY_SIDE_EFFECT;
}
const objectSearchFieldMetadatas =
findManyFlatEntityByIdInFlatEntityMapsOrThrow<FlatSearchFieldMetadata>({
flatEntityMaps: flatSearchFieldMetadataMaps,
flatEntityIds: flatObjectMetadata.searchFieldMetadataIds,
});
const isIndexedField = objectSearchFieldMetadatas.some(
(searchFieldMetadata) =>
searchFieldMetadata.fieldMetadataId === toFlatFieldMetadata.id,
);
const hasNameChanged =
fromFlatFieldMetadata.name !== toFlatFieldMetadata.name;
if (!isIndexedField || !hasNameChanged) {
return EMPTY_SIDE_EFFECT;
}
return {
flatSearchVectorFieldToUpdate:
recomputeSearchVectorFieldFromSearchFieldMetadatas({
flatObjectMetadata,
flatFieldMetadataMaps,
searchFieldMetadatas: objectSearchFieldMetadatas,
overrideFlatFieldMetadataById: new Map([
[toFlatFieldMetadata.id, toFlatFieldMetadata],
]),
}),
};
};