Skip to content

Improve performance on metadata computation #12785

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 24 commits into from
Jun 23, 2025
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,6 @@ export abstract class ActiveOrSuspendedWorkspacesMigrationCommandRunner<
const dataSource =
await this.twentyORMGlobalManager.getDataSourceForWorkspace({
workspaceId,
shouldFailIfMetadataNotFound: false,
});

await this.runOnWorkspace({
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { MigrationInterface, QueryRunner } from 'typeorm';

export class RemoveRelationMetadata1750673748111 implements MigrationInterface {
name = 'RemoveRelationMetadata1750673748111';

public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`CREATE INDEX "IDX_DATA_SOURCE_WORKSPACE_ID_CREATED_AT" ON "core"."dataSource" ("workspaceId", "createdAt") `,
);
await queryRunner.query(
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also removing this

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why ?

`ALTER TABLE "core"."relationMetadata" DROP CONSTRAINT IF EXISTS "FK_9dea8f90d04edbbf9c541a95c3b"`,
);
await queryRunner.query(
`ALTER TABLE "core"."relationMetadata" DROP CONSTRAINT IF EXISTS "FK_3deb257254145a3bdde9575e7d6"`,
);
await queryRunner.query(
`ALTER TABLE "core"."relationMetadata" DROP CONSTRAINT IF EXISTS "FK_0f781f589e5a527b8f3d3a4b824"`,
);
await queryRunner.query(
`ALTER TABLE "core"."relationMetadata" DROP CONSTRAINT IF EXISTS "FK_f2a0acd3a548ee446a1a35df44d"`,
);
await queryRunner.query(`DROP TABLE IF EXISTS "core"."relationMetadata"`);
}

public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`DROP INDEX "core"."IDX_DATA_SOURCE_WORKSPACE_ID_CREATED_AT"`,
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -307,22 +307,34 @@ export const objectMetadataItemMock = {

export const objectMetadataMapItemMock = {
id: 'mockObjectId',
icon: 'Icon123',
nameSingular: 'objectName',
namePlural: 'objectsName',
fields,
fieldsById: fields.reduce((acc, field) => {
// @ts-expect-error legacy noImplicitAny
acc[field.id] = field;

return acc;
}, {}),
fieldsByName: fields.reduce((acc, field) => {
fieldIdByName: fields.reduce((acc, field) => {
// @ts-expect-error legacy noImplicitAny
acc[field.name] = field;

return acc;
}, {}),
} as ObjectMetadataItemWithFieldMaps;
fieldIdByJoinColumnName: {},
labelSingular: 'Object',
labelPlural: 'Objects',
workspaceId: 'mockWorkspaceId',
isCustom: false,
isSystem: false,
targetTableName: '',
indexMetadatas: [],
isActive: true,
isRemote: false,
isAuditLogged: false,
isSearchable: false,
} satisfies ObjectMetadataItemWithFieldMaps;

export const objectMetadataMapsMock = {
byId: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@ import { FieldMetadataType } from 'twenty-shared/types';
import { WorkspaceEntityDuplicateCriteria } from 'src/engine/api/graphql/workspace-query-builder/types/workspace-entity-duplicate-criteria.type';
import { ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps';

export const mockPersonObjectMetadata = (
export const mockPersonObjectMetadataWithFieldMaps = (
duplicateCriteria: WorkspaceEntityDuplicateCriteria[],
): ObjectMetadataItemWithFieldMaps => ({
id: '',
icon: 'Icon123',
standardId: '',
nameSingular: 'person',
namePlural: 'people',
Expand All @@ -24,12 +25,16 @@ export const mockPersonObjectMetadata = (
labelIdentifierFieldMetadataId: '',
imageIdentifierFieldMetadataId: '',
workspaceId: '',
fields: [],
indexMetadatas: [],
fieldsById: {},
fieldsByJoinColumnName: {},
fieldsByName: {
name: {
fieldIdByName: {
name: 'name-id',
emails: 'emails-id',
linkedinLink: 'linkedinLink-id',
jobTitle: 'jobTitle-id',
},
fieldIdByJoinColumnName: {},
fieldsById: {
'name-id': {
id: '',
objectMetadataId: '',
type: FieldMetadataType.FULL_NAME,
Expand All @@ -45,7 +50,7 @@ export const mockPersonObjectMetadata = (
isUnique: false,
workspaceId: '',
},
emails: {
'emails-id': {
id: '',
objectMetadataId: '',
type: FieldMetadataType.EMAILS,
Expand All @@ -59,7 +64,7 @@ export const mockPersonObjectMetadata = (
isCustom: false,
workspaceId: '',
},
linkedinLink: {
'linkedinLink-id': {
id: '',
objectMetadataId: '',
type: FieldMetadataType.LINKS,
Expand All @@ -76,7 +81,7 @@ export const mockPersonObjectMetadata = (
isUnique: false,
workspaceId: '',
},
jobTitle: {
'jobTitle-id': {
id: '',
objectMetadataId: '',
type: FieldMetadataType.TEXT,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,19 @@ import { Brackets, NotBrackets, WhereExpressionBuilder } from 'typeorm';

import { ObjectRecordFilter } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface';

import { FieldMetadataMap } from 'src/engine/metadata-modules/types/field-metadata-map';
import { ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps';
import { WorkspaceSelectQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-select-query-builder';

import { GraphqlQueryFilterFieldParser } from './graphql-query-filter-field.parser';

export class GraphqlQueryFilterConditionParser {
private fieldMetadataMapByName: FieldMetadataMap;
private fieldMetadataMapByJoinColumnName: FieldMetadataMap;
private objectMetadataMapItem: ObjectMetadataItemWithFieldMaps;
private queryFilterFieldParser: GraphqlQueryFilterFieldParser;

constructor(
fieldMetadataMapByName: FieldMetadataMap,
fieldMetadataMapByJoinColumnName: FieldMetadataMap,
) {
this.fieldMetadataMapByName = fieldMetadataMapByName;
this.fieldMetadataMapByJoinColumnName = fieldMetadataMapByJoinColumnName;
constructor(objectMetadataMapItem: ObjectMetadataItemWithFieldMaps) {
this.objectMetadataMapItem = objectMetadataMapItem;
this.queryFilterFieldParser = new GraphqlQueryFilterFieldParser(
this.fieldMetadataMapByName,
this.fieldMetadataMapByJoinColumnName,
this.objectMetadataMapItem,
);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,21 +10,16 @@ import {
import { computeWhereConditionParts } from 'src/engine/api/graphql/graphql-query-runner/utils/compute-where-condition-parts';
import { compositeTypeDefinitions } from 'src/engine/metadata-modules/field-metadata/composite-types';
import { isCompositeFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/utils/is-composite-field-metadata-type.util';
import { FieldMetadataMap } from 'src/engine/metadata-modules/types/field-metadata-map';
import { ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps';
import { CompositeFieldMetadataType } from 'src/engine/metadata-modules/workspace-migration/factories/composite-column-action.factory';

const ARRAY_OPERATORS = ['in', 'contains', 'notContains'];

export class GraphqlQueryFilterFieldParser {
private fieldMetadataMapByName: FieldMetadataMap;
private fieldMetadataMapByJoinColumnName: FieldMetadataMap;

constructor(
fieldMetadataMapByName: FieldMetadataMap,
fieldMetadataMapByJoinColumnName: FieldMetadataMap,
) {
this.fieldMetadataMapByName = fieldMetadataMapByName;
this.fieldMetadataMapByJoinColumnName = fieldMetadataMapByJoinColumnName;
private objectMetadataMapItem: ObjectMetadataItemWithFieldMaps;

constructor(objectMetadataMapItem: ObjectMetadataItemWithFieldMaps) {
this.objectMetadataMapItem = objectMetadataMapItem;
}

public parse(
Expand All @@ -35,9 +30,12 @@ export class GraphqlQueryFilterFieldParser {
filterValue: any,
isFirst = false,
): void {
const fieldMetadataId =
this.objectMetadataMapItem.fieldIdByName[`${key}`] ||
this.objectMetadataMapItem.fieldIdByJoinColumnName[`${key}`];

const fieldMetadata =
this.fieldMetadataMapByName[`${key}`] ||
this.fieldMetadataMapByJoinColumnName[`${key}`];
this.objectMetadataMapItem.fieldsById[fieldMetadataId];

if (!fieldMetadata) {
throw new Error(`Field metadata not found for field: ${key}`);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,14 @@ import {
} from 'src/engine/api/graphql/graphql-query-runner/errors/graphql-query-runner.exception';
import { compositeTypeDefinitions } from 'src/engine/metadata-modules/field-metadata/composite-types';
import { isCompositeFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/utils/is-composite-field-metadata-type.util';
import { FieldMetadataMap } from 'src/engine/metadata-modules/types/field-metadata-map';
import { ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps';
import { CompositeFieldMetadataType } from 'src/engine/metadata-modules/workspace-migration/factories/composite-column-action.factory';

export class GraphqlQueryOrderFieldParser {
private fieldMetadataMapByName: FieldMetadataMap;
private objectMetadataMapItem: ObjectMetadataItemWithFieldMaps;

constructor(fieldMetadataMapByName: FieldMetadataMap) {
this.fieldMetadataMapByName = fieldMetadataMapByName;
constructor(objectMetadataMapItem: ObjectMetadataItemWithFieldMaps) {
this.objectMetadataMapItem = objectMetadataMapItem;
}

parse(
Expand All @@ -30,7 +30,9 @@ export class GraphqlQueryOrderFieldParser {
return orderBy.reduce(
(acc, item) => {
Object.entries(item).forEach(([key, value]) => {
const fieldMetadata = this.fieldMetadataMapByName[key];
const fieldMetadataId = this.objectMetadataMapItem.fieldIdByName[key];
const fieldMetadata =
this.objectMetadataMapItem.fieldsById[fieldMetadataId];

if (!fieldMetadata || value === undefined) {
throw new GraphqlQueryRunnerException(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,21 +1,20 @@
import { FieldMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata.interface';

import { GraphqlQuerySelectedFieldsResult } from 'src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query-selected-fields/graphql-selected-fields.parser';
import {
AggregationField,
getAvailableAggregationsFromObjectFields,
} from 'src/engine/api/graphql/workspace-schema-builder/utils/get-available-aggregations-from-object-fields.util';
import { ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps';

export class GraphqlQuerySelectedFieldsAggregateParser {
parse(
// eslint-disable-next-line @typescript-eslint/no-explicit-any
graphqlSelectedFields: Partial<Record<string, any>>,
fieldMetadataMapByName: Record<string, FieldMetadataInterface>,
objectMetadataMapItem: ObjectMetadataItemWithFieldMaps,
accumulator: GraphqlQuerySelectedFieldsResult,
): void {
const availableAggregations: Record<string, AggregationField> =
getAvailableAggregationsFromObjectFields(
Object.values(fieldMetadataMapByName),
Object.values(objectMetadataMapItem.fieldsById),
);

for (const selectedField of Object.keys(graphqlSelectedFields)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,13 @@ export class GraphqlQuerySelectedFieldsRelationParser {
this.objectMetadataMaps,
);

const targetFields = targetObjectMetadata.fieldsByName;
const fieldParser = new GraphqlQuerySelectedFieldsParser(
this.objectMetadataMaps,
);
const relationAccumulator = fieldParser.parse(fieldValue, targetFields);
const relationAccumulator = fieldParser.parse(
fieldValue,
targetObjectMetadata,
);

accumulator.select[fieldKey] = {
id: true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { GraphqlQuerySelectedFieldsAggregateParser } from 'src/engine/api/graphq
import { GraphqlQuerySelectedFieldsRelationParser } from 'src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query-selected-fields/graphql-selected-fields-relation.parser';
import { compositeTypeDefinitions } from 'src/engine/metadata-modules/field-metadata/composite-types';
import { isCompositeFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/utils/is-composite-field-metadata-type.util';
import { ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps';
import { ObjectMetadataMaps } from 'src/engine/metadata-modules/types/object-metadata-maps';
import { CompositeFieldMetadataType } from 'src/engine/metadata-modules/workspace-migration/factories/composite-column-action.factory';
import { isRelationFieldMetadataType } from 'src/engine/utils/is-relation-field-metadata-type.util';
Expand All @@ -32,7 +33,7 @@ export class GraphqlQuerySelectedFieldsParser {
parse(
// eslint-disable-next-line @typescript-eslint/no-explicit-any
graphqlSelectedFields: Partial<Record<string, any>>,
fieldMetadataMapByName: Record<string, FieldMetadataInterface>,
objectMetadataMapItem: ObjectMetadataItemWithFieldMaps,
): GraphqlQuerySelectedFieldsResult {
const accumulator: GraphqlQuerySelectedFieldsResult = {
select: {},
Expand All @@ -43,7 +44,7 @@ export class GraphqlQuerySelectedFieldsParser {
if (this.isRootConnection(graphqlSelectedFields)) {
this.parseConnectionField(
graphqlSelectedFields,
fieldMetadataMapByName,
objectMetadataMapItem,
accumulator,
);

Expand All @@ -52,13 +53,13 @@ export class GraphqlQuerySelectedFieldsParser {

this.aggregateParser.parse(
graphqlSelectedFields,
fieldMetadataMapByName,
objectMetadataMapItem,
accumulator,
);

this.parseRecordField(
graphqlSelectedFields,
fieldMetadataMapByName,
objectMetadataMapItem,
accumulator,
);

Expand All @@ -68,13 +69,16 @@ export class GraphqlQuerySelectedFieldsParser {
private parseRecordField(
// eslint-disable-next-line @typescript-eslint/no-explicit-any
graphqlSelectedFields: Partial<Record<string, any>>,
fieldMetadataMapByName: Record<string, FieldMetadataInterface>,
objectMetadataMapItem: ObjectMetadataItemWithFieldMaps,
accumulator: GraphqlQuerySelectedFieldsResult,
): void {
for (const [fieldKey, fieldValue] of Object.entries(
graphqlSelectedFields,
)) {
const fieldMetadata = fieldMetadataMapByName[fieldKey];
const fieldMetadata =
objectMetadataMapItem.fieldsById[
objectMetadataMapItem.fieldIdByName[fieldKey]
];

if (!fieldMetadata) {
continue;
Expand Down Expand Up @@ -103,18 +107,18 @@ export class GraphqlQuerySelectedFieldsParser {
private parseConnectionField(
// eslint-disable-next-line @typescript-eslint/no-explicit-any
graphqlSelectedFields: Partial<Record<string, any>>,
fieldMetadataMapByName: Record<string, FieldMetadataInterface>,
objectMetadataMapItem: ObjectMetadataItemWithFieldMaps,
accumulator: GraphqlQuerySelectedFieldsResult,
): void {
this.aggregateParser.parse(
graphqlSelectedFields,
fieldMetadataMapByName,
objectMetadataMapItem,
accumulator,
);

const node = graphqlSelectedFields.edges.node;

this.parseRecordField(node, fieldMetadataMapByName, accumulator);
this.parseRecordField(node, objectMetadataMapItem, accumulator);
}

private isRootConnection(
Expand Down
Loading
Loading