## What this is close https://github.com/twentyhq/core-team-issues/issues/2622 A **POC / discussion branch** implementing the runner-scoped alternative to the `__warmedUpCache` design in [core-team-issues#2622](https://github.com/twentyhq/core-team-issues/issues/2622). Not for merge as-is — meant to diff against that plan. ## Problem `deriveSearchVectorAsExpressionForTsVectorField` scans the entire `flatSearchFieldMetadataMaps` (`Object.values(...).filter(...)`) once per object created in a migration. On install that's `O(objectsCreated × totalSearchFields)` — the quadratic #2622 targets. Only **one** of the three call sites is actually hot: - `create-object` (runner) — global maps, called per created object → the quadratic - export DDL — maps already built **per object** (O(k)) - `update-field` rebuild — one field, gated on `rebuildSearchVector` ## Approach Instead of a private `__warmedUpCache` side-channel on `FlatEntityMaps<T>` + drain-on-hydration, this keeps the index in the **consumer**: 1. `derive` now takes `targetSearchFieldMetadatas` (already scoped to the tsVector field) instead of scanning the map itself. 2. The runner builds a `Map<tsVectorFieldMetadataId, searchFields[]>` **once per migration**, lazily, and threads it through the action context. Safe because `searchFieldMetadata` creates are ordered before `objectMetadata` creates (`computeOrderedMigrationActions`), so the map is complete on first use. → `O(totalSearchFields)`. 3. `getTargetSearchFieldMetadatasForTsVectorField` (O(total) filter) stays as the fallback for the one-off callers (export, field-update) and when the accessor isn't provided. ## Why this over `__warmedUpCache` - **No `FlatEntityMaps<T>` type widening**, no convention-only privacy, no id/universalIdentifier drain to keep in sync. - **No referential-integrity obligation.** The index only ever contains entities present in the map, so the "search field created-then-deleted before its object hydrates" case (deferred as an edge in #2622) can't put a stale id into an aggregator and crash `derive` via the `-orThrow` lookup. - **One `derive` path**, not "aggregator + direct-filter fallback for export". - Blast radius: ~220 lines, mostly a new util + test. ## Benchmark (micro, isolated function) Median of 7 trials, 10 search fields per object, running the real shipped utils — old = `getTargetSearchFieldMetadatasForTsVectorField` once per object (identical to the old inline scan), new = `buildSearchFieldMetadatasByTsVectorFieldId` once + N lookups (both assert they resolve the same fields): | objects | total search fields | old (scan/obj) | new (index once) | speedup | |--------:|--------------------:|---------------:|-----------------:|--------:| | 50 | 500 | 2.08 ms | 0.06 ms | 33× | | 100 | 1,000 | 9.26 ms | 0.12 ms | 77× | | 200 | 2,000 | 36.2 ms | 0.23 ms | 160× | | 400 | 4,000 | 151 ms | 0.40 ms | 379× | | 800 | 8,000 | 701 ms | 0.92 ms | 766× | Confirms the old path is quadratic (~4× per doubling of object count) and the new path is linear (sub-ms throughout). **Caveats — read these before trusting the speedup:** - This is the **isolated derivation function**, no DB / DDL / inserts. In a real `create-object` action the derive is a small fraction of per-action cost, so the end-to-end win is far smaller than the ratios above. - A default workspace has ~20–30 objects, where the **old** code already costs only ~1–2 ms total across the whole install. The quadratic only becomes material (>50 ms, the runner's slow-action threshold) around **200–400 objects**. - The measurement that should actually gate this — `[install-perf] create:objectMetadata` on a real install against a real DB with a few hundred objects — has **not** been run yet. The micro-benchmark bounds the upside and locates the knee of the curve; it does not prove end-to-end payoff. ## Not done on purpose - **No end-to-end benchmark yet** — step 0 should still be measuring `[install-perf] create:objectMetadata` on a real large install to confirm the quadratic is worth removing at all. - Relies on the ordering invariant (commented at the build site). The fully self-contained variant is to put the object's search fields on `FlatCreateObjectAction` (builder change) — deliberately left out to keep this runner-scoped. ## Checks `nx typecheck twenty-server`, `nx lint:diff-with-main twenty-server`, new util spec + existing `generate-workspace-schema-ddl` spec all green.
This commit is contained in:
+6
-2
@@ -7,6 +7,7 @@ import { isFlatFieldMetadataOfType } from 'src/engine/metadata-modules/flat-fiel
|
||||
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 { deriveSearchVectorAsExpressionForTsVectorField } from 'src/engine/metadata-modules/flat-search-field-metadata/utils/derive-search-vector-as-expression-for-ts-vector-field.util';
|
||||
import { getTargetSearchFieldMetadatasForTsVectorField } from 'src/engine/metadata-modules/flat-search-field-metadata/utils/get-target-search-field-metadatas-for-ts-vector-field.util';
|
||||
import { type 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 { type SearchFieldMetadataEntity } from 'src/engine/metadata-modules/search-field-metadata/search-field-metadata.entity';
|
||||
@@ -125,8 +126,11 @@ export const generateWorkspaceSchemaDdl = (
|
||||
FieldMetadataType.TS_VECTOR,
|
||||
)
|
||||
? deriveSearchVectorAsExpressionForTsVectorField({
|
||||
tsVectorFieldMetadataId: flatFieldMetadata.id,
|
||||
flatSearchFieldMetadataMaps,
|
||||
targetSearchFieldMetadatas:
|
||||
getTargetSearchFieldMetadatasForTsVectorField({
|
||||
tsVectorFieldMetadataId: flatFieldMetadata.id,
|
||||
flatSearchFieldMetadataMaps,
|
||||
}),
|
||||
indexedFieldById,
|
||||
})
|
||||
: undefined,
|
||||
|
||||
+92
@@ -0,0 +1,92 @@
|
||||
import { type FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type';
|
||||
import { type FlatSearchFieldMetadata } from 'src/engine/metadata-modules/flat-search-field-metadata/types/flat-search-field-metadata.type';
|
||||
import { buildSearchFieldMetadatasByTsVectorFieldId } from 'src/engine/metadata-modules/flat-search-field-metadata/utils/build-search-field-metadatas-by-ts-vector-field-id.util';
|
||||
import { getTargetSearchFieldMetadatasForTsVectorField } from 'src/engine/metadata-modules/flat-search-field-metadata/utils/get-target-search-field-metadatas-for-ts-vector-field.util';
|
||||
|
||||
const buildFlatSearchFieldMetadata = (
|
||||
overrides: Partial<FlatSearchFieldMetadata> &
|
||||
Pick<FlatSearchFieldMetadata, 'universalIdentifier'>,
|
||||
): FlatSearchFieldMetadata =>
|
||||
({
|
||||
id: overrides.universalIdentifier,
|
||||
tsVectorFieldMetadataId: 'ts-vector-1',
|
||||
fieldMetadataId: 'field-1',
|
||||
position: 0,
|
||||
...overrides,
|
||||
}) as unknown as FlatSearchFieldMetadata;
|
||||
|
||||
const buildMaps = (
|
||||
flatSearchFieldMetadatas: FlatSearchFieldMetadata[],
|
||||
): FlatEntityMaps<FlatSearchFieldMetadata> => ({
|
||||
byUniversalIdentifier: Object.fromEntries(
|
||||
flatSearchFieldMetadatas.map((flatSearchFieldMetadata) => [
|
||||
flatSearchFieldMetadata.universalIdentifier,
|
||||
flatSearchFieldMetadata,
|
||||
]),
|
||||
),
|
||||
universalIdentifierById: {},
|
||||
universalIdentifiersByApplicationId: {},
|
||||
});
|
||||
|
||||
describe('buildSearchFieldMetadatasByTsVectorFieldId', () => {
|
||||
it('groups search fields by their tsVector field id', () => {
|
||||
const a1 = buildFlatSearchFieldMetadata({
|
||||
universalIdentifier: 'a1',
|
||||
tsVectorFieldMetadataId: 'ts-vector-a',
|
||||
});
|
||||
const a2 = buildFlatSearchFieldMetadata({
|
||||
universalIdentifier: 'a2',
|
||||
tsVectorFieldMetadataId: 'ts-vector-a',
|
||||
});
|
||||
const b1 = buildFlatSearchFieldMetadata({
|
||||
universalIdentifier: 'b1',
|
||||
tsVectorFieldMetadataId: 'ts-vector-b',
|
||||
});
|
||||
|
||||
const grouped = buildSearchFieldMetadatasByTsVectorFieldId(
|
||||
buildMaps([a1, a2, b1]),
|
||||
);
|
||||
|
||||
expect(grouped.get('ts-vector-a')).toEqual([a1, a2]);
|
||||
expect(grouped.get('ts-vector-b')).toEqual([b1]);
|
||||
expect(grouped.get('ts-vector-unknown')).toBeUndefined();
|
||||
});
|
||||
|
||||
it('matches the one-off filter helper for a given tsVector field', () => {
|
||||
const maps = buildMaps([
|
||||
buildFlatSearchFieldMetadata({
|
||||
universalIdentifier: 'a1',
|
||||
tsVectorFieldMetadataId: 'ts-vector-a',
|
||||
}),
|
||||
buildFlatSearchFieldMetadata({
|
||||
universalIdentifier: 'b1',
|
||||
tsVectorFieldMetadataId: 'ts-vector-b',
|
||||
}),
|
||||
]);
|
||||
|
||||
const grouped = buildSearchFieldMetadatasByTsVectorFieldId(maps);
|
||||
|
||||
expect(grouped.get('ts-vector-a')).toEqual(
|
||||
getTargetSearchFieldMetadatasForTsVectorField({
|
||||
tsVectorFieldMetadataId: 'ts-vector-a',
|
||||
flatSearchFieldMetadataMaps: maps,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('ignores undefined map entries', () => {
|
||||
const maps = buildMaps([
|
||||
buildFlatSearchFieldMetadata({
|
||||
universalIdentifier: 'a1',
|
||||
tsVectorFieldMetadataId: 'ts-vector-a',
|
||||
}),
|
||||
]);
|
||||
|
||||
maps.byUniversalIdentifier['ghost'] = undefined;
|
||||
|
||||
const grouped = buildSearchFieldMetadatasByTsVectorFieldId(maps);
|
||||
|
||||
expect(grouped.get('ts-vector-a')).toHaveLength(1);
|
||||
expect(grouped.size).toBe(1);
|
||||
});
|
||||
});
|
||||
+86
@@ -0,0 +1,86 @@
|
||||
import { type FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type';
|
||||
import { type FlatSearchFieldMetadata } from 'src/engine/metadata-modules/flat-search-field-metadata/types/flat-search-field-metadata.type';
|
||||
import { createSearchFieldMetadatasByTsVectorFieldIdAccessor } from 'src/engine/metadata-modules/flat-search-field-metadata/utils/create-search-field-metadatas-by-ts-vector-field-id-accessor.util';
|
||||
|
||||
const buildFlatSearchFieldMetadata = (
|
||||
universalIdentifier: string,
|
||||
tsVectorFieldMetadataId: string,
|
||||
): FlatSearchFieldMetadata =>
|
||||
({
|
||||
id: universalIdentifier,
|
||||
universalIdentifier,
|
||||
tsVectorFieldMetadataId,
|
||||
fieldMetadataId: `field-${universalIdentifier}`,
|
||||
position: 0,
|
||||
}) as unknown as FlatSearchFieldMetadata;
|
||||
|
||||
const buildMaps = (
|
||||
flatSearchFieldMetadatas: FlatSearchFieldMetadata[],
|
||||
): FlatEntityMaps<FlatSearchFieldMetadata> => ({
|
||||
byUniversalIdentifier: Object.fromEntries(
|
||||
flatSearchFieldMetadatas.map((flatSearchFieldMetadata) => [
|
||||
flatSearchFieldMetadata.universalIdentifier,
|
||||
flatSearchFieldMetadata,
|
||||
]),
|
||||
),
|
||||
universalIdentifierById: {},
|
||||
universalIdentifiersByApplicationId: {},
|
||||
});
|
||||
|
||||
describe('createSearchFieldMetadatasByTsVectorFieldIdAccessor', () => {
|
||||
it('resolves the search fields for a tsVector field', () => {
|
||||
const maps = buildMaps([
|
||||
buildFlatSearchFieldMetadata('a1', 'ts-vector-a'),
|
||||
buildFlatSearchFieldMetadata('a2', 'ts-vector-a'),
|
||||
buildFlatSearchFieldMetadata('b1', 'ts-vector-b'),
|
||||
]);
|
||||
|
||||
const accessor = createSearchFieldMetadatasByTsVectorFieldIdAccessor(
|
||||
() => maps,
|
||||
);
|
||||
|
||||
expect(accessor.get('ts-vector-a')).toHaveLength(2);
|
||||
expect(accessor.get('ts-vector-b')).toHaveLength(1);
|
||||
expect(accessor.get('ts-vector-unknown')).toEqual([]);
|
||||
});
|
||||
|
||||
it('serves a stale result after the map changes until invalidate() is called', () => {
|
||||
const maps = buildMaps([buildFlatSearchFieldMetadata('a1', 'ts-vector-a')]);
|
||||
|
||||
const accessor = createSearchFieldMetadatasByTsVectorFieldIdAccessor(
|
||||
() => maps,
|
||||
);
|
||||
|
||||
expect(accessor.get('ts-vector-a')).toHaveLength(1);
|
||||
|
||||
maps.byUniversalIdentifier['a2'] = buildFlatSearchFieldMetadata(
|
||||
'a2',
|
||||
'ts-vector-a',
|
||||
);
|
||||
|
||||
expect(accessor.get('ts-vector-a')).toHaveLength(1);
|
||||
|
||||
accessor.invalidate();
|
||||
|
||||
expect(accessor.get('ts-vector-a')).toHaveLength(2);
|
||||
});
|
||||
|
||||
it('rebuilds against the latest maps reference returned by the getter', () => {
|
||||
let maps = buildMaps([buildFlatSearchFieldMetadata('a1', 'ts-vector-a')]);
|
||||
|
||||
const accessor = createSearchFieldMetadatasByTsVectorFieldIdAccessor(
|
||||
() => maps,
|
||||
);
|
||||
|
||||
expect(accessor.get('ts-vector-a')).toHaveLength(1);
|
||||
|
||||
maps = buildMaps([
|
||||
buildFlatSearchFieldMetadata('a1', 'ts-vector-a'),
|
||||
buildFlatSearchFieldMetadata('a2', 'ts-vector-a'),
|
||||
]);
|
||||
|
||||
accessor.invalidate();
|
||||
|
||||
expect(accessor.get('ts-vector-a')).toHaveLength(2);
|
||||
});
|
||||
});
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { type FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type';
|
||||
import { type FlatSearchFieldMetadata } from 'src/engine/metadata-modules/flat-search-field-metadata/types/flat-search-field-metadata.type';
|
||||
|
||||
export const buildSearchFieldMetadatasByTsVectorFieldId = (
|
||||
flatSearchFieldMetadataMaps: FlatEntityMaps<FlatSearchFieldMetadata>,
|
||||
): Map<string, FlatSearchFieldMetadata[]> => {
|
||||
const searchFieldMetadatasByTsVectorFieldId = new Map<
|
||||
string,
|
||||
FlatSearchFieldMetadata[]
|
||||
>();
|
||||
|
||||
for (const flatSearchFieldMetadata of Object.values(
|
||||
flatSearchFieldMetadataMaps.byUniversalIdentifier,
|
||||
)) {
|
||||
if (!isDefined(flatSearchFieldMetadata)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const { tsVectorFieldMetadataId } = flatSearchFieldMetadata;
|
||||
|
||||
const existingSearchFieldMetadatas =
|
||||
searchFieldMetadatasByTsVectorFieldId.get(tsVectorFieldMetadataId) ?? [];
|
||||
|
||||
existingSearchFieldMetadatas.push(flatSearchFieldMetadata);
|
||||
|
||||
searchFieldMetadatasByTsVectorFieldId.set(
|
||||
tsVectorFieldMetadataId,
|
||||
existingSearchFieldMetadatas,
|
||||
);
|
||||
}
|
||||
|
||||
return searchFieldMetadatasByTsVectorFieldId;
|
||||
};
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
import { type FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type';
|
||||
import { type FlatSearchFieldMetadata } from 'src/engine/metadata-modules/flat-search-field-metadata/types/flat-search-field-metadata.type';
|
||||
import { buildSearchFieldMetadatasByTsVectorFieldId } from 'src/engine/metadata-modules/flat-search-field-metadata/utils/build-search-field-metadatas-by-ts-vector-field-id.util';
|
||||
|
||||
// Scoped alternative to the warmed-up optimistic aggregator design in
|
||||
// https://github.com/twentyhq/core-team-issues/issues/2622. Prefer implementing
|
||||
// that centralized cache if a second create-before-parent consumer re-occurs.
|
||||
|
||||
export type SearchFieldMetadatasByTsVectorFieldIdAccessor = {
|
||||
get: (tsVectorFieldMetadataId: string) => FlatSearchFieldMetadata[];
|
||||
invalidate: () => void;
|
||||
};
|
||||
|
||||
export const createSearchFieldMetadatasByTsVectorFieldIdAccessor = (
|
||||
getFlatSearchFieldMetadataMaps: () => FlatEntityMaps<FlatSearchFieldMetadata>,
|
||||
): SearchFieldMetadatasByTsVectorFieldIdAccessor => {
|
||||
let searchFieldMetadatasByTsVectorFieldId:
|
||||
| Map<string, FlatSearchFieldMetadata[]>
|
||||
| undefined;
|
||||
|
||||
return {
|
||||
get: (tsVectorFieldMetadataId) => {
|
||||
searchFieldMetadatasByTsVectorFieldId ??=
|
||||
buildSearchFieldMetadatasByTsVectorFieldId(
|
||||
getFlatSearchFieldMetadataMaps(),
|
||||
);
|
||||
|
||||
return (
|
||||
searchFieldMetadatasByTsVectorFieldId.get(tsVectorFieldMetadataId) ?? []
|
||||
);
|
||||
},
|
||||
invalidate: () => {
|
||||
searchFieldMetadatasByTsVectorFieldId = undefined;
|
||||
},
|
||||
};
|
||||
};
|
||||
+6
-16
@@ -1,7 +1,6 @@
|
||||
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,
|
||||
@@ -9,27 +8,17 @@ import {
|
||||
import { type FlatSearchFieldMetadata } from 'src/engine/metadata-modules/flat-search-field-metadata/types/flat-search-field-metadata.type';
|
||||
|
||||
export const deriveSearchVectorAsExpressionForTsVectorField = ({
|
||||
tsVectorFieldMetadataId,
|
||||
flatSearchFieldMetadataMaps,
|
||||
targetSearchFieldMetadatas,
|
||||
indexedFieldById,
|
||||
}: {
|
||||
tsVectorFieldMetadataId: string;
|
||||
flatSearchFieldMetadataMaps: FlatEntityMaps<FlatSearchFieldMetadata>;
|
||||
targetSearchFieldMetadatas: 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 targetSearchableFields = targetSearchFieldMetadatas.flatMap(
|
||||
(flatSearchFieldMetadata) => {
|
||||
const indexedField = indexedFieldById.get(
|
||||
flatSearchFieldMetadata.fieldMetadataId,
|
||||
);
|
||||
@@ -45,7 +34,8 @@ export const deriveSearchVectorAsExpressionForTsVectorField = ({
|
||||
sortKey: flatSearchFieldMetadata.universalIdentifier,
|
||||
}),
|
||||
];
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
return computeSearchVectorAsExpressionFromSearchFieldMetadatas(
|
||||
targetSearchableFields,
|
||||
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { type FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type';
|
||||
import { type FlatSearchFieldMetadata } from 'src/engine/metadata-modules/flat-search-field-metadata/types/flat-search-field-metadata.type';
|
||||
|
||||
export const getTargetSearchFieldMetadatasForTsVectorField = ({
|
||||
tsVectorFieldMetadataId,
|
||||
flatSearchFieldMetadataMaps,
|
||||
}: {
|
||||
tsVectorFieldMetadataId: string;
|
||||
flatSearchFieldMetadataMaps: FlatEntityMaps<FlatSearchFieldMetadata>;
|
||||
}): FlatSearchFieldMetadata[] =>
|
||||
Object.values(flatSearchFieldMetadataMaps.byUniversalIdentifier)
|
||||
.filter(isDefined)
|
||||
.filter(
|
||||
(flatSearchFieldMetadata) =>
|
||||
flatSearchFieldMetadata.tsVectorFieldMetadataId ===
|
||||
tsVectorFieldMetadataId,
|
||||
);
|
||||
+10
-2
@@ -25,6 +25,7 @@ import { isEnumFlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-
|
||||
import { isFlatFieldMetadataOfType } from 'src/engine/metadata-modules/flat-field-metadata/utils/is-flat-field-metadata-of-type.util';
|
||||
import { isMorphOrRelationFlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/utils/is-morph-or-relation-flat-field-metadata.util';
|
||||
import { deriveSearchVectorAsExpressionForTsVectorField } from 'src/engine/metadata-modules/flat-search-field-metadata/utils/derive-search-vector-as-expression-for-ts-vector-field.util';
|
||||
import { getTargetSearchFieldMetadatasForTsVectorField } from 'src/engine/metadata-modules/flat-search-field-metadata/utils/get-target-search-field-metadatas-for-ts-vector-field.util';
|
||||
import { FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
|
||||
import { WorkspaceSchemaManagerService } from 'src/engine/twenty-orm/workspace-schema-manager/workspace-schema-manager.service';
|
||||
import { computeObjectTargetTable } from 'src/engine/utils/compute-object-target-table.util';
|
||||
@@ -180,6 +181,7 @@ export class UpdateFieldActionHandlerService extends WorkspaceMigrationRunnerAct
|
||||
flatIndexMaps,
|
||||
},
|
||||
workspaceId,
|
||||
getSearchFieldMetadatasByTsVectorFieldId,
|
||||
} = context;
|
||||
const { entityId, update } = flatAction;
|
||||
|
||||
@@ -367,8 +369,14 @@ export class UpdateFieldActionHandlerService extends WorkspaceMigrationRunnerAct
|
||||
|
||||
const searchVectorAsExpression =
|
||||
deriveSearchVectorAsExpressionForTsVectorField({
|
||||
tsVectorFieldMetadataId: optimisticFlatFieldMetadata.id,
|
||||
flatSearchFieldMetadataMaps,
|
||||
targetSearchFieldMetadatas:
|
||||
getSearchFieldMetadatasByTsVectorFieldId?.(
|
||||
optimisticFlatFieldMetadata.id,
|
||||
) ??
|
||||
getTargetSearchFieldMetadatasForTsVectorField({
|
||||
tsVectorFieldMetadataId: optimisticFlatFieldMetadata.id,
|
||||
flatSearchFieldMetadataMaps,
|
||||
}),
|
||||
indexedFieldById,
|
||||
});
|
||||
|
||||
|
||||
+17
-4
@@ -10,6 +10,7 @@ import { isCompositeFlatFieldMetadata } from 'src/engine/metadata-modules/flat-f
|
||||
import { isEnumFlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/utils/is-enum-flat-field-metadata.util';
|
||||
import { isFlatFieldMetadataOfType } from 'src/engine/metadata-modules/flat-field-metadata/utils/is-flat-field-metadata-of-type.util';
|
||||
import { deriveSearchVectorAsExpressionForTsVectorField } from 'src/engine/metadata-modules/flat-search-field-metadata/utils/derive-search-vector-as-expression-for-ts-vector-field.util';
|
||||
import { getTargetSearchFieldMetadatasForTsVectorField } from 'src/engine/metadata-modules/flat-search-field-metadata/utils/get-target-search-field-metadatas-for-ts-vector-field.util';
|
||||
import { WorkspaceSchemaManagerService } from 'src/engine/twenty-orm/workspace-schema-manager/workspace-schema-manager.service';
|
||||
import {
|
||||
FlatCreateObjectAction,
|
||||
@@ -120,7 +121,13 @@ export class CreateObjectActionHandlerService extends WorkspaceMigrationRunnerAc
|
||||
async executeForWorkspaceSchema(
|
||||
context: WorkspaceMigrationActionRunnerContext<FlatCreateObjectAction>,
|
||||
): Promise<void> {
|
||||
const { flatAction, queryRunner, workspaceId, allFlatEntityMaps } = context;
|
||||
const {
|
||||
flatAction,
|
||||
queryRunner,
|
||||
workspaceId,
|
||||
allFlatEntityMaps,
|
||||
getSearchFieldMetadatasByTsVectorFieldId,
|
||||
} = context;
|
||||
const { flatEntity: flatObjectMetadata, flatFieldMetadatas } = flatAction;
|
||||
|
||||
const { schemaName, tableName } = getWorkspaceSchemaContextForMigration({
|
||||
@@ -145,9 +152,15 @@ export class CreateObjectActionHandlerService extends WorkspaceMigrationRunnerAc
|
||||
FieldMetadataType.TS_VECTOR,
|
||||
)
|
||||
? deriveSearchVectorAsExpressionForTsVectorField({
|
||||
tsVectorFieldMetadataId: flatFieldMetadata.id,
|
||||
flatSearchFieldMetadataMaps:
|
||||
allFlatEntityMaps.flatSearchFieldMetadataMaps,
|
||||
targetSearchFieldMetadatas:
|
||||
getSearchFieldMetadatasByTsVectorFieldId?.(
|
||||
flatFieldMetadata.id,
|
||||
) ??
|
||||
getTargetSearchFieldMetadatasForTsVectorField({
|
||||
tsVectorFieldMetadataId: flatFieldMetadata.id,
|
||||
flatSearchFieldMetadataMaps:
|
||||
allFlatEntityMaps.flatSearchFieldMetadataMaps,
|
||||
}),
|
||||
indexedFieldById,
|
||||
})
|
||||
: undefined,
|
||||
|
||||
+12
@@ -13,6 +13,7 @@ import { getMetadataFlatEntityMapsKey } from 'src/engine/metadata-modules/flat-e
|
||||
import { getMetadataRelatedMetadataNamesForValidation } from 'src/engine/metadata-modules/flat-entity/utils/get-metadata-related-metadata-names-for-validation.util';
|
||||
import { getMetadataRelatedMetadataNames } from 'src/engine/metadata-modules/flat-entity/utils/get-metadata-related-metadata-names.util';
|
||||
import { getMetadataSerializedRelationNames } from 'src/engine/metadata-modules/flat-entity/utils/get-metadata-serialized-relation-names.util';
|
||||
import { createSearchFieldMetadatasByTsVectorFieldIdAccessor } from 'src/engine/metadata-modules/flat-search-field-metadata/utils/create-search-field-metadatas-by-ts-vector-field-id-accessor.util';
|
||||
import { FIND_ALL_VIEWS_GRAPHQL_OPERATION } from 'src/engine/metadata-modules/view/constants/find-all-views-graphql-operation.constant';
|
||||
import { WorkspaceMetadataVersionService } from 'src/engine/metadata-modules/workspace-metadata-version/services/workspace-metadata-version.service';
|
||||
import { WorkspaceCacheStorageService } from 'src/engine/workspace-cache-storage/workspace-cache-storage.service';
|
||||
@@ -310,6 +311,11 @@ export class WorkspaceMigrationRunnerService {
|
||||
let slowestActionLabel = 'n/a';
|
||||
let actionCount = 0;
|
||||
|
||||
const searchFieldMetadatasByTsVectorFieldIdAccessor =
|
||||
createSearchFieldMetadatasByTsVectorFieldIdAccessor(
|
||||
() => allFlatEntityMaps.flatSearchFieldMetadataMaps,
|
||||
);
|
||||
|
||||
try {
|
||||
await queryRunner.query(`SET LOCAL lock_timeout = '8s'`);
|
||||
|
||||
@@ -330,6 +336,8 @@ export class WorkspaceMigrationRunnerService {
|
||||
queryRunner,
|
||||
workspaceId,
|
||||
preallocatedIdByUniversalIdentifierByMetadataName,
|
||||
getSearchFieldMetadatasByTsVectorFieldId:
|
||||
searchFieldMetadatasByTsVectorFieldIdAccessor.get,
|
||||
},
|
||||
},
|
||||
);
|
||||
@@ -355,6 +363,10 @@ export class WorkspaceMigrationRunnerService {
|
||||
...partialOptimisticCache,
|
||||
} as typeof allFlatEntityMaps;
|
||||
|
||||
if (action.metadataName === 'searchFieldMetadata') {
|
||||
searchFieldMetadatasByTsVectorFieldIdAccessor.invalidate();
|
||||
}
|
||||
|
||||
allMetadataEvents.push(...metadataEvents);
|
||||
allAfterCommitSideEffects.push(...afterCommitSideEffects);
|
||||
}
|
||||
|
||||
+4
@@ -2,6 +2,7 @@ import { type QueryRunner } from 'typeorm';
|
||||
|
||||
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 { type FlatSearchFieldMetadata } from 'src/engine/metadata-modules/flat-search-field-metadata/types/flat-search-field-metadata.type';
|
||||
import { type PreallocatedIdByUniversalIdentifierByMetadataName } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/utils/resolve-universal-relation-identifiers-to-ids.util';
|
||||
import {
|
||||
type AllFlatWorkspaceMigrationAction,
|
||||
@@ -17,6 +18,9 @@ export type WorkspaceMigrationActionRunnerArgs<
|
||||
workspaceId: string;
|
||||
flatApplication: FlatApplication;
|
||||
preallocatedIdByUniversalIdentifierByMetadataName?: PreallocatedIdByUniversalIdentifierByMetadataName;
|
||||
getSearchFieldMetadatasByTsVectorFieldId?: (
|
||||
tsVectorFieldMetadataId: string,
|
||||
) => FlatSearchFieldMetadata[];
|
||||
};
|
||||
|
||||
export type WorkspaceMigrationActionRunnerContext<
|
||||
|
||||
Reference in New Issue
Block a user