Deprecate asExpression from field metadata search_vector (#22287)
## Summary Fully deprecates the cached `asExpression` / `generatedType` settings on `TS_VECTOR` (searchVector) fields. Previously the generated-column expression was stored in `FieldMetadataSettings` and kept in sync via imperative recompute side-effects. It is now **derived at DDL time** from the `searchFieldMetadata` rows that describe which fields feed the search vector, making `searchFieldMetadata` the single source of truth and removing a whole class of cache-drift bugs. This is delivered across the milestones tracked in #2587 and coordinates with the frontend migration (#1428). ## Why - The searchVector expression lived in two places (stored `settings.asExpression` + the actual generated column), kept consistent by bespoke side-effects (`recompute-search-vector-on-field-rename`, label-identifier recompute, etc.). - The frontend reconstructed the searchable-fields list by **regex-parsing** the stored `asExpression`. - Both are brittle. Deriving the expression from `searchFieldMetadata` rows at build/run time removes the cache and the parsing. ## What changed ### Server - data model & derivation - Introduce the `tsVectorFieldMetadata` relation on `searchFieldMetadata` (`tsVectorFieldMetadataId` / universal identifier) linking each searchable-field row to its target `TS_VECTOR` field. - New runtime derivation `deriveSearchVectorAsExpressionForTsVectorField` (`flat-search-field-metadata/utils/...`) used by the create-object and update-field handlers to generate the column expression from `searchFieldMetadata` rows. - Remove `asExpression` / `generatedType` from stored settings: `FieldMetadataSettings.TS_VECTOR` is now `null`; the column builder (`generate-column-definitions.util.ts`) hardcodes `generatedType: 'STORED'` and requires the derived expression. - Delete the imperative recompute side-effects and the `compute-search-vector-universal-settings-from-object-manifest` path; drop the `settings` block from all 28 standard `compute-*-standard-flat-field-metadata` utils. ### Server - migration runner - New `rebuildSearchVector` marker on `update-field` actions: the orchestrator synthesizes targeted column rebuilds (`compute-search-vector-rebuild-target-universal-identifiers.util.ts` + the deprioritize aggregator) only when a searchFieldMetadata change or indexed-field rename actually requires it - instead of rebuilding on every settings touch. - Deferrable FKs + in-flight ID resolution so a `searchFieldMetadata` row and its `TS_VECTOR` field can be created in the same transaction (deterministic UUIDs). ### Frontend (contract change, #1428) - New `SearchFieldMetadataDTO` + dataloader exposing `searchFieldMetadataList` on object metadata. - `SettingsObjectSearchSection` now reads `objectMetadataItem.searchFieldMetadatas` instead of parsing `asExpression`; new `SearchFieldMetadataItem` type, fragment, and mapping updates. ### Upgrade commands (2.18) - `2-18-instance-command-fast-...-add-ts-vector-field-metadata-id-to-search-field-metadata` - `2-18-instance-command-fast-...-make-search-field-metadata-fks-deferrable` - `2-18-instance-command-slow-...-backfill-ts-vector-field-metadata-id-on-search-field-metadata` (These were relocated from 2.16 to 2.18 and re-timestamped into an ordered block - add column -> make FK deferrable -> backfill data - since 2.16/2.17 are released.) ### Tests - Updated search-vector side-effect integration specs to assert behavior (search works) rather than the now-removed `asExpression`; removed the obsolete expression-validation specs; refreshed the application-sync snapshot (`universalSettings: null`). ## Upgrade / compatibility notes - Existing workspaces keep their stored `settings` until a later cleanup; nothing reads it anymore. The new derivation drives all DDL going forward. - Schema changes are gated behind the 2.18 instance commands above. ## Known follow-up (separate PR) https://github.com/twentyhq/core-team-issues/issues/2620 - The column rebuild (`DROP`/`ADD` of the `searchVector` STORED column) cascade-drops its GIN index and does not recreate it - a pre-existing regression on `main` inherited here. A follow-up PR will fix the rebuild handler to recreate the GIN index and add a 2.18 workspace command to recompute every search vector + strip the deprecated settings. (Planned.) ## Test plan - [ ] `npx nx typecheck twenty-server` / `twenty-front` - [ ] `npx nx lint:diff-with-main twenty-server` / `twenty-front` - [ ] Server integration: create/update/delete field, rename indexed field, update object - search returns expected records - [ ] Run the 2.18 instance commands on a seeded DB; verify `tsVectorFieldMetadataId` backfilled and FKs deferrable - [ ] Frontend: object Search settings tab lists the correct searchable fields (no `asExpression` parsing) close https://github.com/twentyhq/core-team-issues/issues/2587 <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/22287?utm_source=github" target="_blank" rel="noopener noreferrer" data-no-image-dialog="true"><picture><source media="(prefers-color-scheme: dark)" srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img alt="Review in cubic" src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a> <!-- End of auto-generated description by cubic. -->
This commit is contained in:
+7
@@ -30,6 +30,7 @@ import { AssignTypeIfIsMorphOrRelationFieldMetadataType } from 'src/engine/metad
|
||||
import { IndexFieldMetadataEntity } from 'src/engine/metadata-modules/index-metadata/index-field-metadata.entity';
|
||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
import { FieldPermissionEntity } from 'src/engine/metadata-modules/object-permission/field-permission/field-permission.entity';
|
||||
import { SearchFieldMetadataEntity } from 'src/engine/metadata-modules/search-field-metadata/search-field-metadata.entity';
|
||||
import { ViewFieldEntity } from 'src/engine/metadata-modules/view-field/entities/view-field.entity';
|
||||
import { ViewFilterEntity } from 'src/engine/metadata-modules/view-filter/entities/view-filter.entity';
|
||||
import { ViewEntity } from 'src/engine/metadata-modules/view/entities/view.entity';
|
||||
@@ -238,4 +239,10 @@ export class FieldMetadataEntity<
|
||||
|
||||
@OneToMany(() => ViewSortEntity, (viewSort) => viewSort.fieldMetadata)
|
||||
viewSorts: Relation<ViewSortEntity[]>;
|
||||
|
||||
@OneToMany(
|
||||
() => SearchFieldMetadataEntity,
|
||||
(searchFieldMetadata) => searchFieldMetadata.fieldMetadata,
|
||||
)
|
||||
searchFieldMetadatas: Relation<SearchFieldMetadataEntity[]>;
|
||||
}
|
||||
|
||||
+3
-8
@@ -16,7 +16,6 @@ import {
|
||||
FieldMetadataExceptionCode,
|
||||
} from 'src/engine/metadata-modules/field-metadata/field-metadata.exception';
|
||||
import { WorkspaceManyOrAllFlatEntityMapsCacheService } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.service';
|
||||
import { findFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps-or-throw.util';
|
||||
import { findFlatEntityByUniversalIdentifierOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-universal-identifier-or-throw.util';
|
||||
import { findFlatEntityByUniversalIdentifier } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-universal-identifier.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';
|
||||
@@ -114,7 +113,6 @@ export class FieldMetadataService extends TypeOrmQueryService<FieldMetadataEntit
|
||||
flatIndexesToDelete,
|
||||
flatIndexesToUpdate,
|
||||
searchFieldMetadatasToDelete,
|
||||
flatSearchVectorFieldsToUpdate,
|
||||
} = fromDeleteFieldInputToFlatFieldMetadatasToDelete({
|
||||
deleteOneFieldInput,
|
||||
flatFieldMetadataMaps: existingFlatFieldMetadataMaps,
|
||||
@@ -162,7 +160,7 @@ export class FieldMetadataService extends TypeOrmQueryService<FieldMetadataEntit
|
||||
fieldMetadata: {
|
||||
flatEntityToCreate: [],
|
||||
flatEntityToDelete: flatFieldMetadatasToDelete,
|
||||
flatEntityToUpdate: flatSearchVectorFieldsToUpdate,
|
||||
flatEntityToUpdate: [],
|
||||
},
|
||||
index: {
|
||||
flatEntityToCreate: [],
|
||||
@@ -228,7 +226,6 @@ export class FieldMetadataService extends TypeOrmQueryService<FieldMetadataEntit
|
||||
flatViewGroupMaps: existingFlatViewGroupMaps,
|
||||
flatViewMaps: existingFlatViewMaps,
|
||||
flatViewFieldMaps: existingFlatViewFieldMaps,
|
||||
flatSearchFieldMetadataMaps: existingFlatSearchFieldMetadataMaps,
|
||||
} = await this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps(
|
||||
{
|
||||
workspaceId,
|
||||
@@ -240,7 +237,6 @@ export class FieldMetadataService extends TypeOrmQueryService<FieldMetadataEntit
|
||||
'flatViewGroupMaps',
|
||||
'flatViewMaps',
|
||||
'flatViewFieldMaps',
|
||||
'flatSearchFieldMetadataMaps',
|
||||
],
|
||||
},
|
||||
);
|
||||
@@ -254,7 +250,6 @@ export class FieldMetadataService extends TypeOrmQueryService<FieldMetadataEntit
|
||||
flatViewGroupMaps: existingFlatViewGroupMaps,
|
||||
flatViewMaps: existingFlatViewMaps,
|
||||
flatViewFieldMaps: existingFlatViewFieldMaps,
|
||||
flatSearchFieldMetadataMaps: existingFlatSearchFieldMetadataMaps,
|
||||
flatApplication: resolvedOwnerFlatApplication,
|
||||
isSystemBuild,
|
||||
});
|
||||
@@ -354,8 +349,8 @@ export class FieldMetadataService extends TypeOrmQueryService<FieldMetadataEntit
|
||||
},
|
||||
);
|
||||
|
||||
return findFlatEntityByIdInFlatEntityMapsOrThrow({
|
||||
flatEntityId: flatFieldMetadatasToUpdate[0].id,
|
||||
return findFlatEntityByUniversalIdentifierOrThrow({
|
||||
universalIdentifier: flatFieldMetadatasToUpdate[0].universalIdentifier,
|
||||
flatEntityMaps: recomputedFlatFieldMetadataMaps,
|
||||
});
|
||||
}
|
||||
|
||||
+5
@@ -1785,6 +1785,11 @@ export const ALL_ENTITY_PROPERTIES_CONFIGURATION_BY_METADATA_NAME = {
|
||||
toStringify: false,
|
||||
universalProperty: 'fieldMetadataUniversalIdentifier',
|
||||
},
|
||||
tsVectorFieldMetadataId: {
|
||||
toCompare: false,
|
||||
toStringify: false,
|
||||
universalProperty: 'tsVectorFieldMetadataUniversalIdentifier',
|
||||
},
|
||||
position: {
|
||||
toCompare: true,
|
||||
toStringify: false,
|
||||
|
||||
+3
@@ -312,6 +312,9 @@ export const ALL_MANY_TO_ONE_METADATA_FOREIGN_KEY = {
|
||||
fieldMetadata: {
|
||||
foreignKey: 'fieldMetadataId',
|
||||
},
|
||||
tsVectorFieldMetadata: {
|
||||
foreignKey: 'tsVectorFieldMetadataId',
|
||||
},
|
||||
},
|
||||
} as const satisfies ManyToOneMetadataRelationsProperties;
|
||||
|
||||
|
||||
+8
-1
@@ -544,10 +544,17 @@ export const ALL_MANY_TO_ONE_METADATA_RELATIONS = {
|
||||
fieldMetadata: {
|
||||
metadataName: 'fieldMetadata',
|
||||
foreignKey: 'fieldMetadataId',
|
||||
inverseOneToManyProperty: null,
|
||||
inverseOneToManyProperty: 'searchFieldMetadatas',
|
||||
isNullable: false,
|
||||
universalForeignKey: 'fieldMetadataUniversalIdentifier',
|
||||
},
|
||||
tsVectorFieldMetadata: {
|
||||
metadataName: 'fieldMetadata',
|
||||
foreignKey: 'tsVectorFieldMetadataId',
|
||||
inverseOneToManyProperty: null,
|
||||
isNullable: false,
|
||||
universalForeignKey: 'tsVectorFieldMetadataUniversalIdentifier',
|
||||
},
|
||||
},
|
||||
} as const satisfies ManyToOneMetadataRelationsProperties;
|
||||
|
||||
|
||||
+6
@@ -77,6 +77,12 @@ export const ALL_ONE_TO_MANY_METADATA_RELATIONS = {
|
||||
universalFlatEntityForeignKeyAggregator:
|
||||
'mainGroupByFieldMetadataViewUniversalIdentifiers',
|
||||
},
|
||||
searchFieldMetadatas: {
|
||||
metadataName: 'searchFieldMetadata',
|
||||
flatEntityForeignKeyAggregator: 'searchFieldMetadataIds',
|
||||
universalFlatEntityForeignKeyAggregator:
|
||||
'searchFieldMetadataUniversalIdentifiers',
|
||||
},
|
||||
},
|
||||
objectMetadata: {
|
||||
fields: {
|
||||
|
||||
+4
-1
@@ -17,7 +17,8 @@ type FieldMetadataOneToManySyncableRelations =
|
||||
type Assertions = [
|
||||
// FieldMetadataEntity OneToMany relations (all targets):
|
||||
// - indexFieldMetadatas, fieldPermissions, viewFields, viewFilters,
|
||||
// - kanbanAggregateOperationViews, calendarViews, mainGroupByFieldMetadataViews, viewSorts
|
||||
// - kanbanAggregateOperationViews, calendarViews, mainGroupByFieldMetadataViews,
|
||||
// - viewSorts, searchFieldMetadatas
|
||||
Expect<
|
||||
Equal<
|
||||
FieldMetadataOneToManyRelations,
|
||||
@@ -29,6 +30,7 @@ type Assertions = [
|
||||
| 'calendarViews'
|
||||
| 'mainGroupByFieldMetadataViews'
|
||||
| 'viewSorts'
|
||||
| 'searchFieldMetadatas'
|
||||
>
|
||||
>,
|
||||
|
||||
@@ -43,6 +45,7 @@ type Assertions = [
|
||||
| 'calendarViews'
|
||||
| 'mainGroupByFieldMetadataViews'
|
||||
| 'viewSorts'
|
||||
| 'searchFieldMetadatas'
|
||||
>
|
||||
>,
|
||||
];
|
||||
|
||||
+2
-1
@@ -10,7 +10,7 @@ type FieldMetadataRelatedProperties =
|
||||
type Assertions = [
|
||||
// FieldMetadataEntity has both ManyToOne and OneToMany relations
|
||||
// ManyToOne: object, workspace, application, relationTargetFieldMetadata, relationTargetObjectMetadata
|
||||
// OneToMany: indexFieldMetadatas, fieldPermissions, viewFields, viewFilters, kanbanAggregateOperationViews, calendarViews, mainGroupByFieldMetadataViews, viewSorts
|
||||
// OneToMany: indexFieldMetadatas, fieldPermissions, viewFields, viewFilters, kanbanAggregateOperationViews, calendarViews, mainGroupByFieldMetadataViews, viewSorts, searchFieldMetadatas
|
||||
Expect<
|
||||
Equal<
|
||||
FieldMetadataRelatedProperties,
|
||||
@@ -27,6 +27,7 @@ type Assertions = [
|
||||
| 'calendarViews'
|
||||
| 'mainGroupByFieldMetadataViews'
|
||||
| 'viewSorts'
|
||||
| 'searchFieldMetadatas'
|
||||
>
|
||||
>,
|
||||
];
|
||||
|
||||
+1
@@ -23,6 +23,7 @@ exports[`getMetadataRelatedMetadataNames should return related metadata names fo
|
||||
"viewField",
|
||||
"viewFilter",
|
||||
"view",
|
||||
"searchFieldMetadata",
|
||||
]
|
||||
`;
|
||||
|
||||
|
||||
+1
-1
@@ -7,11 +7,11 @@ exports[`sortMetadataNamesChildrenFirst should return metadata names sorted with
|
||||
"viewFilter",
|
||||
"commandMenuItem",
|
||||
"fieldPermission",
|
||||
"searchFieldMetadata",
|
||||
"viewField",
|
||||
"objectPermission",
|
||||
"pageLayoutWidget",
|
||||
"rolePermissionFlag",
|
||||
"searchFieldMetadata",
|
||||
"viewSort",
|
||||
"index",
|
||||
"pageLayout",
|
||||
|
||||
+15
@@ -12,6 +12,7 @@ import { type MetadataFlatEntity } from 'src/engine/metadata-modules/flat-entity
|
||||
import { type MetadataRelatedFlatEntityMapsKeys } from 'src/engine/metadata-modules/flat-entity/types/metadata-related-flat-entity-maps-keys.type';
|
||||
import { type MetadataFlatEntityAndRelatedFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/metadata-related-types.type';
|
||||
import { findFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps-or-throw.util';
|
||||
import { findFlatEntityByIdInFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps.util';
|
||||
import { getMetadataFlatEntityMapsKey } from 'src/engine/metadata-modules/flat-entity/utils/get-metadata-flat-entity-maps-key.util';
|
||||
import { addUniversalFlatEntityToUniversalFlatEntityAndRelatedEntityMapsThroughMutationOrThrow } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/utils/add-universal-flat-entity-to-universal-flat-entity-and-related-entity-maps-through-mutation-or-throw.util';
|
||||
import { replaceFlatEntityInFlatEntityMapsThroughMutationOrThrow } from 'src/engine/workspace-manager/workspace-migration/utils/replace-flat-entity-in-flat-entity-maps-through-mutation-or-throw.util';
|
||||
@@ -22,6 +23,7 @@ type AddFlatEntityToFlatEntityAndRelatedEntityMapsThroughMutationOrThrowArgs<
|
||||
metadataName: T;
|
||||
flatEntity: MetadataFlatEntity<T>;
|
||||
flatEntityAndRelatedMapsToMutate: MetadataFlatEntityAndRelatedFlatEntityMaps<T>;
|
||||
skipMissingRelatedEntities?: boolean;
|
||||
};
|
||||
|
||||
export const addFlatEntityToFlatEntityAndRelatedEntityMapsThroughMutationOrThrow =
|
||||
@@ -29,6 +31,7 @@ export const addFlatEntityToFlatEntityAndRelatedEntityMapsThroughMutationOrThrow
|
||||
metadataName,
|
||||
flatEntity,
|
||||
flatEntityAndRelatedMapsToMutate,
|
||||
skipMissingRelatedEntities = false,
|
||||
}: AddFlatEntityToFlatEntityAndRelatedEntityMapsThroughMutationOrThrowArgs<T>) => {
|
||||
addUniversalFlatEntityToUniversalFlatEntityAndRelatedEntityMapsThroughMutationOrThrow(
|
||||
{
|
||||
@@ -36,6 +39,7 @@ export const addFlatEntityToFlatEntityAndRelatedEntityMapsThroughMutationOrThrow
|
||||
universalFlatEntity: flatEntity,
|
||||
universalFlatEntityAndRelatedMapsToMutate:
|
||||
flatEntityAndRelatedMapsToMutate,
|
||||
skipMissingRelatedEntities,
|
||||
},
|
||||
);
|
||||
|
||||
@@ -101,6 +105,17 @@ export const addFlatEntityToFlatEntityAndRelatedEntityMapsThroughMutationOrThrow
|
||||
continue;
|
||||
}
|
||||
|
||||
if (skipMissingRelatedEntities) {
|
||||
const maybeRelatedFlatEntity = findFlatEntityByIdInFlatEntityMaps({
|
||||
flatEntityId: flatEntityRelatedEntityForeignKeyValue,
|
||||
flatEntityMaps: relatedFlatEntityMetadataMaps,
|
||||
});
|
||||
|
||||
if (!isDefined(maybeRelatedFlatEntity)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
const relatedFlatEntity = findFlatEntityByIdInFlatEntityMapsOrThrow({
|
||||
flatEntityId: flatEntityRelatedEntityForeignKeyValue,
|
||||
flatEntityMaps: relatedFlatEntityMetadataMaps,
|
||||
|
||||
+2
@@ -57,6 +57,8 @@ export const getFlatFieldMetadataMock = <T extends FieldMetadataType>(
|
||||
mainGroupByFieldMetadataViewUniversalIdentifiers: [],
|
||||
viewSortIds: [],
|
||||
viewSortUniversalIdentifiers: [],
|
||||
searchFieldMetadataIds: [],
|
||||
searchFieldMetadataUniversalIdentifiers: [],
|
||||
universalSettings: null,
|
||||
...overrides,
|
||||
};
|
||||
|
||||
+2
@@ -77,6 +77,8 @@ export const getRelationTargetFlatFieldMetadataMock = ({
|
||||
mainGroupByFieldMetadataViewUniversalIdentifiers: [],
|
||||
viewSortIds: [],
|
||||
viewSortUniversalIdentifiers: [],
|
||||
searchFieldMetadataIds: [],
|
||||
searchFieldMetadataUniversalIdentifiers: [],
|
||||
universalSettings: settings,
|
||||
};
|
||||
};
|
||||
|
||||
+4
@@ -139,6 +139,7 @@ exports[`fromCreateFieldInputToFlatFieldMetadatasToCreate MORPH_RELATION test su
|
||||
"options": null,
|
||||
"relationTargetFieldMetadataUniversalIdentifier": Any<String>,
|
||||
"relationTargetObjectMetadataUniversalIdentifier": Any<String>,
|
||||
"searchFieldMetadataUniversalIdentifiers": [],
|
||||
"standardOverrides": null,
|
||||
"type": "MORPH_RELATION",
|
||||
"universalIdentifier": Any<String>,
|
||||
@@ -174,6 +175,7 @@ exports[`fromCreateFieldInputToFlatFieldMetadatasToCreate MORPH_RELATION test su
|
||||
"options": null,
|
||||
"relationTargetFieldMetadataUniversalIdentifier": Any<String>,
|
||||
"relationTargetObjectMetadataUniversalIdentifier": Any<String>,
|
||||
"searchFieldMetadataUniversalIdentifiers": [],
|
||||
"standardOverrides": null,
|
||||
"type": "RELATION",
|
||||
"universalIdentifier": Any<String>,
|
||||
@@ -211,6 +213,7 @@ exports[`fromCreateFieldInputToFlatFieldMetadatasToCreate MORPH_RELATION test su
|
||||
"options": null,
|
||||
"relationTargetFieldMetadataUniversalIdentifier": Any<String>,
|
||||
"relationTargetObjectMetadataUniversalIdentifier": Any<String>,
|
||||
"searchFieldMetadataUniversalIdentifiers": [],
|
||||
"standardOverrides": null,
|
||||
"type": "MORPH_RELATION",
|
||||
"universalIdentifier": Any<String>,
|
||||
@@ -246,6 +249,7 @@ exports[`fromCreateFieldInputToFlatFieldMetadatasToCreate MORPH_RELATION test su
|
||||
"options": null,
|
||||
"relationTargetFieldMetadataUniversalIdentifier": Any<String>,
|
||||
"relationTargetObjectMetadataUniversalIdentifier": Any<String>,
|
||||
"searchFieldMetadataUniversalIdentifiers": [],
|
||||
"standardOverrides": null,
|
||||
"type": "RELATION",
|
||||
"universalIdentifier": Any<String>,
|
||||
|
||||
+14
@@ -14,6 +14,7 @@ import { fromFieldMetadataEntityToFlatFieldMetadata } from 'src/engine/metadata-
|
||||
import { IndexMetadataEntity } from 'src/engine/metadata-modules/index-metadata/index-metadata.entity';
|
||||
import { computeUniqueFieldMetadataIdsFromIndexEntities } from 'src/engine/metadata-modules/index-metadata/utils/compute-unique-field-metadata-ids-from-index-entities.util';
|
||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
import { SearchFieldMetadataEntity } from 'src/engine/metadata-modules/search-field-metadata/search-field-metadata.entity';
|
||||
import { ViewFieldEntity } from 'src/engine/metadata-modules/view-field/entities/view-field.entity';
|
||||
import { ViewFilterEntity } from 'src/engine/metadata-modules/view-filter/entities/view-filter.entity';
|
||||
import { ViewGroupEntity } from 'src/engine/metadata-modules/view-group/entities/view-group.entity';
|
||||
@@ -50,6 +51,8 @@ export class WorkspaceFlatFieldMetadataMapCacheService extends WorkspaceCachePro
|
||||
private readonly viewSortRepository: WorkspaceScopedRepository<ViewSortEntity>,
|
||||
@InjectWorkspaceScopedRepository(ViewEntity)
|
||||
private readonly viewRepository: WorkspaceScopedRepository<ViewEntity>,
|
||||
@InjectWorkspaceScopedRepository(SearchFieldMetadataEntity)
|
||||
private readonly searchFieldMetadataRepository: WorkspaceScopedRepository<SearchFieldMetadataEntity>,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
@@ -66,6 +69,7 @@ export class WorkspaceFlatFieldMetadataMapCacheService extends WorkspaceCachePro
|
||||
viewFilters,
|
||||
viewSorts,
|
||||
views,
|
||||
searchFieldMetadatas,
|
||||
] = await Promise.all([
|
||||
this.fieldMetadataRepository.find({
|
||||
where: { workspaceId },
|
||||
@@ -108,6 +112,9 @@ export class WorkspaceFlatFieldMetadataMapCacheService extends WorkspaceCachePro
|
||||
],
|
||||
withDeleted: true,
|
||||
}),
|
||||
this.searchFieldMetadataRepository.find(workspaceId, {
|
||||
select: ['id', 'universalIdentifier', 'fieldMetadataId'],
|
||||
}),
|
||||
]);
|
||||
|
||||
const [
|
||||
@@ -117,6 +124,7 @@ export class WorkspaceFlatFieldMetadataMapCacheService extends WorkspaceCachePro
|
||||
kanbanViewsByFieldId,
|
||||
mainGroupByFieldMetadataViewsByFieldId,
|
||||
viewSortsByFieldId,
|
||||
searchFieldMetadatasByFieldId,
|
||||
] = (
|
||||
[
|
||||
{
|
||||
@@ -143,6 +151,10 @@ export class WorkspaceFlatFieldMetadataMapCacheService extends WorkspaceCachePro
|
||||
entities: viewSorts,
|
||||
foreignKey: 'fieldMetadataId',
|
||||
},
|
||||
{
|
||||
entities: searchFieldMetadatas,
|
||||
foreignKey: 'fieldMetadataId',
|
||||
},
|
||||
] as const
|
||||
).map(regroupEntitiesByRelatedEntityId);
|
||||
|
||||
@@ -173,6 +185,8 @@ export class WorkspaceFlatFieldMetadataMapCacheService extends WorkspaceCachePro
|
||||
fieldMetadataEntity.id,
|
||||
) || [],
|
||||
viewSorts: viewSortsByFieldId.get(fieldMetadataEntity.id) || [],
|
||||
searchFieldMetadatas:
|
||||
searchFieldMetadatasByFieldId.get(fieldMetadataEntity.id) || [],
|
||||
},
|
||||
fieldMetadataIdToUniversalIdentifierMap,
|
||||
objectMetadataIdToUniversalIdentifierMap,
|
||||
|
||||
+1
-30
@@ -1,29 +1,23 @@
|
||||
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'
|
||||
'flatObjectMetadataMaps' | 'flatSearchFieldMetadataMaps'
|
||||
>): {
|
||||
searchFieldMetadatasToDelete: UniversalFlatSearchFieldMetadata[];
|
||||
flatSearchVectorFieldsToUpdate: FlatFieldMetadata<FieldMetadataType.TS_VECTOR>[];
|
||||
} => {
|
||||
const deletedFieldIds = new Set(
|
||||
flatFieldMetadatasToDelete.map((flatFieldMetadata) => flatFieldMetadata.id),
|
||||
@@ -35,8 +29,6 @@ export const computeSearchFieldMetadataDeletionForDeletedFields = ({
|
||||
);
|
||||
|
||||
const searchFieldMetadatasToDelete: UniversalFlatSearchFieldMetadata[] = [];
|
||||
const flatSearchVectorFieldsToUpdate: FlatFieldMetadata<FieldMetadataType.TS_VECTOR>[] =
|
||||
[];
|
||||
|
||||
for (const objectMetadataId of affectedObjectMetadataIds) {
|
||||
const flatObjectMetadata = findFlatEntityByIdInFlatEntityMaps({
|
||||
@@ -59,31 +51,10 @@ export const computeSearchFieldMetadataDeletionForDeletedFields = ({
|
||||
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
-6
@@ -1,4 +1,3 @@
|
||||
import { type FieldMetadataType } from 'twenty-shared/types';
|
||||
import {
|
||||
isDefined,
|
||||
trimAndRemoveDuplicatedWhitespacesFromObjectStringProperties,
|
||||
@@ -13,7 +12,6 @@ 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';
|
||||
@@ -44,7 +42,6 @@ export const fromDeleteFieldInputToFlatFieldMetadatasToDelete = ({
|
||||
flatIndexesToUpdate: UniversalFlatIndexMetadata[];
|
||||
flatIndexesToDelete: UniversalFlatIndexMetadata[];
|
||||
searchFieldMetadatasToDelete: UniversalFlatSearchFieldMetadata[];
|
||||
flatSearchVectorFieldsToUpdate: FlatFieldMetadata<FieldMetadataType.TS_VECTOR>[];
|
||||
} => {
|
||||
const { id: fieldMetadataToDeleteId } =
|
||||
trimAndRemoveDuplicatedWhitespacesFromObjectStringProperties(
|
||||
@@ -187,11 +184,10 @@ export const fromDeleteFieldInputToFlatFieldMetadatasToDelete = ({
|
||||
},
|
||||
);
|
||||
|
||||
const { searchFieldMetadatasToDelete, flatSearchVectorFieldsToUpdate } =
|
||||
const { searchFieldMetadatasToDelete } =
|
||||
computeSearchFieldMetadataDeletionForDeletedFields({
|
||||
flatFieldMetadatasToDelete,
|
||||
flatObjectMetadataMaps: existingFlatObjectMetadataMaps,
|
||||
flatFieldMetadataMaps: existingFlatFieldMetadataMaps,
|
||||
flatSearchFieldMetadataMaps: existingFlatSearchFieldMetadataMaps,
|
||||
});
|
||||
|
||||
@@ -200,6 +196,5 @@ export const fromDeleteFieldInputToFlatFieldMetadatasToDelete = ({
|
||||
flatIndexesToDelete,
|
||||
flatIndexesToUpdate,
|
||||
searchFieldMetadatasToDelete,
|
||||
flatSearchVectorFieldsToUpdate,
|
||||
};
|
||||
};
|
||||
|
||||
+6
@@ -78,6 +78,12 @@ export const fromFieldMetadataEntityToFlatFieldMetadata = (
|
||||
fieldMetadataEntity.viewSorts?.map(
|
||||
({ universalIdentifier }) => universalIdentifier,
|
||||
) ?? [],
|
||||
searchFieldMetadataIds:
|
||||
fieldMetadataEntity.searchFieldMetadatas?.map(({ id }) => id) ?? [],
|
||||
searchFieldMetadataUniversalIdentifiers:
|
||||
fieldMetadataEntity.searchFieldMetadatas?.map(
|
||||
({ universalIdentifier }) => universalIdentifier,
|
||||
) ?? [],
|
||||
fieldPermissionUniversalIdentifiers:
|
||||
fieldMetadataEntity.fieldPermissions?.map(
|
||||
({ universalIdentifier }) => universalIdentifier,
|
||||
|
||||
-5
@@ -35,7 +35,6 @@ type FromUpdateFieldInputToFlatFieldMetadataArgs = {
|
||||
| 'flatViewGroupMaps'
|
||||
| 'flatViewMaps'
|
||||
| 'flatViewFieldMaps'
|
||||
| 'flatSearchFieldMetadataMaps'
|
||||
>;
|
||||
|
||||
type FlatFieldMetadataAndIndexToUpdate = {
|
||||
@@ -52,7 +51,6 @@ export const fromUpdateFieldInputToFlatFieldMetadata = ({
|
||||
flatViewGroupMaps,
|
||||
flatViewMaps,
|
||||
flatViewFieldMaps,
|
||||
flatSearchFieldMetadataMaps,
|
||||
isSystemBuild,
|
||||
}: FromUpdateFieldInputToFlatFieldMetadataArgs): FieldInputTranspilationResult<FlatFieldMetadataAndIndexToUpdate> => {
|
||||
const updateFieldInputInformalProperties =
|
||||
@@ -152,7 +150,6 @@ export const fromUpdateFieldInputToFlatFieldMetadata = ({
|
||||
toFlatFieldMetadata,
|
||||
flatViewMaps,
|
||||
flatViewFieldMaps,
|
||||
flatSearchFieldMetadataMaps,
|
||||
flatApplication,
|
||||
});
|
||||
|
||||
@@ -175,14 +172,12 @@ export const fromUpdateFieldInputToFlatFieldMetadata = ({
|
||||
flatViewsToDelete,
|
||||
flatViewFieldsToDelete,
|
||||
flatViewsToUpdate,
|
||||
flatFieldMetadatasToUpdate: flatFieldMetadatasToUpdateFromSideEffect,
|
||||
} = sideEffectResult.result;
|
||||
|
||||
return {
|
||||
flatFieldMetadatasToUpdate: [
|
||||
...accumulator.flatFieldMetadatasToUpdate,
|
||||
toFlatFieldMetadata,
|
||||
...flatFieldMetadatasToUpdateFromSideEffect,
|
||||
],
|
||||
flatIndexMetadatasToUpdate: [
|
||||
...accumulator.flatIndexMetadatasToUpdate,
|
||||
|
||||
+1
@@ -77,5 +77,6 @@ export const getDefaultFlatFieldMetadata = ({
|
||||
mainGroupByFieldMetadataViewUniversalIdentifiers: [],
|
||||
universalSettings: settings ?? null,
|
||||
viewSortUniversalIdentifiers: [],
|
||||
searchFieldMetadataUniversalIdentifiers: [],
|
||||
} as const satisfies UniversalFlatFieldMetadata;
|
||||
};
|
||||
|
||||
+1
-28
@@ -1,9 +1,7 @@
|
||||
import { type FromTo } from 'twenty-shared/types';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { type FlatApplication } from 'src/engine/core-modules/application/types/flat-application.type';
|
||||
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';
|
||||
@@ -16,7 +14,6 @@ import {
|
||||
handleIndexChangesDuringFieldUpdate,
|
||||
} from 'src/engine/metadata-modules/flat-field-metadata/utils/handle-index-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';
|
||||
|
||||
@@ -24,9 +21,7 @@ export type FlatFieldMetadataUpdateSideEffects =
|
||||
FlatViewFiltersToDeleteAndUpdate &
|
||||
FlatViewGroupsToDeleteUpdateAndCreate &
|
||||
FieldMetadataUpdateIndexSideEffect &
|
||||
FieldMetadataDeactivationSideEffect & {
|
||||
flatFieldMetadatasToUpdate: FlatFieldMetadata[];
|
||||
};
|
||||
FieldMetadataDeactivationSideEffect;
|
||||
|
||||
type HandleFlatFieldMetadataUpdateSideEffectArgs = FromTo<
|
||||
FlatFieldMetadata,
|
||||
@@ -41,7 +36,6 @@ type HandleFlatFieldMetadataUpdateSideEffectArgs = FromTo<
|
||||
| 'flatViewGroupMaps'
|
||||
| 'flatViewMaps'
|
||||
| 'flatViewFieldMaps'
|
||||
| 'flatSearchFieldMetadataMaps'
|
||||
> & {
|
||||
flatApplication: FlatApplication;
|
||||
};
|
||||
@@ -59,7 +53,6 @@ export const FLAT_FIELD_METADATA_UPDATE_EMPTY_SIDE_EFFECTS: FlatFieldMetadataUpd
|
||||
flatViewsToDelete: [],
|
||||
flatViewFieldsToDelete: [],
|
||||
flatViewsToUpdate: [],
|
||||
flatFieldMetadatasToUpdate: [],
|
||||
};
|
||||
|
||||
export const handleFlatFieldMetadataUpdateSideEffect = ({
|
||||
@@ -72,7 +65,6 @@ export const handleFlatFieldMetadataUpdateSideEffect = ({
|
||||
flatViewGroupMaps,
|
||||
flatViewMaps,
|
||||
flatViewFieldMaps,
|
||||
flatSearchFieldMetadataMaps,
|
||||
flatApplication,
|
||||
}: HandleFlatFieldMetadataUpdateSideEffectArgs): FieldInputTranspilationResult<FlatFieldMetadataUpdateSideEffects> => {
|
||||
const sideEffectResult = structuredClone(
|
||||
@@ -140,25 +132,6 @@ export const handleFlatFieldMetadataUpdateSideEffect = ({
|
||||
return indexChangesSideEffectResult;
|
||||
}
|
||||
|
||||
const flatObjectMetadata = findFlatEntityByIdInFlatEntityMapsOrThrow({
|
||||
flatEntityMaps: flatObjectMetadataMaps,
|
||||
flatEntityId: fromFlatFieldMetadata.objectMetadataId,
|
||||
});
|
||||
|
||||
const { flatSearchVectorFieldToUpdate } = recomputeSearchVectorOnFieldRename({
|
||||
fromFlatFieldMetadata,
|
||||
toFlatFieldMetadata,
|
||||
flatObjectMetadata,
|
||||
flatFieldMetadataMaps,
|
||||
flatSearchFieldMetadataMaps,
|
||||
});
|
||||
|
||||
if (isDefined(flatSearchVectorFieldToUpdate)) {
|
||||
sideEffectResult.flatFieldMetadatasToUpdate.push(
|
||||
flatSearchVectorFieldToUpdate,
|
||||
);
|
||||
}
|
||||
|
||||
const {
|
||||
flatIndexMetadatasToUpdate,
|
||||
flatIndexMetadatasToCreate,
|
||||
|
||||
-68
@@ -1,68 +0,0 @@
|
||||
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],
|
||||
]),
|
||||
}),
|
||||
};
|
||||
};
|
||||
-19
@@ -1,11 +1,9 @@
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { isNonEmptyString } from '@sniptt/guards';
|
||||
import { type FieldMetadataType } from 'twenty-shared/types';
|
||||
|
||||
import { FieldMetadataExceptionCode } from 'src/engine/metadata-modules/field-metadata/field-metadata.exception';
|
||||
import { type FlatFieldMetadataTypeValidationArgs } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata-type-validator.type';
|
||||
import { type FlatFieldMetadataValidationError } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata-validation-error.type';
|
||||
import { isSafeTsVectorExpression } from 'src/engine/workspace-manager/workspace-migration/utils/remove-sql-injection.util';
|
||||
|
||||
export const validateTsVectorFlatFieldMetadata = ({
|
||||
flatEntityToValidate,
|
||||
@@ -30,22 +28,5 @@ export const validateTsVectorFlatFieldMetadata = ({
|
||||
});
|
||||
}
|
||||
|
||||
const asExpression = flatEntityToValidate.universalSettings?.asExpression;
|
||||
|
||||
if (!isNonEmptyString(asExpression)) {
|
||||
errors.push({
|
||||
code: FieldMetadataExceptionCode.INVALID_FIELD_INPUT,
|
||||
message:
|
||||
'Field type TS_VECTOR must have an expression. This may have failed to be built because record identifier field does not exist or is not of a searchable type.',
|
||||
userFriendlyMessage: msg`Field type TS_VECTOR must have an expression. This may have failed to be built because record identifier field does not exist or is not of a searchable type.`,
|
||||
});
|
||||
} else if (!isSafeTsVectorExpression(asExpression)) {
|
||||
errors.push({
|
||||
code: FieldMetadataExceptionCode.INVALID_FIELD_INPUT,
|
||||
message: 'Field type TS_VECTOR expression is invalid',
|
||||
userFriendlyMessage: msg`The search field expression is invalid.`,
|
||||
});
|
||||
}
|
||||
|
||||
return errors;
|
||||
};
|
||||
|
||||
+19
-29
@@ -1,24 +1,20 @@
|
||||
import { type FieldMetadataType, type FromTo } from 'twenty-shared/types';
|
||||
import { type FromTo } from 'twenty-shared/types';
|
||||
import { isDefined, isSearchableFieldType } 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 { 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';
|
||||
import { buildFlatSearchFieldMetadataForField } from 'src/engine/metadata-modules/flat-search-field-metadata/utils/build-flat-search-field-metadata-for-field.util';
|
||||
import { findTsVectorFlatFieldMetadataForObject } from 'src/engine/metadata-modules/flat-search-field-metadata/utils/find-ts-vector-flat-field-metadata-for-object.util';
|
||||
import { type UniversalFlatSearchFieldMetadata } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-search-field-metadata.type';
|
||||
|
||||
export type LabelIdentifierUpdateSearchVectorSideEffect = {
|
||||
flatSearchVectorFieldToUpdate:
|
||||
| FlatFieldMetadata<FieldMetadataType.TS_VECTOR>
|
||||
| undefined;
|
||||
export type LabelIdentifierUpdateSearchFieldMetadataSideEffect = {
|
||||
searchFieldMetadatasToCreate: UniversalFlatSearchFieldMetadata[];
|
||||
};
|
||||
|
||||
type RecomputeSearchVectorOnLabelIdentifierUpdateArgs = FromTo<
|
||||
type ComputeSearchFieldMetadataToCreateOnLabelIdentifierUpdateArgs = FromTo<
|
||||
FlatObjectMetadata,
|
||||
'flatObjectMetadata'
|
||||
> &
|
||||
@@ -27,21 +23,20 @@ type RecomputeSearchVectorOnLabelIdentifierUpdateArgs = FromTo<
|
||||
'flatFieldMetadataMaps' | 'flatSearchFieldMetadataMaps'
|
||||
>;
|
||||
|
||||
const EMPTY_SIDE_EFFECT: LabelIdentifierUpdateSearchVectorSideEffect = {
|
||||
flatSearchVectorFieldToUpdate: undefined,
|
||||
const EMPTY_SIDE_EFFECT: LabelIdentifierUpdateSearchFieldMetadataSideEffect = {
|
||||
searchFieldMetadatasToCreate: [],
|
||||
};
|
||||
|
||||
// Relabeling is additive: it indexes the new label identifier field while preserving the
|
||||
// existing search surface (e.g. the provisioned name row). We never drop a row here so the
|
||||
// previous label identifier stays searchable. Removal of fields from the search surface is
|
||||
// deferred to a follow-up PR.
|
||||
export const recomputeSearchVectorOnLabelIdentifierUpdate = ({
|
||||
// previous label identifier stays searchable. The searchVector column itself is rebuilt
|
||||
// from the resulting rows by the rebuildSearchVector marker in the migration runner.
|
||||
export const computeSearchFieldMetadataToCreateOnLabelIdentifierUpdate = ({
|
||||
fromFlatObjectMetadata,
|
||||
toFlatObjectMetadata,
|
||||
flatFieldMetadataMaps,
|
||||
flatSearchFieldMetadataMaps,
|
||||
}: RecomputeSearchVectorOnLabelIdentifierUpdateArgs): LabelIdentifierUpdateSearchVectorSideEffect => {
|
||||
}: ComputeSearchFieldMetadataToCreateOnLabelIdentifierUpdateArgs): LabelIdentifierUpdateSearchFieldMetadataSideEffect => {
|
||||
const newLabelIdentifierFieldMetadataId =
|
||||
toFlatObjectMetadata.labelIdentifierFieldMetadataId;
|
||||
|
||||
@@ -79,6 +74,15 @@ export const recomputeSearchVectorOnLabelIdentifierUpdate = ({
|
||||
return EMPTY_SIDE_EFFECT;
|
||||
}
|
||||
|
||||
const tsVectorFlatFieldMetadata = findTsVectorFlatFieldMetadataForObject({
|
||||
fieldUniversalIdentifiers: fromFlatObjectMetadata.fieldUniversalIdentifiers,
|
||||
flatFieldMetadataMaps,
|
||||
});
|
||||
|
||||
if (!isDefined(tsVectorFlatFieldMetadata)) {
|
||||
return EMPTY_SIDE_EFFECT;
|
||||
}
|
||||
|
||||
const newLabelIdentifierPosition =
|
||||
existingSearchFieldMetadatas.reduce(
|
||||
(maxPosition, searchFieldMetadata) =>
|
||||
@@ -89,25 +93,11 @@ export const recomputeSearchVectorOnLabelIdentifierUpdate = ({
|
||||
const newSearchFieldMetadata = buildFlatSearchFieldMetadataForField({
|
||||
flatObjectMetadata: fromFlatObjectMetadata,
|
||||
flatFieldMetadata: newLabelIdentifierField,
|
||||
tsVectorFlatFieldMetadata,
|
||||
position: newLabelIdentifierPosition,
|
||||
});
|
||||
|
||||
const nextSearchFieldMetadatas = [
|
||||
...existingSearchFieldMetadatas,
|
||||
{
|
||||
fieldMetadataId: newLabelIdentifierFieldMetadataId,
|
||||
position: newLabelIdentifierPosition,
|
||||
universalIdentifier: newSearchFieldMetadata.universalIdentifier,
|
||||
},
|
||||
];
|
||||
|
||||
return {
|
||||
flatSearchVectorFieldToUpdate:
|
||||
recomputeSearchVectorFieldFromSearchFieldMetadatas({
|
||||
flatObjectMetadata: fromFlatObjectMetadata,
|
||||
flatFieldMetadataMaps,
|
||||
searchFieldMetadatas: nextSearchFieldMetadatas,
|
||||
}),
|
||||
searchFieldMetadatasToCreate: [newSearchFieldMetadata],
|
||||
};
|
||||
};
|
||||
-2
@@ -102,7 +102,6 @@ export const fromUpdateObjectInputToFlatObjectMetadataAndRelatedFlatEntities =
|
||||
flatViewFieldsToCreate,
|
||||
flatViewFieldsToUpdate,
|
||||
otherObjectFlatFieldMetadatasToUpdate,
|
||||
sameObjectFlatFieldMetadatasToUpdate,
|
||||
searchFieldMetadatasToCreate,
|
||||
} = handleFlatObjectMetadataUpdateSideEffect({
|
||||
fromFlatObjectMetadata: existingFlatObjectMetadata,
|
||||
@@ -121,7 +120,6 @@ export const fromUpdateObjectInputToFlatObjectMetadataAndRelatedFlatEntities =
|
||||
flatViewFieldsToCreate,
|
||||
flatViewFieldsToUpdate,
|
||||
otherObjectFlatFieldMetadatasToUpdate,
|
||||
sameObjectFlatFieldMetadatasToUpdate,
|
||||
searchFieldMetadatasToCreate,
|
||||
};
|
||||
};
|
||||
|
||||
+3
-12
@@ -2,10 +2,9 @@ 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 { 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 { computeSearchFieldMetadataToCreateOnLabelIdentifierUpdate } from 'src/engine/metadata-modules/flat-object-metadata/utils/compute-search-field-metadata-to-create-on-label-identifier-update.util';
|
||||
import { recomputeIndexAfterFlatObjectMetadataSingularNameUpdate } from 'src/engine/metadata-modules/flat-object-metadata/utils/recompute-index-after-flat-object-metadata-singular-name-update.util';
|
||||
import { recomputeSearchVectorOnLabelIdentifierUpdate } from 'src/engine/metadata-modules/flat-object-metadata/utils/recompute-search-vector-on-label-identifier-update.util';
|
||||
import { recomputeViewFieldIdentifierAfterFlatObjectIdentifierUpdate } from 'src/engine/metadata-modules/flat-object-metadata/utils/recompute-view-field-identifier-after-flat-object-identifier-update.util';
|
||||
import { renameRelatedMorphFieldOnObjectNamesUpdate } from 'src/engine/metadata-modules/flat-object-metadata/utils/rename-related-morph-field-on-object-names-update.util';
|
||||
import { type UniversalFlatFieldMetadata } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-field-metadata.type';
|
||||
@@ -15,7 +14,6 @@ import { type UniversalFlatViewField } from 'src/engine/workspace-manager/worksp
|
||||
|
||||
export type FlatObjectMetadataUpdateSideEffects = {
|
||||
otherObjectFlatFieldMetadatasToUpdate: UniversalFlatFieldMetadata[];
|
||||
sameObjectFlatFieldMetadatasToUpdate: UniversalFlatFieldMetadata[];
|
||||
flatViewFieldsToUpdate: UniversalFlatViewField[];
|
||||
flatViewFieldsToCreate: UniversalFlatViewField[];
|
||||
flatIndexMetadatasToUpdate: UniversalFlatIndexMetadata[];
|
||||
@@ -89,20 +87,14 @@ export const handleFlatObjectMetadataUpdateSideEffect = ({
|
||||
flatViewFieldsToUpdate: [],
|
||||
};
|
||||
|
||||
const sameObjectFlatFieldMetadatasToUpdate: FlatFieldMetadata[] = [];
|
||||
|
||||
const { flatSearchVectorFieldToUpdate, searchFieldMetadatasToCreate } =
|
||||
recomputeSearchVectorOnLabelIdentifierUpdate({
|
||||
const { searchFieldMetadatasToCreate } =
|
||||
computeSearchFieldMetadataToCreateOnLabelIdentifierUpdate({
|
||||
fromFlatObjectMetadata,
|
||||
toFlatObjectMetadata,
|
||||
flatFieldMetadataMaps,
|
||||
flatSearchFieldMetadataMaps,
|
||||
});
|
||||
|
||||
if (isDefined(flatSearchVectorFieldToUpdate)) {
|
||||
sameObjectFlatFieldMetadatasToUpdate.push(flatSearchVectorFieldToUpdate);
|
||||
}
|
||||
|
||||
return {
|
||||
flatIndexMetadatasToUpdate: [
|
||||
...morphRelatedFlatIndexesToUpdate,
|
||||
@@ -111,7 +103,6 @@ export const handleFlatObjectMetadataUpdateSideEffect = ({
|
||||
flatViewFieldsToCreate,
|
||||
flatViewFieldsToUpdate,
|
||||
otherObjectFlatFieldMetadatasToUpdate: morphFlatFieldMetadatasToUpdate,
|
||||
sameObjectFlatFieldMetadatasToUpdate,
|
||||
searchFieldMetadatasToCreate,
|
||||
};
|
||||
};
|
||||
|
||||
-112
@@ -1,112 +0,0 @@
|
||||
import { 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 { isFlatFieldMetadataOfType } from 'src/engine/metadata-modules/flat-field-metadata/utils/is-flat-field-metadata-of-type.util';
|
||||
import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
|
||||
import { type FlatSearchFieldMetadata } from 'src/engine/metadata-modules/flat-search-field-metadata/types/flat-search-field-metadata.type';
|
||||
import {
|
||||
ObjectMetadataException,
|
||||
ObjectMetadataExceptionCode,
|
||||
} from 'src/engine/metadata-modules/object-metadata/object-metadata.exception';
|
||||
import {
|
||||
buildSearchVectorTargetField,
|
||||
computeSearchVectorAsExpressionFromSearchFieldMetadatas,
|
||||
} from 'src/engine/metadata-modules/flat-search-field-metadata/utils/compute-search-vector-as-expression-from-search-field-metadatas.util';
|
||||
import { SEARCH_VECTOR_FIELD } from 'src/engine/metadata-modules/search-field-metadata/constants/search-vector-field.constants';
|
||||
|
||||
type RecomputeSearchVectorFieldFromSearchFieldMetadatasArgs = {
|
||||
flatObjectMetadata: FlatObjectMetadata;
|
||||
// Post-change searchFieldMetadata rows for the object, carrying the field id and the
|
||||
// per-object position that drives the deterministic asExpression order.
|
||||
searchFieldMetadatas: Pick<
|
||||
FlatSearchFieldMetadata,
|
||||
'fieldMetadataId' | 'position' | 'universalIdentifier'
|
||||
>[];
|
||||
// Takes precedence over the flat maps when resolving an id — for a field created or
|
||||
// renamed in the same migration, whose maps entry is absent or stale.
|
||||
overrideFlatFieldMetadataById?: Map<string, FlatFieldMetadata>;
|
||||
} & Pick<AllFlatEntityMaps, 'flatFieldMetadataMaps'>;
|
||||
|
||||
export const recomputeSearchVectorFieldFromSearchFieldMetadatas = ({
|
||||
flatObjectMetadata,
|
||||
flatFieldMetadataMaps,
|
||||
searchFieldMetadatas,
|
||||
overrideFlatFieldMetadataById,
|
||||
}: RecomputeSearchVectorFieldFromSearchFieldMetadatasArgs):
|
||||
| FlatFieldMetadata<FieldMetadataType.TS_VECTOR>
|
||||
| undefined => {
|
||||
const objectFlatFieldMetadatas =
|
||||
findManyFlatEntityByIdInFlatEntityMapsOrThrow({
|
||||
flatEntityMaps: flatFieldMetadataMaps,
|
||||
flatEntityIds: flatObjectMetadata.fieldIds,
|
||||
});
|
||||
|
||||
const searchVectorField = objectFlatFieldMetadatas.find(
|
||||
(field) => field.name === SEARCH_VECTOR_FIELD.name,
|
||||
);
|
||||
|
||||
if (
|
||||
!isDefined(searchVectorField) ||
|
||||
!isFlatFieldMetadataOfType(searchVectorField, FieldMetadataType.TS_VECTOR)
|
||||
) {
|
||||
throw new ObjectMetadataException(
|
||||
`Search vector field not found for object metadata ${flatObjectMetadata.id}`,
|
||||
ObjectMetadataExceptionCode.INVALID_OBJECT_INPUT,
|
||||
);
|
||||
}
|
||||
|
||||
const targetSearchableFields = searchFieldMetadatas.map(
|
||||
(searchFieldMetadata) => {
|
||||
const { fieldMetadataId, position, universalIdentifier } =
|
||||
searchFieldMetadata;
|
||||
|
||||
const overriddenFlatFieldMetadata =
|
||||
overrideFlatFieldMetadataById?.get(fieldMetadataId);
|
||||
|
||||
const flatFieldMetadata =
|
||||
overriddenFlatFieldMetadata ??
|
||||
findFlatEntityByIdInFlatEntityMaps({
|
||||
flatEntityId: fieldMetadataId,
|
||||
flatEntityMaps: flatFieldMetadataMaps,
|
||||
});
|
||||
|
||||
if (!isDefined(flatFieldMetadata)) {
|
||||
throw new ObjectMetadataException(
|
||||
`Field metadata ${fieldMetadataId} referenced by searchFieldMetadata not found for object metadata ${flatObjectMetadata.id}`,
|
||||
ObjectMetadataExceptionCode.INVALID_OBJECT_INPUT,
|
||||
);
|
||||
}
|
||||
|
||||
return buildSearchVectorTargetField(
|
||||
flatFieldMetadata,
|
||||
position,
|
||||
universalIdentifier,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
try {
|
||||
const newAsExpression =
|
||||
computeSearchVectorAsExpressionFromSearchFieldMetadatas(
|
||||
targetSearchableFields,
|
||||
);
|
||||
|
||||
return {
|
||||
...searchVectorField,
|
||||
universalSettings: {
|
||||
...searchVectorField.universalSettings,
|
||||
asExpression: newAsExpression,
|
||||
generatedType: 'STORED',
|
||||
},
|
||||
};
|
||||
} catch (error) {
|
||||
throw new ObjectMetadataException(
|
||||
`Failed to compute search vector column expression for object metadata ${flatObjectMetadata.id}: ${error instanceof Error ? error.message : String(error)}`,
|
||||
ObjectMetadataExceptionCode.INVALID_OBJECT_INPUT,
|
||||
);
|
||||
}
|
||||
};
|
||||
+41
-2
@@ -1,6 +1,8 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { FieldMetadataType } from 'twenty-shared/types';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { Repository } from 'typeorm';
|
||||
|
||||
import { WorkspaceCacheProvider } from 'src/engine/workspace-cache/interfaces/workspace-cache-provider.service';
|
||||
@@ -11,6 +13,7 @@ import { createEmptyFlatEntityMaps } from 'src/engine/metadata-modules/flat-enti
|
||||
import { FlatSearchFieldMetadataMaps } from 'src/engine/metadata-modules/flat-search-field-metadata/types/flat-search-field-metadata-maps.type';
|
||||
import { fromSearchFieldMetadataEntityToFlatSearchFieldMetadata } from 'src/engine/metadata-modules/flat-search-field-metadata/utils/from-search-field-metadata-entity-to-flat-search-field-metadata.util';
|
||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
import { SEARCH_VECTOR_FIELD } from 'src/engine/metadata-modules/search-field-metadata/constants/search-vector-field.constants';
|
||||
import { SearchFieldMetadataEntity } from 'src/engine/metadata-modules/search-field-metadata/search-field-metadata.entity';
|
||||
import { InjectWorkspaceScopedRepository } from 'src/engine/twenty-orm/workspace-scoped-repository/inject-workspace-scoped-repository.decorator';
|
||||
import { WorkspaceScopedRepository } from 'src/engine/twenty-orm/workspace-scoped-repository/workspace-scoped-repository';
|
||||
@@ -56,7 +59,13 @@ export class WorkspaceFlatSearchFieldMetadataMapCacheService extends WorkspaceCa
|
||||
}),
|
||||
this.fieldMetadataRepository.find({
|
||||
where: { workspaceId },
|
||||
select: ['id', 'universalIdentifier'],
|
||||
select: [
|
||||
'id',
|
||||
'universalIdentifier',
|
||||
'name',
|
||||
'type',
|
||||
'objectMetadataId',
|
||||
],
|
||||
withDeleted: true,
|
||||
}),
|
||||
]);
|
||||
@@ -68,12 +77,42 @@ export class WorkspaceFlatSearchFieldMetadataMapCacheService extends WorkspaceCa
|
||||
const fieldMetadataIdToUniversalIdentifierMap =
|
||||
createIdToUniversalIdentifierMap(fieldMetadatas);
|
||||
|
||||
// Each object's single system TS_VECTOR field, used to backfill the FK for
|
||||
// legacy searchFieldMetadata rows still NULL before the 2.18 slow command runs.
|
||||
// TODO: remove this fallback (and the searchVector map) once the minimum
|
||||
// cross-upgrade supported version is past 2.18 — at which point no instance can
|
||||
// still have NULL tsVectorFieldMetadataId rows.
|
||||
const searchVectorFieldIdByObjectMetadataId = new Map<string, string>();
|
||||
|
||||
for (const fieldMetadata of fieldMetadatas) {
|
||||
if (
|
||||
fieldMetadata.type === FieldMetadataType.TS_VECTOR &&
|
||||
fieldMetadata.name === SEARCH_VECTOR_FIELD.name
|
||||
) {
|
||||
searchVectorFieldIdByObjectMetadataId.set(
|
||||
fieldMetadata.objectMetadataId,
|
||||
fieldMetadata.id,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const flatSearchFieldMetadataMaps = createEmptyFlatEntityMaps();
|
||||
|
||||
for (const searchFieldMetadata of existingSearchFieldMetadatas) {
|
||||
const resolvedTsVectorFieldMetadataId =
|
||||
(searchFieldMetadata.tsVectorFieldMetadataId as string | null) ??
|
||||
searchVectorFieldIdByObjectMetadataId.get(
|
||||
searchFieldMetadata.objectMetadataId,
|
||||
);
|
||||
|
||||
const flatSearchFieldMetadata =
|
||||
fromSearchFieldMetadataEntityToFlatSearchFieldMetadata({
|
||||
entity: searchFieldMetadata,
|
||||
entity: isDefined(resolvedTsVectorFieldMetadataId)
|
||||
? {
|
||||
...searchFieldMetadata,
|
||||
tsVectorFieldMetadataId: resolvedTsVectorFieldMetadataId,
|
||||
}
|
||||
: searchFieldMetadata,
|
||||
applicationIdToUniversalIdentifierMap,
|
||||
objectMetadataIdToUniversalIdentifierMap,
|
||||
fieldMetadataIdToUniversalIdentifierMap,
|
||||
|
||||
+4
@@ -6,6 +6,7 @@ import { type UniversalFlatSearchFieldMetadata } from 'src/engine/workspace-mana
|
||||
export const buildFlatSearchFieldMetadataForField = ({
|
||||
flatObjectMetadata,
|
||||
flatFieldMetadata,
|
||||
tsVectorFlatFieldMetadata,
|
||||
position,
|
||||
}: {
|
||||
flatObjectMetadata: Pick<
|
||||
@@ -13,6 +14,7 @@ export const buildFlatSearchFieldMetadataForField = ({
|
||||
'applicationUniversalIdentifier' | 'universalIdentifier'
|
||||
>;
|
||||
flatFieldMetadata: { universalIdentifier: string };
|
||||
tsVectorFlatFieldMetadata: { universalIdentifier: string };
|
||||
position: number;
|
||||
}): UniversalFlatSearchFieldMetadata => {
|
||||
const createdAt = new Date().toISOString();
|
||||
@@ -26,5 +28,7 @@ export const buildFlatSearchFieldMetadataForField = ({
|
||||
flatObjectMetadata.applicationUniversalIdentifier,
|
||||
objectMetadataUniversalIdentifier: flatObjectMetadata.universalIdentifier,
|
||||
fieldMetadataUniversalIdentifier: flatFieldMetadata.universalIdentifier,
|
||||
tsVectorFieldMetadataUniversalIdentifier:
|
||||
tsVectorFlatFieldMetadata.universalIdentifier,
|
||||
};
|
||||
};
|
||||
|
||||
+9
-5
@@ -15,11 +15,15 @@ export type SearchVectorTargetField = {
|
||||
sortKey: string;
|
||||
};
|
||||
|
||||
export const buildSearchVectorTargetField = (
|
||||
field: { name: string; type: FieldMetadataType },
|
||||
position: number,
|
||||
sortKey: string,
|
||||
): SearchVectorTargetField => ({
|
||||
export const buildSearchVectorTargetField = ({
|
||||
field,
|
||||
position,
|
||||
sortKey,
|
||||
}: {
|
||||
field: { name: string; type: FieldMetadataType };
|
||||
position: number;
|
||||
sortKey: string;
|
||||
}): SearchVectorTargetField => ({
|
||||
name: field.name,
|
||||
type: field.type,
|
||||
position,
|
||||
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
import { type FieldMetadataType } from 'twenty-shared/types';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { type FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type';
|
||||
import {
|
||||
buildSearchVectorTargetField,
|
||||
computeSearchVectorAsExpressionFromSearchFieldMetadatas,
|
||||
} from 'src/engine/metadata-modules/flat-search-field-metadata/utils/compute-search-vector-as-expression-from-search-field-metadatas.util';
|
||||
import { type FlatSearchFieldMetadata } from 'src/engine/metadata-modules/flat-search-field-metadata/types/flat-search-field-metadata.type';
|
||||
|
||||
export const deriveSearchVectorAsExpressionForTsVectorField = ({
|
||||
tsVectorFieldMetadataId,
|
||||
flatSearchFieldMetadataMaps,
|
||||
indexedFieldById,
|
||||
}: {
|
||||
tsVectorFieldMetadataId: string;
|
||||
flatSearchFieldMetadataMaps: FlatEntityMaps<FlatSearchFieldMetadata>;
|
||||
indexedFieldById: ReadonlyMap<
|
||||
string,
|
||||
{ name: string; type: FieldMetadataType }
|
||||
>;
|
||||
}): string => {
|
||||
const targetSearchableFields = Object.values(
|
||||
flatSearchFieldMetadataMaps.byUniversalIdentifier,
|
||||
)
|
||||
.filter(isDefined)
|
||||
.filter(
|
||||
(flatSearchFieldMetadata) =>
|
||||
flatSearchFieldMetadata.tsVectorFieldMetadataId ===
|
||||
tsVectorFieldMetadataId,
|
||||
)
|
||||
.flatMap((flatSearchFieldMetadata) => {
|
||||
const indexedField = indexedFieldById.get(
|
||||
flatSearchFieldMetadata.fieldMetadataId,
|
||||
);
|
||||
|
||||
if (!isDefined(indexedField)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return [
|
||||
buildSearchVectorTargetField({
|
||||
field: indexedField,
|
||||
position: flatSearchFieldMetadata.position,
|
||||
sortKey: flatSearchFieldMetadata.universalIdentifier,
|
||||
}),
|
||||
];
|
||||
});
|
||||
|
||||
return computeSearchVectorAsExpressionFromSearchFieldMetadatas(
|
||||
targetSearchableFields,
|
||||
);
|
||||
};
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
import { FieldMetadataType } from 'twenty-shared/types';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { type FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type';
|
||||
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
|
||||
import { SEARCH_VECTOR_FIELD } from 'src/engine/metadata-modules/search-field-metadata/constants/search-vector-field.constants';
|
||||
|
||||
// Resolves the object's single system TS_VECTOR field (the 'searchVector' field) that
|
||||
// every searchFieldMetadata row contributes to.
|
||||
// Could be refactored once https://github.com/twentyhq/twenty/pull/21949 has been merged.
|
||||
export const findTsVectorFlatFieldMetadataForObject = ({
|
||||
fieldUniversalIdentifiers,
|
||||
flatFieldMetadataMaps,
|
||||
}: {
|
||||
fieldUniversalIdentifiers: string[];
|
||||
flatFieldMetadataMaps: FlatEntityMaps<FlatFieldMetadata>;
|
||||
}): FlatFieldMetadata | undefined =>
|
||||
fieldUniversalIdentifiers
|
||||
.map(
|
||||
(fieldUniversalIdentifier) =>
|
||||
flatFieldMetadataMaps.byUniversalIdentifier[fieldUniversalIdentifier],
|
||||
)
|
||||
.find(
|
||||
(flatFieldMetadata) =>
|
||||
isDefined(flatFieldMetadata) &&
|
||||
flatFieldMetadata.type === FieldMetadataType.TS_VECTOR &&
|
||||
flatFieldMetadata.name === SEARCH_VECTOR_FIELD.name,
|
||||
);
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
import { type FlatSearchFieldMetadata } from 'src/engine/metadata-modules/flat-search-field-metadata/types/flat-search-field-metadata.type';
|
||||
import { type SearchFieldMetadataDTO } from 'src/engine/metadata-modules/search-field-metadata/dtos/search-field-metadata.dto';
|
||||
|
||||
export const fromFlatSearchFieldMetadataToSearchFieldMetadataDto = (
|
||||
flatSearchFieldMetadata: FlatSearchFieldMetadata,
|
||||
): SearchFieldMetadataDTO => ({
|
||||
id: flatSearchFieldMetadata.id,
|
||||
objectMetadataId: flatSearchFieldMetadata.objectMetadataId,
|
||||
fieldMetadataId: flatSearchFieldMetadata.fieldMetadataId,
|
||||
tsVectorFieldMetadataId: flatSearchFieldMetadata.tsVectorFieldMetadataId,
|
||||
position: flatSearchFieldMetadata.position,
|
||||
createdAt: new Date(flatSearchFieldMetadata.createdAt),
|
||||
updatedAt: new Date(flatSearchFieldMetadata.updatedAt),
|
||||
workspaceId: flatSearchFieldMetadata.workspaceId,
|
||||
});
|
||||
+13
@@ -55,6 +55,18 @@ export const fromSearchFieldMetadataEntityToFlatSearchFieldMetadata = ({
|
||||
);
|
||||
}
|
||||
|
||||
const tsVectorFieldMetadataUniversalIdentifier =
|
||||
fieldMetadataIdToUniversalIdentifierMap.get(
|
||||
searchFieldMetadataEntity.tsVectorFieldMetadataId,
|
||||
);
|
||||
|
||||
if (!isDefined(tsVectorFieldMetadataUniversalIdentifier)) {
|
||||
throw new FlatEntityMapsException(
|
||||
`TS_VECTOR field metadata with id ${searchFieldMetadataEntity.tsVectorFieldMetadataId} not found for searchFieldMetadata ${searchFieldMetadataEntity.id}`,
|
||||
FlatEntityMapsExceptionCode.ENTITY_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
...searchFieldMetadataEntityWithoutRelations,
|
||||
createdAt: searchFieldMetadataEntity.createdAt.toISOString(),
|
||||
@@ -64,5 +76,6 @@ export const fromSearchFieldMetadataEntityToFlatSearchFieldMetadata = ({
|
||||
applicationUniversalIdentifier,
|
||||
objectMetadataUniversalIdentifier,
|
||||
fieldMetadataUniversalIdentifier,
|
||||
tsVectorFieldMetadataUniversalIdentifier,
|
||||
};
|
||||
};
|
||||
|
||||
+8
@@ -40,6 +40,7 @@ const PARTIAL_ID_FIELD = {
|
||||
mainGroupByFieldMetadataViewUniversalIdentifiers: [],
|
||||
fieldPermissionUniversalIdentifiers: [],
|
||||
viewSortUniversalIdentifiers: [],
|
||||
searchFieldMetadataUniversalIdentifiers: [],
|
||||
} as const satisfies PartialSystemFlatFieldMetadata<FieldMetadataType.UUID>;
|
||||
|
||||
const PARTIAL_CREATED_AT_FIELD = {
|
||||
@@ -69,6 +70,7 @@ const PARTIAL_CREATED_AT_FIELD = {
|
||||
mainGroupByFieldMetadataViewUniversalIdentifiers: [],
|
||||
fieldPermissionUniversalIdentifiers: [],
|
||||
viewSortUniversalIdentifiers: [],
|
||||
searchFieldMetadataUniversalIdentifiers: [],
|
||||
} as const satisfies PartialSystemFlatFieldMetadata<FieldMetadataType.DATE_TIME>;
|
||||
|
||||
const PARTIAL_UPDATED_AT_FIELD = {
|
||||
@@ -98,6 +100,7 @@ const PARTIAL_UPDATED_AT_FIELD = {
|
||||
mainGroupByFieldMetadataViewUniversalIdentifiers: [],
|
||||
fieldPermissionUniversalIdentifiers: [],
|
||||
viewSortUniversalIdentifiers: [],
|
||||
searchFieldMetadataUniversalIdentifiers: [],
|
||||
} as const satisfies PartialSystemFlatFieldMetadata<FieldMetadataType.DATE_TIME>;
|
||||
|
||||
const PARTIAL_DELETED_AT_FIELD = {
|
||||
@@ -127,6 +130,7 @@ const PARTIAL_DELETED_AT_FIELD = {
|
||||
mainGroupByFieldMetadataViewUniversalIdentifiers: [],
|
||||
fieldPermissionUniversalIdentifiers: [],
|
||||
viewSortUniversalIdentifiers: [],
|
||||
searchFieldMetadataUniversalIdentifiers: [],
|
||||
} as const satisfies PartialSystemFlatFieldMetadata<FieldMetadataType.DATE_TIME>;
|
||||
|
||||
const PARTIAL_CREATED_BY_FIELD = {
|
||||
@@ -156,6 +160,7 @@ const PARTIAL_CREATED_BY_FIELD = {
|
||||
mainGroupByFieldMetadataViewUniversalIdentifiers: [],
|
||||
fieldPermissionUniversalIdentifiers: [],
|
||||
viewSortUniversalIdentifiers: [],
|
||||
searchFieldMetadataUniversalIdentifiers: [],
|
||||
} as const satisfies PartialSystemFlatFieldMetadata<FieldMetadataType.ACTOR>;
|
||||
|
||||
const PARTIAL_UPDATED_BY_FIELD = {
|
||||
@@ -185,6 +190,7 @@ const PARTIAL_UPDATED_BY_FIELD = {
|
||||
mainGroupByFieldMetadataViewUniversalIdentifiers: [],
|
||||
fieldPermissionUniversalIdentifiers: [],
|
||||
viewSortUniversalIdentifiers: [],
|
||||
searchFieldMetadataUniversalIdentifiers: [],
|
||||
} as const satisfies PartialSystemFlatFieldMetadata<FieldMetadataType.ACTOR>;
|
||||
|
||||
const PARTIAL_POSITION_FIELD = {
|
||||
@@ -214,6 +220,7 @@ const PARTIAL_POSITION_FIELD = {
|
||||
mainGroupByFieldMetadataViewUniversalIdentifiers: [],
|
||||
fieldPermissionUniversalIdentifiers: [],
|
||||
viewSortUniversalIdentifiers: [],
|
||||
searchFieldMetadataUniversalIdentifiers: [],
|
||||
} as const satisfies PartialSystemFlatFieldMetadata<FieldMetadataType.POSITION>;
|
||||
|
||||
const PARTIAL_SEARCH_VECTOR_FIELD = {
|
||||
@@ -245,6 +252,7 @@ const PARTIAL_SEARCH_VECTOR_FIELD = {
|
||||
mainGroupByFieldMetadataViewUniversalIdentifiers: [],
|
||||
fieldPermissionUniversalIdentifiers: [],
|
||||
viewSortUniversalIdentifiers: [],
|
||||
searchFieldMetadataUniversalIdentifiers: [],
|
||||
} as const satisfies PartialSystemFlatFieldMetadata<FieldMetadataType.TS_VECTOR>;
|
||||
|
||||
export const PARTIAL_SYSTEM_FLAT_FIELD_METADATAS = {
|
||||
|
||||
+22
@@ -30,6 +30,7 @@ import { ObjectRecordCountDTO } from 'src/engine/metadata-modules/object-metadat
|
||||
import { UpdateOneObjectInput } from 'src/engine/metadata-modules/object-metadata/dtos/update-object.input';
|
||||
import { ObjectMetadataService } from 'src/engine/metadata-modules/object-metadata/object-metadata.service';
|
||||
import { ObjectRecordCountService } from 'src/engine/metadata-modules/object-metadata/object-record-count.service';
|
||||
import { SearchFieldMetadataDTO } from 'src/engine/metadata-modules/search-field-metadata/dtos/search-field-metadata.dto';
|
||||
import { objectMetadataGraphqlApiExceptionHandler } from 'src/engine/metadata-modules/object-metadata/utils/object-metadata-graphql-api-exception-handler.util';
|
||||
import { resolveObjectMetadataStandardOverride } from 'src/engine/metadata-modules/object-metadata/utils/resolve-object-metadata-standard-override.util';
|
||||
import { PermissionsGraphqlApiExceptionFilter } from 'src/engine/metadata-modules/permissions/utils/permissions-graphql-api-exception.filter';
|
||||
@@ -273,4 +274,25 @@ export class ObjectMetadataResolver {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
@ResolveField(() => [SearchFieldMetadataDTO], { nullable: false })
|
||||
async searchFieldMetadataList(
|
||||
@AuthWorkspace() workspace: WorkspaceEntity,
|
||||
@Parent() objectMetadata: ObjectMetadataDTO,
|
||||
@Context() context: { loaders: IDataloaders },
|
||||
): Promise<SearchFieldMetadataDTO[]> {
|
||||
try {
|
||||
const searchFieldMetadataItems =
|
||||
await context.loaders.searchFieldMetadataLoader.load({
|
||||
objectMetadata,
|
||||
workspaceId: workspace.id,
|
||||
});
|
||||
|
||||
return searchFieldMetadataItems;
|
||||
} catch (error) {
|
||||
objectMetadataGraphqlApiExceptionHandler(error);
|
||||
|
||||
return [];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+1
-5
@@ -107,7 +107,6 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
|
||||
|
||||
const {
|
||||
otherObjectFlatFieldMetadatasToUpdate,
|
||||
sameObjectFlatFieldMetadatasToUpdate,
|
||||
flatObjectMetadataToUpdate,
|
||||
flatIndexMetadatasToUpdate,
|
||||
flatViewFieldsToCreate,
|
||||
@@ -171,10 +170,7 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
|
||||
fieldMetadata: {
|
||||
flatEntityToCreate: [],
|
||||
flatEntityToDelete: [],
|
||||
flatEntityToUpdate: [
|
||||
...otherObjectFlatFieldMetadatasToUpdate,
|
||||
...sameObjectFlatFieldMetadatasToUpdate,
|
||||
],
|
||||
flatEntityToUpdate: [...otherObjectFlatFieldMetadatasToUpdate],
|
||||
},
|
||||
viewField: {
|
||||
flatEntityToCreate: flatViewFieldsToCreate,
|
||||
|
||||
+2
-7
@@ -2,7 +2,6 @@ import { FieldMetadataType } from 'twenty-shared/types';
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
import { PARTIAL_SYSTEM_FLAT_FIELD_METADATAS } from 'src/engine/metadata-modules/object-metadata/constants/partial-system-flat-field-metadatas.constant';
|
||||
import { getTsVectorColumnExpressionFromFields } from 'src/engine/workspace-manager/utils/get-ts-vector-column-expression.util';
|
||||
import { type UniversalFlatFieldMetadata } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-field-metadata.type';
|
||||
import { type UniversalFlatObjectMetadata } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-object-metadata.type';
|
||||
|
||||
@@ -154,15 +153,11 @@ export const buildDefaultFlatFieldMetadatasForCustomObject = ({
|
||||
fieldPermissionUniversalIdentifiers: [],
|
||||
universalSettings: null,
|
||||
viewSortUniversalIdentifiers: [],
|
||||
searchFieldMetadataUniversalIdentifiers: [],
|
||||
};
|
||||
|
||||
const searchVectorUniversalSettings: UniversalFlatFieldMetadata<FieldMetadataType.TS_VECTOR>['universalSettings'] =
|
||||
{
|
||||
asExpression: getTsVectorColumnExpressionFromFields(
|
||||
nameField ? [nameField] : [],
|
||||
),
|
||||
generatedType: 'STORED',
|
||||
};
|
||||
null;
|
||||
|
||||
return {
|
||||
fields: {
|
||||
|
||||
+2
@@ -27,6 +27,8 @@ export const buildDefaultSearchFieldMetadatasForCustomObject = ({
|
||||
const nameSearchFieldMetadata = buildFlatSearchFieldMetadataForField({
|
||||
flatObjectMetadata,
|
||||
flatFieldMetadata: nameField,
|
||||
tsVectorFlatFieldMetadata:
|
||||
defaultFlatFieldForCustomObjectMaps.fields.searchVector,
|
||||
position: 0,
|
||||
});
|
||||
|
||||
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
import { Field, HideField, ObjectType } from '@nestjs/graphql';
|
||||
|
||||
import {
|
||||
Authorize,
|
||||
IDField,
|
||||
QueryOptions,
|
||||
} from '@ptc-org/nestjs-query-graphql';
|
||||
import { IsDateString, IsNotEmpty, IsNumber, IsUUID } from 'class-validator';
|
||||
|
||||
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
|
||||
|
||||
@ObjectType('SearchField')
|
||||
@Authorize({
|
||||
// oxlint-disable-next-line typescript/no-explicit-any
|
||||
authorize: (context: any) => ({
|
||||
workspaceId: { eq: context?.req?.workspace?.id },
|
||||
}),
|
||||
})
|
||||
@QueryOptions({
|
||||
defaultResultSize: 10,
|
||||
disableSort: true,
|
||||
maxResultsSize: 1000,
|
||||
})
|
||||
export class SearchFieldMetadataDTO {
|
||||
@IsUUID()
|
||||
@IsNotEmpty()
|
||||
@IDField(() => UUIDScalarType)
|
||||
id: string;
|
||||
|
||||
objectMetadataId: string;
|
||||
|
||||
@IsUUID()
|
||||
@IsNotEmpty()
|
||||
@Field(() => UUIDScalarType)
|
||||
fieldMetadataId: string;
|
||||
|
||||
@IsUUID()
|
||||
@IsNotEmpty()
|
||||
@Field(() => UUIDScalarType)
|
||||
tsVectorFieldMetadataId: string;
|
||||
|
||||
@IsNumber()
|
||||
@IsNotEmpty()
|
||||
@Field()
|
||||
position: number;
|
||||
|
||||
@IsDateString()
|
||||
@Field()
|
||||
createdAt: Date;
|
||||
|
||||
@IsDateString()
|
||||
@Field()
|
||||
updatedAt: Date;
|
||||
|
||||
@HideField()
|
||||
workspaceId: string;
|
||||
}
|
||||
+2
@@ -3,4 +3,6 @@ export enum SearchFieldMetadataExceptionCode {
|
||||
INVALID_SEARCH_FIELD_METADATA_DATA = 'INVALID_SEARCH_FIELD_METADATA_DATA',
|
||||
OBJECT_METADATA_NOT_FOUND = 'OBJECT_METADATA_NOT_FOUND',
|
||||
FIELD_METADATA_NOT_FOUND = 'FIELD_METADATA_NOT_FOUND',
|
||||
TS_VECTOR_FIELD_METADATA_NOT_FOUND = 'TS_VECTOR_FIELD_METADATA_NOT_FOUND',
|
||||
INVALID_TS_VECTOR_FIELD_METADATA = 'INVALID_TS_VECTOR_FIELD_METADATA',
|
||||
}
|
||||
|
||||
+17
-1
@@ -12,6 +12,7 @@ import {
|
||||
} from 'typeorm';
|
||||
|
||||
import { ADD_UNIVERSAL_IDENTIFIER_AND_APPLICATION_ID_TO_SEARCH_FIELD_METADATA_UPGRADE_COMMAND_NAME } from 'src/database/commands/upgrade-version-command/2-16/add-universal-identifier-and-application-id-to-search-field-metadata-upgrade-command-name.constant';
|
||||
import { ADD_TS_VECTOR_FIELD_METADATA_ID_TO_SEARCH_FIELD_METADATA_UPGRADE_COMMAND_NAME } from 'src/database/commands/upgrade-version-command/2-18/add-ts-vector-field-metadata-id-to-search-field-metadata-upgrade-command-name.constant';
|
||||
import { WasIntroducedInUpgrade } from 'src/engine/core-modules/upgrade/decorators/was-introduced-in-upgrade.decorator';
|
||||
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
@@ -46,10 +47,25 @@ export class SearchFieldMetadataEntity extends SyncableEntity {
|
||||
@Column({ nullable: false, type: 'uuid' })
|
||||
fieldMetadataId: string;
|
||||
|
||||
@ManyToOne(() => FieldMetadataEntity, { onDelete: 'CASCADE' })
|
||||
@ManyToOne(
|
||||
() => FieldMetadataEntity,
|
||||
(fieldMetadata) => fieldMetadata.searchFieldMetadatas,
|
||||
{ onDelete: 'CASCADE' },
|
||||
)
|
||||
@JoinColumn({ name: 'fieldMetadataId' })
|
||||
fieldMetadata: Relation<FieldMetadataEntity>;
|
||||
|
||||
@WasIntroducedInUpgrade({
|
||||
upgradeCommandName:
|
||||
ADD_TS_VECTOR_FIELD_METADATA_ID_TO_SEARCH_FIELD_METADATA_UPGRADE_COMMAND_NAME,
|
||||
})
|
||||
@Column({ nullable: false, type: 'uuid' })
|
||||
tsVectorFieldMetadataId: string;
|
||||
|
||||
@ManyToOne(() => FieldMetadataEntity, { onDelete: 'CASCADE' })
|
||||
@JoinColumn({ name: 'tsVectorFieldMetadataId' })
|
||||
tsVectorFieldMetadata: Relation<FieldMetadataEntity>;
|
||||
|
||||
@Column({ nullable: false, type: 'float' })
|
||||
position: number;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user