Steps to test

1. Run metadata migrations
2. Run sync-metadata on your workspace
3. Enable the following feature flags: 
IS_SEARCH_ENABLED
IS_QUERY_RUNNER_TWENTY_ORM_ENABLED
IS_WORKSPACE_MIGRATED_FOR_SEARCH
4. Type Cmd + K and search anything
This commit is contained in:
Marie
2024-10-03 17:18:49 +02:00
committed by GitHub
parent 4c250dd811
commit 5f9435c718
71 changed files with 1517 additions and 209 deletions

View File

@ -2,15 +2,15 @@ import { Injectable } from '@nestjs/common';
import { WorkspaceMigrationBuilderAction } from 'src/engine/workspace-manager/workspace-migration-builder/interfaces/workspace-migration-builder-action.interface';
import { IndexMetadataEntity } from 'src/engine/metadata-modules/index-metadata/index-metadata.entity';
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
import { generateMigrationName } from 'src/engine/metadata-modules/workspace-migration/utils/generate-migration-name.util';
import {
WorkspaceMigrationEntity,
WorkspaceMigrationIndexActionType,
WorkspaceMigrationTableActionType,
} from 'src/engine/metadata-modules/workspace-migration/workspace-migration.entity';
import { computeObjectTargetTable } from 'src/engine/utils/compute-object-target-table.util';
import { generateMigrationName } from 'src/engine/metadata-modules/workspace-migration/utils/generate-migration-name.util';
import { IndexMetadataEntity } from 'src/engine/metadata-modules/index-metadata/index-metadata.entity';
@Injectable()
export class WorkspaceMigrationIndexFactory {
@ -94,6 +94,7 @@ export class WorkspaceMigrationIndexFactory {
return fieldMetadata.name;
}),
type: indexMetadata.indexType,
}));
workspaceMigrations.push({

View File

@ -26,6 +26,7 @@ import { WorkspaceMigrationService } from 'src/engine/metadata-modules/workspace
import { WorkspaceDataSourceService } from 'src/engine/workspace-datasource/workspace-datasource.service';
import { WorkspaceMigrationEnumService } from 'src/engine/workspace-manager/workspace-migration-runner/services/workspace-migration-enum.service';
import { convertOnDeleteActionToOnDelete } from 'src/engine/workspace-manager/workspace-migration-runner/utils/convert-on-delete-action-to-on-delete.util';
import { isDefined } from 'src/utils/is-defined';
import { WorkspaceMigrationTypeService } from './services/workspace-migration-type.service';
import { customTableDefaultColumns } from './utils/custom-table-default-column.util';
@ -194,13 +195,21 @@ export class WorkspaceMigrationRunnerService {
for (const index of indexes) {
switch (index.action) {
case WorkspaceMigrationIndexActionType.CREATE:
await queryRunner.createIndex(
`${schemaName}.${tableName}`,
new TableIndex({
name: index.name,
columnNames: index.columns,
}),
);
if (isDefined(index.type)) {
const quotedColumns = index.columns.map((column) => `"${column}"`);
await queryRunner.query(`
CREATE INDEX "${index.name}" ON "${schemaName}"."${tableName}" USING ${index.type} (${quotedColumns.join(', ')})
`);
} else {
await queryRunner.createIndex(
`${schemaName}.${tableName}`,
new TableIndex({
name: index.name,
columnNames: index.columns,
}),
);
}
break;
case WorkspaceMigrationIndexActionType.DROP:
try {
@ -380,6 +389,8 @@ export class WorkspaceMigrationRunnerService {
enumName: enumName,
isArray: migrationColumn.isArray,
isNullable: migrationColumn.isNullable,
asExpression: migrationColumn.asExpression,
generatedType: migrationColumn.generatedType,
}),
);
}

View File

@ -24,6 +24,8 @@ const commonFieldPropertiesToIgnore = [
'settings',
'joinColumn',
'gate',
'asExpression',
'generatedType',
];
const fieldPropertiesToStringify = ['defaultValue'] as const;

View File

@ -135,6 +135,7 @@ export const COMPANY_STANDARD_FIELD_IDS = {
favorites: '20202020-4d1d-41ac-b13b-621631298d55',
attachments: '20202020-c1b5-4120-b0f0-987ca401ed53',
timelineActivities: '20202020-0414-4daf-9c0d-64fe7b27f89f',
searchVector: '85c71601-72f9-4b7b-b343-d46100b2c74d',
};
export const CONNECTED_ACCOUNT_STANDARD_FIELD_IDS = {
@ -300,6 +301,7 @@ export const OPPORTUNITY_STANDARD_FIELD_IDS = {
noteTargets: '20202020-dd3f-42d5-a382-db58aabf43d3',
attachments: '20202020-87c7-4118-83d6-2f4031005209',
timelineActivities: '20202020-30e2-421f-96c7-19c69d1cf631',
searchVector: '428a0da5-4b2e-4ce3-b695-89a8b384e6e3',
};
export const PERSON_STANDARD_FIELD_IDS = {
@ -325,6 +327,7 @@ export const PERSON_STANDARD_FIELD_IDS = {
messageParticipants: '20202020-498e-4c61-8158-fa04f0638334',
calendarEventParticipants: '20202020-52ee-45e9-a702-b64b3753e3a9',
timelineActivities: '20202020-a43e-4873-9c23-e522de906ce5',
searchVector: '57d1d7ad-fa10-44fc-82f3-ad0959ec2534',
};
export const TASK_STANDARD_FIELD_IDS = {
@ -463,4 +466,5 @@ export const CUSTOM_OBJECT_STANDARD_FIELD_IDS = {
favorites: '20202020-a4a7-4686-b296-1c6c3482ee21',
attachments: '20202020-8d59-46ca-b7b2-73d167712134',
timelineActivities: '20202020-f1ef-4ba4-8f33-1a4577afa477',
searchVector: '70e56537-18ef-4811-b1c7-0a444006b815',
};

View File

@ -166,6 +166,8 @@ export class StandardFieldFactory {
isCustom: workspaceFieldMetadataArgs.isDeprecated ? true : false,
isSystem: workspaceFieldMetadataArgs.isSystem ?? false,
isActive: workspaceFieldMetadataArgs.isActive ?? true,
asExpression: workspaceFieldMetadataArgs.asExpression,
generatedType: workspaceFieldMetadataArgs.generatedType,
},
];
}

View File

@ -5,9 +5,12 @@ import { PartialIndexMetadata } from 'src/engine/workspace-manager/workspace-syn
import { WorkspaceSyncContext } from 'src/engine/workspace-manager/workspace-sync-metadata/interfaces/workspace-sync-context.interface';
import { IndexMetadataEntity } from 'src/engine/metadata-modules/index-metadata/index-metadata.entity';
import { generateDeterministicIndexName } from 'src/engine/metadata-modules/index-metadata/utils/generate-deterministic-index-name';
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
import { BaseWorkspaceEntity } from 'src/engine/twenty-orm/base.workspace-entity';
import { CustomWorkspaceEntity } from 'src/engine/twenty-orm/custom.workspace-entity';
import { metadataArgsStorage } from 'src/engine/twenty-orm/storage/metadata-args.storage';
import { computeTableName } from 'src/engine/utils/compute-table-name.util';
import { isGatedAndNotEnabled } from 'src/engine/workspace-manager/workspace-sync-metadata/utils/is-gate-and-not-enabled.util';
@Injectable()
@ -15,23 +18,37 @@ export class StandardIndexFactory {
create(
standardObjectMetadataDefinitions: (typeof BaseWorkspaceEntity)[],
context: WorkspaceSyncContext,
originalObjectMetadataMap: Record<string, ObjectMetadataEntity>,
originalStandardObjectMetadataMap: Record<string, ObjectMetadataEntity>,
originalCustomObjectMetadataMap: Record<string, ObjectMetadataEntity>,
workspaceFeatureFlagsMap: FeatureFlagMap,
): Partial<IndexMetadataEntity>[] {
return standardObjectMetadataDefinitions.flatMap((standardObjectMetadata) =>
this.createIndexMetadata(
standardObjectMetadata,
const standardIndexOnStandardObjects =
standardObjectMetadataDefinitions.flatMap((standardObjectMetadata) =>
this.createStandardIndexMetadataForStandardObject(
standardObjectMetadata,
context,
originalStandardObjectMetadataMap,
workspaceFeatureFlagsMap,
),
);
const standardIndexesOnCustomObjects =
this.createStandardIndexMetadataForCustomObject(
context,
originalObjectMetadataMap,
originalCustomObjectMetadataMap,
workspaceFeatureFlagsMap,
),
);
);
return [
standardIndexOnStandardObjects,
standardIndexesOnCustomObjects,
].flat();
}
private createIndexMetadata(
private createStandardIndexMetadataForStandardObject(
target: typeof BaseWorkspaceEntity,
context: WorkspaceSyncContext,
originalObjectMetadataMap: Record<string, ObjectMetadataEntity>,
originalStandardObjectMetadataMap: Record<string, ObjectMetadataEntity>,
workspaceFeatureFlagsMap: FeatureFlagMap,
): Partial<IndexMetadataEntity>[] {
const workspaceEntity = metadataArgsStorage.filterEntities(target);
@ -58,7 +75,7 @@ export class StandardIndexFactory {
return workspaceIndexMetadataArgsCollection.map(
(workspaceIndexMetadataArgs) => {
const objectMetadata =
originalObjectMetadataMap[workspaceEntity.nameSingular];
originalStandardObjectMetadataMap[workspaceEntity.nameSingular];
if (!objectMetadata) {
throw new Error(
@ -71,10 +88,55 @@ export class StandardIndexFactory {
objectMetadataId: objectMetadata.id,
name: workspaceIndexMetadataArgs.name,
columns: workspaceIndexMetadataArgs.columns,
isCustom: false,
indexType: workspaceIndexMetadataArgs.type,
};
return indexMetadata;
},
);
}
private createStandardIndexMetadataForCustomObject(
context: WorkspaceSyncContext,
originalCustomObjectMetadataMap: Record<string, ObjectMetadataEntity>,
workspaceFeatureFlagsMap: FeatureFlagMap,
): Partial<IndexMetadataEntity>[] {
const target = CustomWorkspaceEntity;
const workspaceEntity = metadataArgsStorage.filterExtendedEntities(target);
if (!workspaceEntity) {
throw new Error(
`Object metadata decorator not found, can't parse ${target.name}`,
);
}
const workspaceIndexMetadataArgsCollection = metadataArgsStorage
.filterIndexes(target)
.filter((workspaceIndexMetadataArgs) => {
return !isGatedAndNotEnabled(
workspaceIndexMetadataArgs.gate,
workspaceFeatureFlagsMap,
);
});
return Object.entries(originalCustomObjectMetadataMap).flatMap(
([customObjectName, customObjectMetadata]) => {
return workspaceIndexMetadataArgsCollection.map(
(workspaceIndexMetadataArgs) => {
const indexMetadata: PartialIndexMetadata = {
workspaceId: context.workspaceId,
objectMetadataId: customObjectMetadata.id,
name: `IDX_${generateDeterministicIndexName([computeTableName(customObjectName, true), ...workspaceIndexMetadataArgs.columns])}`,
columns: workspaceIndexMetadataArgs.columns,
isCustom: false,
indexType: workspaceIndexMetadataArgs.type,
};
return indexMetadata;
},
);
},
);
}
}

View File

@ -16,6 +16,8 @@ export type PartialFieldMetadata = Omit<
workspaceId: string;
objectMetadataId?: string;
isActive?: boolean;
asExpression?: string;
generatedType?: 'STORED' | 'VIRTUAL';
};
export type PartialComputedFieldMetadata = {

View File

@ -10,6 +10,7 @@ import {
} from 'src/engine/workspace-manager/workspace-sync-metadata/interfaces/comparator.interface';
import { WorkspaceSyncContext } from 'src/engine/workspace-manager/workspace-sync-metadata/interfaces/workspace-sync-context.interface';
import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
import { WorkspaceMigrationEntity } from 'src/engine/metadata-modules/workspace-migration/workspace-migration.entity';
import { CustomWorkspaceEntity } from 'src/engine/twenty-orm/custom.workspace-entity';
@ -143,13 +144,27 @@ export class WorkspaceSyncFieldMetadataService {
] of standardObjectStandardFieldMetadataMap) {
const originalObjectMetadata =
originalObjectMetadataMap[standardObjectId];
const computedStandardFieldMetadataCollection = computeStandardFields(
let computedStandardFieldMetadataCollection = computeStandardFields(
standardFieldMetadataCollection,
originalObjectMetadata,
// We need to provide this for generated relations with custom objects
customObjectMetadataCollection,
);
let originalObjectMetadataFields = originalObjectMetadata.fields;
if (!workspaceFeatureFlagsMap.IS_SEARCH_ENABLED) {
computedStandardFieldMetadataCollection =
computedStandardFieldMetadataCollection.filter(
(field) => field.type !== FieldMetadataType.TS_VECTOR,
);
originalObjectMetadataFields = originalObjectMetadataFields.filter(
(field) => field.type !== FieldMetadataType.TS_VECTOR,
);
}
const fieldComparatorResults = this.workspaceFieldComparator.compare(
originalObjectMetadata.id,
originalObjectMetadata.fields,
@ -177,11 +192,24 @@ export class WorkspaceSyncFieldMetadataService {
// Loop over all custom objects from the DB and compare their fields with standard fields
for (const customObjectMetadata of customObjectMetadataCollection) {
// Also, maybe it's better to refactor a bit and move generation part into a separate module ?
const standardFieldMetadataCollection = computeStandardFields(
let standardFieldMetadataCollection = computeStandardFields(
customObjectStandardFieldMetadataCollection,
customObjectMetadata,
);
let customObjectMetadataFields = customObjectMetadata.fields;
if (!workspaceFeatureFlagsMap.IS_SEARCH_ENABLED) {
standardFieldMetadataCollection =
standardFieldMetadataCollection.filter(
(field) => field.type !== FieldMetadataType.TS_VECTOR,
);
customObjectMetadataFields = customObjectMetadataFields.filter(
(field) => field.type !== FieldMetadataType.TS_VECTOR,
);
}
/**
* COMPARE FIELD METADATA
*/

View File

@ -1,22 +1,25 @@
import { Injectable, Logger } from '@nestjs/common';
import { EntityManager } from 'typeorm';
import { Any, EntityManager } from 'typeorm';
import { FeatureFlagMap } from 'src/engine/core-modules/feature-flag/interfaces/feature-flag-map.interface';
import { WorkspaceSyncContext } from 'src/engine/workspace-manager/workspace-sync-metadata/interfaces/workspace-sync-context.interface';
import { ComparatorAction } from 'src/engine/workspace-manager/workspace-sync-metadata/interfaces/comparator.interface';
import { WorkspaceMigrationBuilderAction } from 'src/engine/workspace-manager/workspace-migration-builder/interfaces/workspace-migration-builder-action.interface';
import { ComparatorAction } from 'src/engine/workspace-manager/workspace-sync-metadata/interfaces/comparator.interface';
import { WorkspaceSyncContext } from 'src/engine/workspace-manager/workspace-sync-metadata/interfaces/workspace-sync-context.interface';
import { WorkspaceMigrationEntity } from 'src/engine/metadata-modules/workspace-migration/workspace-migration.entity';
import { WorkspaceSyncStorage } from 'src/engine/workspace-manager/workspace-sync-metadata/storage/workspace-sync.storage';
import { StandardIndexFactory } from 'src/engine/workspace-manager/workspace-sync-metadata/factories/standard-index.factory';
import {
IndexMetadataEntity,
IndexType,
} from 'src/engine/metadata-modules/index-metadata/index-metadata.entity';
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
import { mapObjectMetadataByUniqueIdentifier } from 'src/engine/workspace-manager/workspace-sync-metadata/utils/sync-metadata.util';
import { IndexMetadataEntity } from 'src/engine/metadata-modules/index-metadata/index-metadata.entity';
import { standardObjectMetadataDefinitions } from 'src/engine/workspace-manager/workspace-sync-metadata/standard-objects';
import { WorkspaceIndexComparator } from 'src/engine/workspace-manager/workspace-sync-metadata/comparators/workspace-index.comparator';
import { WorkspaceMetadataUpdaterService } from 'src/engine/workspace-manager/workspace-sync-metadata/services/workspace-metadata-updater.service';
import { WorkspaceMigrationEntity } from 'src/engine/metadata-modules/workspace-migration/workspace-migration.entity';
import { WorkspaceMigrationIndexFactory } from 'src/engine/workspace-manager/workspace-migration-builder/factories/workspace-migration-index.factory';
import { WorkspaceIndexComparator } from 'src/engine/workspace-manager/workspace-sync-metadata/comparators/workspace-index.comparator';
import { StandardIndexFactory } from 'src/engine/workspace-manager/workspace-sync-metadata/factories/standard-index.factory';
import { WorkspaceMetadataUpdaterService } from 'src/engine/workspace-manager/workspace-sync-metadata/services/workspace-metadata-updater.service';
import { standardObjectMetadataDefinitions } from 'src/engine/workspace-manager/workspace-sync-metadata/standard-objects';
import { WorkspaceSyncStorage } from 'src/engine/workspace-manager/workspace-sync-metadata/storage/workspace-sync.storage';
import { mapObjectMetadataByUniqueIdentifier } from 'src/engine/workspace-manager/workspace-sync-metadata/utils/sync-metadata.util';
@Injectable()
export class WorkspaceSyncIndexMetadataService {
@ -47,35 +50,60 @@ export class WorkspaceSyncIndexMetadataService {
workspaceId: context.workspaceId,
// We're only interested in standard fields
fields: { isCustom: false },
isCustom: false,
},
relations: ['dataSource', 'fields', 'indexes'],
});
// Create map of object metadata & field metadata by unique identifier
const originalObjectMetadataMap = mapObjectMetadataByUniqueIdentifier(
originalObjectMetadataCollection,
// Relation are based on the singular name
const originalStandardObjectMetadataMap =
mapObjectMetadataByUniqueIdentifier(
originalObjectMetadataCollection.filter(
(objectMetadata) => !objectMetadata.isCustom,
),
// Relation are based on the singular name
(objectMetadata) => objectMetadata.nameSingular,
);
const originalCustomObjectMetadataMap = mapObjectMetadataByUniqueIdentifier(
originalObjectMetadataCollection.filter(
(objectMetadata) => objectMetadata.isCustom,
),
(objectMetadata) => objectMetadata.nameSingular,
);
const indexMetadataRepository = manager.getRepository(IndexMetadataEntity);
const originalIndexMetadataCollection = await indexMetadataRepository.find({
let originalIndexMetadataCollection = await indexMetadataRepository.find({
where: {
workspaceId: context.workspaceId,
objectMetadataId: Any(
Object.values(originalObjectMetadataCollection).map(
(object) => object.id,
),
),
isCustom: false,
},
relations: ['indexFieldMetadatas.fieldMetadata'],
});
// Generate index metadata from models
const standardIndexMetadataCollection = this.standardIndexFactory.create(
let standardIndexMetadataCollection = this.standardIndexFactory.create(
standardObjectMetadataDefinitions,
context,
originalObjectMetadataMap,
originalStandardObjectMetadataMap,
originalCustomObjectMetadataMap,
workspaceFeatureFlagsMap,
);
if (!workspaceFeatureFlagsMap.IS_SEARCH_ENABLED) {
originalIndexMetadataCollection = originalIndexMetadataCollection.filter(
(index) => index.indexType !== IndexType.GIN,
);
standardIndexMetadataCollection = standardIndexMetadataCollection.filter(
(index) => index.indexType !== IndexType.GIN,
);
}
const indexComparatorResults = this.workspaceIndexComparator.compare(
originalIndexMetadataCollection,
standardIndexMetadataCollection,

View File

@ -54,8 +54,6 @@ export const standardObjectMetadataDefinitions = [
CompanyWorkspaceEntity,
ConnectedAccountWorkspaceEntity,
FavoriteWorkspaceEntity,
OpportunityWorkspaceEntity,
PersonWorkspaceEntity,
TimelineActivityWorkspaceEntity,
ViewFieldWorkspaceEntity,
ViewFilterWorkspaceEntity,
@ -79,10 +77,4 @@ export const standardObjectMetadataDefinitions = [
PersonWorkspaceEntity,
TaskWorkspaceEntity,
TaskTargetWorkspaceEntity,
TimelineActivityWorkspaceEntity,
ViewFieldWorkspaceEntity,
ViewFilterWorkspaceEntity,
ViewSortWorkspaceEntity,
ViewWorkspaceEntity,
WebhookWorkspaceEntity,
];

View File

@ -0,0 +1,103 @@
import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
import { getTsVectorColumnExpressionFromFields } from 'src/engine/workspace-manager/workspace-sync-metadata/utils/get-ts-vector-column-expression.util';
const nameTextField = { name: 'name', type: FieldMetadataType.TEXT };
const nameFullNameField = {
name: 'name',
type: FieldMetadataType.FULL_NAME,
};
const jobTitleTextField = { name: 'jobTitle', type: FieldMetadataType.TEXT };
const emailsEmailsField = { name: 'emails', type: FieldMetadataType.EMAILS };
jest.mock(
'src/engine/metadata-modules/field-metadata/utils/compute-column-name.util',
() => ({
computeColumnName: jest.fn((name) => {
if (name === 'name') {
return 'name';
}
if (name === 'jobTitle') {
return 'jobTitle';
}
if (name === 'emailsPrimaryEmail') {
return 'emailsPrimaryEmail';
}
if (name === 'emailsAdditionalEmails') {
return 'emailsAdditionalEmails';
}
if (name === 'nameFirstName') {
return 'nameFirstName';
}
if (name === 'nameLastName') {
return 'nameLastName';
}
}),
computeCompositeColumnName: jest.fn((field, property) => {
if (
field.name === emailsEmailsField.name &&
property.name === 'primaryEmail'
) {
return 'emailsPrimaryEmail';
}
if (
field.name === emailsEmailsField.name &&
property.name === 'additionalEmails'
) {
return 'emailsAdditionalEmails';
}
if (
field.name === nameFullNameField.name &&
property.name === 'firstName'
) {
return 'nameFirstName';
}
if (
field.name === nameFullNameField.name &&
property.name === 'lastName'
) {
return 'nameLastName';
}
}),
}),
);
describe('getTsVectorColumnExpressionFromFields', () => {
it('should generate correct expression for simple text field', () => {
const fields = [nameTextField];
const result = getTsVectorColumnExpressionFromFields(fields);
expect(result).toContain("to_tsvector('simple', COALESCE(\"name\", ''))");
});
it('should handle multiple fields', () => {
const fields = [nameFullNameField, jobTitleTextField, emailsEmailsField];
const result = getTsVectorColumnExpressionFromFields(fields);
const expected = `
CASE
WHEN "deletedAt" IS NULL THEN
to_tsvector('simple', COALESCE("nameFirstName", '') || ' ' || COALESCE("nameLastName", '') || ' ' || COALESCE("jobTitle", '') || ' ' ||
COALESCE(
replace(
"emailsPrimaryEmail",
'@',
' '
),
''
)
)
ELSE NULL
END
`.trim();
expect(result.trim()).toBe(expected);
});
it('should include CASE statement for handling deletedAt', () => {
const fields = [nameTextField];
const result = getTsVectorColumnExpressionFromFields(fields);
expect(result).toContain('CASE');
expect(result).toContain('WHEN "deletedAt" IS NULL THEN');
expect(result).toContain('ELSE NULL');
});
});

View File

@ -0,0 +1,88 @@
import { compositeTypeDefinitions } from 'src/engine/metadata-modules/field-metadata/composite-types';
import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
import {
computeColumnName,
computeCompositeColumnName,
} from 'src/engine/metadata-modules/field-metadata/utils/compute-column-name.util';
import { isCompositeFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/utils/is-composite-field-metadata-type.util';
import {
WorkspaceMigrationException,
WorkspaceMigrationExceptionCode,
} from 'src/engine/metadata-modules/workspace-migration/workspace-migration.exception';
type FieldTypeAndNameMetadata = {
name: string;
type: FieldMetadataType;
};
export const getTsVectorColumnExpressionFromFields = (
fieldsUsedForSearch: FieldTypeAndNameMetadata[],
): string => {
const columnExpressions = fieldsUsedForSearch.flatMap(
getColumnExpressionsFromField,
);
const concatenatedExpression = columnExpressions.join(" || ' ' || ");
const tsVectorExpression = `to_tsvector('simple', ${concatenatedExpression})`;
return `
CASE
WHEN "deletedAt" IS NULL THEN
${tsVectorExpression}
ELSE NULL
END
`;
};
const getColumnExpressionsFromField = (
fieldMetadataTypeAndName: FieldTypeAndNameMetadata,
): string[] => {
if (isCompositeFieldMetadataType(fieldMetadataTypeAndName.type)) {
const compositeType = compositeTypeDefinitions.get(
fieldMetadataTypeAndName.type,
);
if (!compositeType) {
throw new WorkspaceMigrationException(
`Composite type not found for field metadata type: ${fieldMetadataTypeAndName.type}`,
WorkspaceMigrationExceptionCode.INVALID_FIELD_METADATA,
);
}
return compositeType.properties
.filter((property) => property.type === FieldMetadataType.TEXT)
.map((property) => {
const columnName = computeCompositeColumnName(
fieldMetadataTypeAndName,
property,
);
return getColumnExpression(columnName, fieldMetadataTypeAndName.type);
});
}
const columnName = computeColumnName(fieldMetadataTypeAndName.name);
return [getColumnExpression(columnName, fieldMetadataTypeAndName.type)];
};
const getColumnExpression = (
columnName: string,
fieldType: FieldMetadataType,
): string => {
const quotedColumnName = `"${columnName}"`;
if (fieldType === FieldMetadataType.EMAILS) {
return `
COALESCE(
replace(
${quotedColumnName},
'@',
' '
),
''
)
`;
} else {
return `COALESCE(${quotedColumnName}, '')`;
}
};