Improve object metadata maps size (#8635)

## Context
The object metadata map is becoming quite large and its structure could
be simplified.
We are removing byNameSingular/byNamePlural keys, the former can be
retrieved through a new helper and the latter is not used in the code
base currently.
This commit is contained in:
Weiko
2024-11-21 11:49:19 +01:00
committed by GitHub
parent 8772f8aac7
commit 24dbabcad7
10 changed files with 130 additions and 29 deletions

View File

@ -19,6 +19,7 @@ import {
import { FieldMetadataMap } from 'src/engine/metadata-modules/types/field-metadata-map'; 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 { 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 { ObjectMetadataMaps } from 'src/engine/metadata-modules/types/object-metadata-maps';
import { getObjectMetadataMapItemByNameSingular } from 'src/engine/metadata-modules/utils/get-object-metadata-map-item-by-name-singular.util';
export class GraphqlQueryParser { export class GraphqlQueryParser {
private fieldMetadataMapByName: FieldMetadataMap; private fieldMetadataMapByName: FieldMetadataMap;
@ -108,9 +109,10 @@ export class GraphqlQueryParser {
parentObjectMetadata: ObjectMetadataItemWithFieldMaps, parentObjectMetadata: ObjectMetadataItemWithFieldMaps,
graphqlSelectedFields: Partial<Record<string, any>>, graphqlSelectedFields: Partial<Record<string, any>>,
): GraphqlQuerySelectedFieldsResult { ): GraphqlQuerySelectedFieldsResult {
const parentFields = const parentFields = getObjectMetadataMapItemByNameSingular(
this.objectMetadataMaps.byNameSingular[parentObjectMetadata.nameSingular] this.objectMetadataMaps,
?.fieldsByName; parentObjectMetadata.nameSingular,
)?.fieldsByName;
if (!parentFields) { if (!parentFields) {
throw new Error( throw new Error(

View File

@ -17,6 +17,7 @@ import { compositeTypeDefinitions } from 'src/engine/metadata-modules/field-meta
import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
import { isCompositeFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/utils/is-composite-field-metadata-type.util'; import { isCompositeFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/utils/is-composite-field-metadata-type.util';
import { ObjectMetadataMaps } from 'src/engine/metadata-modules/types/object-metadata-maps'; import { ObjectMetadataMaps } from 'src/engine/metadata-modules/types/object-metadata-maps';
import { getObjectMetadataMapItemByNameSingular } from 'src/engine/metadata-modules/utils/get-object-metadata-map-item-by-name-singular.util';
import { CompositeFieldMetadataType } from 'src/engine/metadata-modules/workspace-migration/factories/composite-column-action.factory'; 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'; import { isRelationFieldMetadataType } from 'src/engine/utils/is-relation-field-metadata-type.util';
import { isPlainObject } from 'src/utils/is-plain-object'; import { isPlainObject } from 'src/utils/is-plain-object';
@ -143,7 +144,10 @@ export class ObjectRecordsToGraphqlConnectionHelper {
); );
} }
const objectMetadata = this.objectMetadataMaps.byNameSingular[objectName]; const objectMetadata = getObjectMetadataMapItemByNameSingular(
this.objectMetadataMaps,
objectName,
);
if (!objectMetadata) { if (!objectMetadata) {
throw new GraphqlQueryRunnerException( throw new GraphqlQueryRunnerException(

View File

@ -7,6 +7,10 @@ import {
import { ObjectRecord } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface'; import { ObjectRecord } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface';
import {
GraphqlQueryRunnerException,
GraphqlQueryRunnerExceptionCode,
} from 'src/engine/api/graphql/graphql-query-runner/errors/graphql-query-runner.exception';
import { ProcessAggregateHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/process-aggregate.helper'; import { ProcessAggregateHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/process-aggregate.helper';
import { import {
getRelationMetadata, getRelationMetadata,
@ -15,6 +19,7 @@ import {
import { AggregationField } from 'src/engine/api/graphql/workspace-schema-builder/utils/get-available-aggregations-from-object-fields.util'; import { AggregationField } 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'; 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 { ObjectMetadataMaps } from 'src/engine/metadata-modules/types/object-metadata-maps';
import { getObjectMetadataMapItemByNameSingular } from 'src/engine/metadata-modules/utils/get-object-metadata-map-item-by-name-singular.util';
import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util'; import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util';
import { deduceRelationDirection } from 'src/engine/utils/deduce-relation-direction.util'; import { deduceRelationDirection } from 'src/engine/utils/deduce-relation-direction.util';
@ -177,13 +182,23 @@ export class ProcessNestedRelationsHelper {
joinField: `${inverseRelationName}Id`, joinField: `${inverseRelationName}Id`,
}); });
const referenceObjectMetadataItemWithFieldsMaps =
getObjectMetadataMapItemByNameSingular(
objectMetadataMaps,
referenceObjectMetadata.nameSingular,
);
if (!referenceObjectMetadataItemWithFieldsMaps) {
throw new GraphqlQueryRunnerException(
`Object ${referenceObjectMetadata.nameSingular} not found`,
GraphqlQueryRunnerExceptionCode.OBJECT_METADATA_NOT_FOUND,
);
}
if (Object.keys(nestedRelations).length > 0) { if (Object.keys(nestedRelations).length > 0) {
await this.processNestedRelations({ await this.processNestedRelations({
objectMetadataMaps, objectMetadataMaps,
parentObjectMetadataItem: parentObjectMetadataItem: referenceObjectMetadataItemWithFieldsMaps,
objectMetadataMaps.byNameSingular[
referenceObjectMetadata.nameSingular
],
parentObjectRecords: relationResults as ObjectRecord[], parentObjectRecords: relationResults as ObjectRecord[],
parentObjectRecordsAggregatedValues: relationAggregatedFieldsResult, parentObjectRecordsAggregatedValues: relationAggregatedFieldsResult,
relations: nestedRelations as Record< relations: nestedRelations as Record<
@ -258,13 +273,23 @@ export class ProcessNestedRelationsHelper {
relationName, relationName,
}); });
const referenceObjectMetadataItemWithFieldsMaps =
getObjectMetadataMapItemByNameSingular(
objectMetadataMaps,
referenceObjectMetadata.nameSingular,
);
if (!referenceObjectMetadataItemWithFieldsMaps) {
throw new GraphqlQueryRunnerException(
`Object ${referenceObjectMetadata.nameSingular} not found`,
GraphqlQueryRunnerExceptionCode.OBJECT_METADATA_NOT_FOUND,
);
}
if (Object.keys(nestedRelations).length > 0) { if (Object.keys(nestedRelations).length > 0) {
await this.processNestedRelations({ await this.processNestedRelations({
objectMetadataMaps, objectMetadataMaps,
parentObjectMetadataItem: parentObjectMetadataItem: referenceObjectMetadataItemWithFieldsMaps,
objectMetadataMaps.byNameSingular[
referenceObjectMetadata.nameSingular
],
parentObjectRecords: relationResults as ObjectRecord[], parentObjectRecords: relationResults as ObjectRecord[],
parentObjectRecordsAggregatedValues: relationAggregatedFieldsResult, parentObjectRecordsAggregatedValues: relationAggregatedFieldsResult,
relations: nestedRelations as Record< relations: nestedRelations as Record<

View File

@ -22,6 +22,7 @@ import { ObjectRecordsToGraphqlConnectionHelper } from 'src/engine/api/graphql/g
import { settings } from 'src/engine/constants/settings'; import { settings } from 'src/engine/constants/settings';
import { DUPLICATE_CRITERIA_COLLECTION } from 'src/engine/core-modules/duplicate/constants/duplicate-criteria.constants'; import { DUPLICATE_CRITERIA_COLLECTION } from 'src/engine/core-modules/duplicate/constants/duplicate-criteria.constants';
import { ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps'; import { ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps';
import { getObjectMetadataMapItemByNameSingular } from 'src/engine/metadata-modules/utils/get-object-metadata-map-item-by-name-singular.util';
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
import { formatData } from 'src/engine/twenty-orm/utils/format-data.util'; import { formatData } from 'src/engine/twenty-orm/utils/format-data.util';
import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util'; import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util';
@ -56,10 +57,21 @@ export class GraphqlQueryFindDuplicatesResolverService
objectMetadataItemWithFieldMaps.nameSingular, objectMetadataItemWithFieldMaps.nameSingular,
); );
const objectMetadataItemWithFieldsMaps =
getObjectMetadataMapItemByNameSingular(
objectMetadataMaps,
objectMetadataItemWithFieldMaps.nameSingular,
);
if (!objectMetadataItemWithFieldsMaps) {
throw new GraphqlQueryRunnerException(
`Object ${objectMetadataItemWithFieldMaps.nameSingular} not found`,
GraphqlQueryRunnerExceptionCode.OBJECT_METADATA_NOT_FOUND,
);
}
const graphqlQueryParser = new GraphqlQueryParser( const graphqlQueryParser = new GraphqlQueryParser(
objectMetadataMaps.byNameSingular[ objectMetadataItemWithFieldsMaps?.fieldsByName,
objectMetadataItemWithFieldMaps.nameSingular
].fieldsByName,
objectMetadataMaps, objectMetadataMaps,
); );

View File

@ -2,6 +2,5 @@ import { ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/typ
export type ObjectMetadataMaps = { export type ObjectMetadataMaps = {
byId: Record<string, ObjectMetadataItemWithFieldMaps>; byId: Record<string, ObjectMetadataItemWithFieldMaps>;
byNameSingular: Record<string, ObjectMetadataItemWithFieldMaps>; idByNameSingular: Record<string, string>;
byNamePlural: Record<string, ObjectMetadataItemWithFieldMaps>;
}; };

View File

@ -0,0 +1,42 @@
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 { getObjectMetadataMapItemByNameSingular } from 'src/engine/metadata-modules/utils/get-object-metadata-map-item-by-name-singular.util';
describe('getObjectMetadataMapItemByNameSingular', () => {
it('should return the correct metadata item when given a valid singular name', () => {
const mockMetadataItem = {
id: 'test-id',
nameSingular: 'company',
} as ObjectMetadataItemWithFieldMaps;
const mockObjectMetadataMaps: ObjectMetadataMaps = {
byId: {
'test-id': mockMetadataItem,
},
idByNameSingular: {
company: 'test-id',
},
};
const result = getObjectMetadataMapItemByNameSingular(
mockObjectMetadataMaps,
'company',
);
expect(result).toBe(mockMetadataItem);
});
it('should return undefined when the singular name does not exist', () => {
const mockObjectMetadataMaps: ObjectMetadataMaps = {
byId: {},
idByNameSingular: {},
};
const result = getObjectMetadataMapItemByNameSingular(
mockObjectMetadataMaps,
'nonexistent',
);
expect(result).toBeUndefined();
});
});

View File

@ -9,8 +9,7 @@ export const generateObjectMetadataMaps = (
): ObjectMetadataMaps => { ): ObjectMetadataMaps => {
const objectMetadataMaps: ObjectMetadataMaps = { const objectMetadataMaps: ObjectMetadataMaps = {
byId: {}, byId: {},
byNameSingular: {}, idByNameSingular: {},
byNamePlural: {},
}; };
for (const objectMetadata of objectMetadataCollection) { for (const objectMetadata of objectMetadataCollection) {
@ -29,10 +28,8 @@ export const generateObjectMetadataMaps = (
}; };
objectMetadataMaps.byId[objectMetadata.id] = processedObjectMetadata; objectMetadataMaps.byId[objectMetadata.id] = processedObjectMetadata;
objectMetadataMaps.byNameSingular[objectMetadata.nameSingular] = objectMetadataMaps.idByNameSingular[objectMetadata.nameSingular] =
processedObjectMetadata; objectMetadata.id;
objectMetadataMaps.byNamePlural[objectMetadata.namePlural] =
processedObjectMetadata;
} }
return objectMetadataMaps; return objectMetadataMaps;

View File

@ -0,0 +1,11 @@
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';
export const getObjectMetadataMapItemByNameSingular = (
objectMetadataMaps: ObjectMetadataMaps,
nameSingular: string,
): ObjectMetadataItemWithFieldMaps | undefined => {
return objectMetadataMaps.byId[
objectMetadataMaps.idByNameSingular[nameSingular]
];
};

View File

@ -23,6 +23,7 @@ import { UpsertOptions } from 'typeorm/repository/UpsertOptions';
import { WorkspaceInternalContext } from 'src/engine/twenty-orm/interfaces/workspace-internal-context.interface'; import { WorkspaceInternalContext } from 'src/engine/twenty-orm/interfaces/workspace-internal-context.interface';
import { ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps'; import { ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps';
import { getObjectMetadataMapItemByNameSingular } from 'src/engine/metadata-modules/utils/get-object-metadata-map-item-by-name-singular.util';
import { WorkspaceEntitiesStorage } from 'src/engine/twenty-orm/storage/workspace-entities.storage'; import { WorkspaceEntitiesStorage } from 'src/engine/twenty-orm/storage/workspace-entities.storage';
import { formatData } from 'src/engine/twenty-orm/utils/format-data.util'; import { formatData } from 'src/engine/twenty-orm/utils/format-data.util';
import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util'; import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util';
@ -630,16 +631,20 @@ export class WorkspaceRepository<
throw new Error('Object metadata name is missing'); throw new Error('Object metadata name is missing');
} }
const objectMetadata = const objectMetadata = getObjectMetadataMapItemByNameSingular(
this.internalContext.objectMetadataMaps.byNameSingular[ this.internalContext.objectMetadataMaps,
objectMetadataName objectMetadataName,
]; );
if (!objectMetadata) { if (!objectMetadata) {
throw new Error( throw new Error(
`Object metadata for object "${objectMetadataName}" is missing ` + `Object metadata for object "${objectMetadataName}" is missing ` +
`in workspace "${this.internalContext.workspaceId}" ` + `in workspace "${this.internalContext.workspaceId}" ` +
`with object metadata collection length: ${this.internalContext.objectMetadataMaps.byNameSingular.length}`, `with object metadata collection length: ${
Object.keys(
this.internalContext.objectMetadataMaps.idByNameSingular,
).length
}`,
); );
} }

View File

@ -14,6 +14,7 @@ import { QUERY_MAX_RECORDS } from 'src/engine/api/graphql/graphql-query-runner/c
import { GraphqlQueryParser } from 'src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query.parser'; import { GraphqlQueryParser } from 'src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query.parser';
import { ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps'; 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 { ObjectMetadataMaps } from 'src/engine/metadata-modules/types/object-metadata-maps';
import { getObjectMetadataMapItemByNameSingular } from 'src/engine/metadata-modules/utils/get-object-metadata-map-item-by-name-singular.util';
import { ScopedWorkspaceContextFactory } from 'src/engine/twenty-orm/factories/scoped-workspace-context.factory'; import { ScopedWorkspaceContextFactory } from 'src/engine/twenty-orm/factories/scoped-workspace-context.factory';
import { WorkspaceRepository } from 'src/engine/twenty-orm/repository/workspace.repository'; import { WorkspaceRepository } from 'src/engine/twenty-orm/repository/workspace.repository';
import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager'; import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager';
@ -179,7 +180,10 @@ export class RecordCRUDWorkflowAction implements WorkflowAction {
} }
const objectMetadataItemWithFieldsMaps = const objectMetadataItemWithFieldsMaps =
objectMetadataMaps.byNameSingular[workflowActionInput.objectName]; getObjectMetadataMapItemByNameSingular(
objectMetadataMaps,
workflowActionInput.objectName,
);
if (!objectMetadataItemWithFieldsMaps) { if (!objectMetadataItemWithFieldsMaps) {
throw new RecordCRUDActionException( throw new RecordCRUDActionException(