diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/helpers/process-nested-relations-v2.helper.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/helpers/process-nested-relations-v2.helper.ts index 03bad6365..1557ba668 100644 --- a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/helpers/process-nested-relations-v2.helper.ts +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/helpers/process-nested-relations-v2.helper.ts @@ -11,6 +11,7 @@ import { 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 { buildColumnsToSelect } from 'src/engine/api/graphql/graphql-query-runner/utils/build-columns-to-select'; import { getTargetObjectMetadataOrThrow } from 'src/engine/api/graphql/graphql-query-runner/utils/get-target-object-metadata.util'; import { AggregationField } from 'src/engine/api/graphql/workspace-schema-builder/utils/get-available-aggregations-from-object-fields.util'; import { AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type'; @@ -40,6 +41,7 @@ export class ProcessNestedRelationsV2Helper { workspaceDataSource, roleId, shouldBypassPermissionChecks, + selectedFields, }: { objectMetadataMaps: ObjectMetadataMaps; parentObjectMetadataItem: ObjectMetadataItemWithFieldMaps; @@ -52,6 +54,8 @@ export class ProcessNestedRelationsV2Helper { authContext: AuthContext; workspaceDataSource: WorkspaceDataSource; shouldBypassPermissionChecks: boolean; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + selectedFields: Record; roleId?: string; }): Promise { const processRelationTasks = Object.entries(relations).map( @@ -69,6 +73,10 @@ export class ProcessNestedRelationsV2Helper { workspaceDataSource, shouldBypassPermissionChecks, roleId, + selectedFields: + selectedFields[sourceFieldName] instanceof Object + ? selectedFields[sourceFieldName] + : undefined, }), ); @@ -88,6 +96,7 @@ export class ProcessNestedRelationsV2Helper { workspaceDataSource, shouldBypassPermissionChecks, roleId, + selectedFields, }: { objectMetadataMaps: ObjectMetadataMaps; parentObjectMetadataItem: ObjectMetadataItemWithFieldMaps; @@ -102,6 +111,7 @@ export class ProcessNestedRelationsV2Helper { workspaceDataSource: WorkspaceDataSource; shouldBypassPermissionChecks: boolean; roleId?: string; + selectedFields: Record; }): Promise { const sourceFieldMetadataId = parentObjectMetadataItem.fieldIdByName[sourceFieldName]; @@ -139,10 +149,20 @@ export class ProcessNestedRelationsV2Helper { roleId, ); - const targetObjectQueryBuilder = targetObjectRepository.createQueryBuilder( + let targetObjectQueryBuilder = targetObjectRepository.createQueryBuilder( targetObjectMetadata.nameSingular, ); + const columnsToSelect = buildColumnsToSelect({ + select: selectedFields, + relations: nestedRelations, + objectMetadataItemWithFieldMaps: targetObjectMetadata, + }); + + targetObjectQueryBuilder = targetObjectQueryBuilder.setFindOptions({ + select: columnsToSelect, + }); + const relationIds = this.getUniqueIds({ records: parentObjectRecords, idField: @@ -208,6 +228,7 @@ export class ProcessNestedRelationsV2Helper { workspaceDataSource, shouldBypassPermissionChecks, roleId, + selectedFields, }); } } @@ -324,7 +345,14 @@ export class ProcessNestedRelationsV2Helper { ); } + const queryBuilderOptions = referenceQueryBuilder.getFindOptions(); + const columnWithoutQuotes = column.replace(/["']/g, ''); + const result = await referenceQueryBuilder + .setFindOptions({ + ...queryBuilderOptions, + select: { ...queryBuilderOptions.select, [columnWithoutQuotes]: true }, + }) .where(`${column} IN (:...ids)`, { ids, }) diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/helpers/process-nested-relations.helper.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/helpers/process-nested-relations.helper.ts index 080630a60..70e9958ae 100644 --- a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/helpers/process-nested-relations.helper.ts +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/helpers/process-nested-relations.helper.ts @@ -29,6 +29,7 @@ export class ProcessNestedRelationsHelper { workspaceDataSource, shouldBypassPermissionChecks, roleId, + selectedFields, }: { objectMetadataMaps: ObjectMetadataMaps; parentObjectMetadataItem: ObjectMetadataItemWithFieldMaps; @@ -41,6 +42,8 @@ export class ProcessNestedRelationsHelper { authContext: AuthContext; workspaceDataSource: WorkspaceDataSource; shouldBypassPermissionChecks: boolean; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + selectedFields: Record; roleId?: string; }): Promise { return this.processNestedRelationsV2Helper.processNestedRelations({ @@ -55,6 +58,7 @@ export class ProcessNestedRelationsHelper { workspaceDataSource, shouldBypassPermissionChecks, roleId, + selectedFields, }); } } diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-create-many-resolver.service.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-create-many-resolver.service.ts index 71752b667..115a9d9bf 100644 --- a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-create-many-resolver.service.ts +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-create-many-resolver.service.ts @@ -17,6 +17,7 @@ import { GraphqlQueryRunnerExceptionCode, } from 'src/engine/api/graphql/graphql-query-runner/errors/graphql-query-runner.exception'; import { ObjectRecordsToGraphqlConnectionHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/object-records-to-graphql-connection.helper'; +import { buildColumnsToSelect } from 'src/engine/api/graphql/graphql-query-runner/utils/build-columns-to-select'; import { assertIsValidUuid } from 'src/engine/api/graphql/workspace-query-runner/utils/assert-is-valid-uuid.util'; import { AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type'; import { compositeTypeDefinitions } from 'src/engine/metadata-modules/field-metadata/composite-types'; @@ -397,7 +398,16 @@ export class GraphqlQueryCreateManyResolverService extends GraphqlQueryBaseResol objectMetadataItemWithFieldMaps.nameSingular, ); + const columnsToSelect = buildColumnsToSelect({ + select: executionArgs.graphqlQuerySelectedFieldsResult.select, + relations: executionArgs.graphqlQuerySelectedFieldsResult.relations, + objectMetadataItemWithFieldMaps, + }); + const nonFormattedUpsertedRecords = await queryBuilder + .setFindOptions({ + select: columnsToSelect, + }) .where({ id: In(objectRecords.generatedMaps.map((record) => record.id)), }) @@ -433,6 +443,7 @@ export class GraphqlQueryCreateManyResolverService extends GraphqlQueryBaseResol workspaceDataSource: executionArgs.workspaceDataSource, roleId, shouldBypassPermissionChecks, + selectedFields: executionArgs.graphqlQuerySelectedFieldsResult.select, }); } diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-create-one-resolver.service.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-create-one-resolver.service.ts index cec42860f..25c41ab31 100644 --- a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-create-one-resolver.service.ts +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-create-one-resolver.service.ts @@ -12,6 +12,7 @@ import { WorkspaceQueryRunnerOptions } from 'src/engine/api/graphql/workspace-qu import { CreateOneResolverArgs } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface'; import { ObjectRecordsToGraphqlConnectionHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/object-records-to-graphql-connection.helper'; +import { buildColumnsToSelect } from 'src/engine/api/graphql/graphql-query-runner/utils/build-columns-to-select'; import { assertIsValidUuid } from 'src/engine/api/graphql/workspace-query-runner/utils/assert-is-valid-uuid.util'; import { assertMutationNotOnRemoteObject } from 'src/engine/metadata-modules/object-metadata/utils/assert-mutation-not-on-remote-object.util'; import { getObjectMetadataFromObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/utils/get-object-metadata-from-object-metadata-Item-with-field-maps'; @@ -41,7 +42,16 @@ export class GraphqlQueryCreateOneResolverService extends GraphqlQueryBaseResolv objectMetadataItemWithFieldMaps.nameSingular, ); + const columnsToSelect = buildColumnsToSelect({ + select: executionArgs.graphqlQuerySelectedFieldsResult.select, + relations: executionArgs.graphqlQuerySelectedFieldsResult.relations, + objectMetadataItemWithFieldMaps, + }); + const nonFormattedUpsertedRecords = await queryBuilder + .setFindOptions({ + select: columnsToSelect, + }) .where({ id: In(objectRecords.generatedMaps.map((record) => record.id)), }) @@ -73,6 +83,7 @@ export class GraphqlQueryCreateOneResolverService extends GraphqlQueryBaseResolv workspaceDataSource: executionArgs.workspaceDataSource, roleId, shouldBypassPermissionChecks: executionArgs.isExecutedByApiKey, + selectedFields: executionArgs.graphqlQuerySelectedFieldsResult.select, }); } diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-delete-many-resolver.service.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-delete-many-resolver.service.ts index 30032b374..3d9d68889 100644 --- a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-delete-many-resolver.service.ts +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-delete-many-resolver.service.ts @@ -11,6 +11,7 @@ import { WorkspaceQueryRunnerOptions } from 'src/engine/api/graphql/workspace-qu import { DeleteManyResolverArgs } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface'; import { ObjectRecordsToGraphqlConnectionHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/object-records-to-graphql-connection.helper'; +import { buildColumnsToReturn } from 'src/engine/api/graphql/graphql-query-runner/utils/build-columns-to-return'; import { assertIsValidUuid } from 'src/engine/api/graphql/workspace-query-runner/utils/assert-is-valid-uuid.util'; import { assertMutationNotOnRemoteObject } from 'src/engine/metadata-modules/object-metadata/utils/assert-mutation-not-on-remote-object.util'; import { getObjectMetadataFromObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/utils/get-object-metadata-from-object-metadata-Item-with-field-maps'; @@ -45,9 +46,15 @@ export class GraphqlQueryDeleteManyResolverService extends GraphqlQueryBaseResol executionArgs.args.filter, ); + const columnsToReturn = buildColumnsToReturn({ + select: executionArgs.graphqlQuerySelectedFieldsResult.select, + relations: executionArgs.graphqlQuerySelectedFieldsResult.relations, + objectMetadataItemWithFieldMaps, + }); + const nonFormattedDeletedObjectRecords = await queryBuilder .softDelete() - .returning('*') + .returning(columnsToReturn) .execute(); const formattedDeletedRecords = formatResult( @@ -75,6 +82,7 @@ export class GraphqlQueryDeleteManyResolverService extends GraphqlQueryBaseResol workspaceDataSource: executionArgs.workspaceDataSource, roleId, shouldBypassPermissionChecks: executionArgs.isExecutedByApiKey, + selectedFields: executionArgs.graphqlQuerySelectedFieldsResult.select, }); } diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-delete-one-resolver.service.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-delete-one-resolver.service.ts index 4b0ef3a9c..1d395aaf1 100644 --- a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-delete-one-resolver.service.ts +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-delete-one-resolver.service.ts @@ -15,10 +15,11 @@ import { GraphqlQueryRunnerExceptionCode, } from 'src/engine/api/graphql/graphql-query-runner/errors/graphql-query-runner.exception'; import { ObjectRecordsToGraphqlConnectionHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/object-records-to-graphql-connection.helper'; +import { buildColumnsToReturn } from 'src/engine/api/graphql/graphql-query-runner/utils/build-columns-to-return'; import { assertIsValidUuid } from 'src/engine/api/graphql/workspace-query-runner/utils/assert-is-valid-uuid.util'; import { assertMutationNotOnRemoteObject } from 'src/engine/metadata-modules/object-metadata/utils/assert-mutation-not-on-remote-object.util'; -import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util'; import { getObjectMetadataFromObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/utils/get-object-metadata-from-object-metadata-Item-with-field-maps'; +import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util'; @Injectable() export class GraphqlQueryDeleteOneResolverService extends GraphqlQueryBaseResolverService< @@ -37,10 +38,16 @@ export class GraphqlQueryDeleteOneResolverService extends GraphqlQueryBaseResolv objectMetadataItemWithFieldMaps.nameSingular, ); + const columnsToReturn = buildColumnsToReturn({ + select: executionArgs.graphqlQuerySelectedFieldsResult.select, + relations: executionArgs.graphqlQuerySelectedFieldsResult.relations, + objectMetadataItemWithFieldMaps, + }); + const nonFormattedDeletedObjectRecords = await queryBuilder .softDelete() .where({ id: executionArgs.args.id }) - .returning('*') + .returning(columnsToReturn) .execute(); const formattedDeletedRecords = formatResult( @@ -77,6 +84,7 @@ export class GraphqlQueryDeleteOneResolverService extends GraphqlQueryBaseResolv workspaceDataSource: executionArgs.workspaceDataSource, roleId, shouldBypassPermissionChecks: executionArgs.isExecutedByApiKey, + selectedFields: executionArgs.graphqlQuerySelectedFieldsResult.select, }); } diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-destroy-many-resolver.service.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-destroy-many-resolver.service.ts index ae23c6dff..7e107673f 100644 --- a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-destroy-many-resolver.service.ts +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-destroy-many-resolver.service.ts @@ -11,6 +11,7 @@ import { WorkspaceQueryRunnerOptions } from 'src/engine/api/graphql/workspace-qu import { DestroyManyResolverArgs } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface'; import { ObjectRecordsToGraphqlConnectionHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/object-records-to-graphql-connection.helper'; +import { buildColumnsToReturn } from 'src/engine/api/graphql/graphql-query-runner/utils/build-columns-to-return'; import { getObjectMetadataFromObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/utils/get-object-metadata-from-object-metadata-Item-with-field-maps'; import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util'; import { computeTableName } from 'src/engine/utils/compute-table-name.util'; @@ -43,9 +44,15 @@ export class GraphqlQueryDestroyManyResolverService extends GraphqlQueryBaseReso executionArgs.args.filter, ); + const columnsToReturn = buildColumnsToReturn({ + select: executionArgs.graphqlQuerySelectedFieldsResult.select, + relations: executionArgs.graphqlQuerySelectedFieldsResult.relations, + objectMetadataItemWithFieldMaps, + }); + const nonFormattedDeletedObjectRecords = await queryBuilder .delete() - .returning('*') + .returning(columnsToReturn) .execute(); const deletedRecords = formatResult( @@ -73,6 +80,7 @@ export class GraphqlQueryDestroyManyResolverService extends GraphqlQueryBaseReso workspaceDataSource: executionArgs.workspaceDataSource, roleId, shouldBypassPermissionChecks: executionArgs.isExecutedByApiKey, + selectedFields: executionArgs.graphqlQuerySelectedFieldsResult.select, }); } diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-destroy-one-resolver.service.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-destroy-one-resolver.service.ts index 6b032f4fa..d218596c6 100644 --- a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-destroy-one-resolver.service.ts +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-destroy-one-resolver.service.ts @@ -15,6 +15,7 @@ import { GraphqlQueryRunnerExceptionCode, } from 'src/engine/api/graphql/graphql-query-runner/errors/graphql-query-runner.exception'; import { ObjectRecordsToGraphqlConnectionHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/object-records-to-graphql-connection.helper'; +import { buildColumnsToReturn } from 'src/engine/api/graphql/graphql-query-runner/utils/build-columns-to-return'; import { getObjectMetadataFromObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/utils/get-object-metadata-from-object-metadata-Item-with-field-maps'; import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util'; @@ -35,10 +36,16 @@ export class GraphqlQueryDestroyOneResolverService extends GraphqlQueryBaseResol objectMetadataItemWithFieldMaps.nameSingular, ); + const columnsToReturn = buildColumnsToReturn({ + select: executionArgs.graphqlQuerySelectedFieldsResult.select, + relations: executionArgs.graphqlQuerySelectedFieldsResult.relations, + objectMetadataItemWithFieldMaps, + }); + const nonFormattedDeletedObjectRecords = await queryBuilder .delete() .where({ id: executionArgs.args.id }) - .returning('*') + .returning(columnsToReturn) .execute(); if (!nonFormattedDeletedObjectRecords.affected) { @@ -73,6 +80,7 @@ export class GraphqlQueryDestroyOneResolverService extends GraphqlQueryBaseResol workspaceDataSource: executionArgs.workspaceDataSource, roleId, shouldBypassPermissionChecks: executionArgs.isExecutedByApiKey, + selectedFields: executionArgs.graphqlQuerySelectedFieldsResult.select, }); } diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-find-duplicates-resolver.service.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-find-duplicates-resolver.service.ts index 0fea44808..e6207f928 100644 --- a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-find-duplicates-resolver.service.ts +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-find-duplicates-resolver.service.ts @@ -21,6 +21,7 @@ import { } from 'src/engine/api/graphql/graphql-query-runner/errors/graphql-query-runner.exception'; import { GraphqlQueryParser } from 'src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query.parser'; import { ObjectRecordsToGraphqlConnectionHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/object-records-to-graphql-connection.helper'; +import { buildColumnsToSelect } from 'src/engine/api/graphql/graphql-query-runner/utils/build-columns-to-select'; import { buildDuplicateConditions } from 'src/engine/api/utils/build-duplicate-conditions.utils'; import { getObjectMetadataMapItemByNameSingular } from 'src/engine/metadata-modules/utils/get-object-metadata-map-item-by-name-singular.util'; import { formatData } from 'src/engine/twenty-orm/utils/format-data.util'; @@ -102,6 +103,12 @@ export class GraphqlQueryFindDuplicatesResolverService extends GraphqlQueryBaseR }); } + const columnsToSelect = buildColumnsToSelect({ + select: executionArgs.graphqlQuerySelectedFieldsResult.select, + relations: executionArgs.graphqlQuerySelectedFieldsResult.relations, + objectMetadataItemWithFieldMaps, + }); + const duplicateRecordsQueryBuilder = executionArgs.repository.createQueryBuilder( objectMetadataItemWithFieldMaps.nameSingular, @@ -113,8 +120,11 @@ export class GraphqlQueryFindDuplicatesResolverService extends GraphqlQueryBaseR duplicateConditions, ); - const nonFormattedDuplicates = - (await duplicateRecordsQueryBuilder.getMany()) as ObjectRecord[]; + const nonFormattedDuplicates = (await duplicateRecordsQueryBuilder + .setFindOptions({ + select: columnsToSelect, + }) + .getMany()) as ObjectRecord[]; const duplicates = formatResult( nonFormattedDuplicates, diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-find-many-resolver.service.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-find-many-resolver.service.ts index cf5173ce2..73592fcbf 100644 --- a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-find-many-resolver.service.ts +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-find-many-resolver.service.ts @@ -23,6 +23,7 @@ import { } from 'src/engine/api/graphql/graphql-query-runner/errors/graphql-query-runner.exception'; import { ObjectRecordsToGraphqlConnectionHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/object-records-to-graphql-connection.helper'; import { ProcessAggregateHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/process-aggregate.helper'; +import { buildColumnsToSelect } from 'src/engine/api/graphql/graphql-query-runner/utils/build-columns-to-select'; import { getCursor, getPaginationInfo, @@ -120,7 +121,16 @@ export class GraphqlQueryFindManyResolverService extends GraphqlQueryBaseResolve const limit = executionArgs.args.first ?? executionArgs.args.last ?? QUERY_MAX_RECORDS; + const columnsToSelect = buildColumnsToSelect({ + select: executionArgs.graphqlQuerySelectedFieldsResult.select, + relations: executionArgs.graphqlQuerySelectedFieldsResult.relations, + objectMetadataItemWithFieldMaps, + }); + const nonFormattedObjectRecords = await queryBuilder + .setFindOptions({ + select: columnsToSelect, + }) .take(limit + 1) .getMany(); @@ -156,6 +166,7 @@ export class GraphqlQueryFindManyResolverService extends GraphqlQueryBaseResolve workspaceDataSource: executionArgs.workspaceDataSource, roleId, shouldBypassPermissionChecks: executionArgs.isExecutedByApiKey, + selectedFields: executionArgs.graphqlQuerySelectedFieldsResult.select, }); } diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-find-one-resolver.service.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-find-one-resolver.service.ts index 860148f38..1cbe5c077 100644 --- a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-find-one-resolver.service.ts +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-find-one-resolver.service.ts @@ -18,6 +18,7 @@ import { GraphqlQueryRunnerExceptionCode, } from 'src/engine/api/graphql/graphql-query-runner/errors/graphql-query-runner.exception'; import { ObjectRecordsToGraphqlConnectionHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/object-records-to-graphql-connection.helper'; +import { buildColumnsToSelect } from 'src/engine/api/graphql/graphql-query-runner/utils/build-columns-to-select'; import { WorkspaceQueryRunnerException, WorkspaceQueryRunnerExceptionCode, @@ -52,7 +53,17 @@ export class GraphqlQueryFindOneResolverService extends GraphqlQueryBaseResolver executionArgs.args.filter ?? ({} as ObjectRecordFilter), ); - const nonFormattedObjectRecord = await queryBuilder.getOne(); + const columnsToSelect = buildColumnsToSelect({ + select: executionArgs.graphqlQuerySelectedFieldsResult.select, + relations: executionArgs.graphqlQuerySelectedFieldsResult.relations, + objectMetadataItemWithFieldMaps, + }); + + const nonFormattedObjectRecord = await queryBuilder + .setFindOptions({ + select: columnsToSelect, + }) + .getOne(); const objectRecord = formatResult( nonFormattedObjectRecord, @@ -80,6 +91,7 @@ export class GraphqlQueryFindOneResolverService extends GraphqlQueryBaseResolver workspaceDataSource: executionArgs.workspaceDataSource, roleId, shouldBypassPermissionChecks: executionArgs.isExecutedByApiKey, + selectedFields: executionArgs.graphqlQuerySelectedFieldsResult.select, }); } diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-restore-many-resolver.service.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-restore-many-resolver.service.ts index 501d33c2a..a91057c45 100644 --- a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-restore-many-resolver.service.ts +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-restore-many-resolver.service.ts @@ -11,11 +11,12 @@ import { WorkspaceQueryRunnerOptions } from 'src/engine/api/graphql/workspace-qu import { RestoreManyResolverArgs } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface'; import { ObjectRecordsToGraphqlConnectionHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/object-records-to-graphql-connection.helper'; +import { buildColumnsToReturn } from 'src/engine/api/graphql/graphql-query-runner/utils/build-columns-to-return'; import { assertIsValidUuid } from 'src/engine/api/graphql/workspace-query-runner/utils/assert-is-valid-uuid.util'; import { assertMutationNotOnRemoteObject } from 'src/engine/metadata-modules/object-metadata/utils/assert-mutation-not-on-remote-object.util'; +import { getObjectMetadataFromObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/utils/get-object-metadata-from-object-metadata-Item-with-field-maps'; import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util'; import { computeTableName } from 'src/engine/utils/compute-table-name.util'; -import { getObjectMetadataFromObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/utils/get-object-metadata-from-object-metadata-Item-with-field-maps'; @Injectable() export class GraphqlQueryRestoreManyResolverService extends GraphqlQueryBaseResolverService< @@ -45,9 +46,15 @@ export class GraphqlQueryRestoreManyResolverService extends GraphqlQueryBaseReso executionArgs.args.filter, ); + const columnsToReturn = buildColumnsToReturn({ + select: executionArgs.graphqlQuerySelectedFieldsResult.select, + relations: executionArgs.graphqlQuerySelectedFieldsResult.relations, + objectMetadataItemWithFieldMaps, + }); + const nonFormattedRestoredObjectRecords = await queryBuilder .restore() - .returning('*') + .returning(columnsToReturn) .execute(); const formattedRestoredRecords = formatResult( @@ -75,6 +82,7 @@ export class GraphqlQueryRestoreManyResolverService extends GraphqlQueryBaseReso workspaceDataSource: executionArgs.workspaceDataSource, roleId, shouldBypassPermissionChecks: executionArgs.isExecutedByApiKey, + selectedFields: executionArgs.graphqlQuerySelectedFieldsResult.select, }); } diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-restore-one-resolver.service.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-restore-one-resolver.service.ts index b8dbbde08..7381d486f 100644 --- a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-restore-one-resolver.service.ts +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-restore-one-resolver.service.ts @@ -15,6 +15,7 @@ import { GraphqlQueryRunnerExceptionCode, } from 'src/engine/api/graphql/graphql-query-runner/errors/graphql-query-runner.exception'; import { ObjectRecordsToGraphqlConnectionHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/object-records-to-graphql-connection.helper'; +import { buildColumnsToReturn } from 'src/engine/api/graphql/graphql-query-runner/utils/build-columns-to-return'; import { assertIsValidUuid } from 'src/engine/api/graphql/workspace-query-runner/utils/assert-is-valid-uuid.util'; import { assertMutationNotOnRemoteObject } from 'src/engine/metadata-modules/object-metadata/utils/assert-mutation-not-on-remote-object.util'; import { getObjectMetadataFromObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/utils/get-object-metadata-from-object-metadata-Item-with-field-maps'; @@ -37,10 +38,16 @@ export class GraphqlQueryRestoreOneResolverService extends GraphqlQueryBaseResol objectMetadataItemWithFieldMaps.nameSingular, ); + const columnsToReturn = buildColumnsToReturn({ + select: executionArgs.graphqlQuerySelectedFieldsResult.select, + relations: executionArgs.graphqlQuerySelectedFieldsResult.relations, + objectMetadataItemWithFieldMaps, + }); + const nonFormattedRestoredObjectRecords = await queryBuilder .restore() .where({ id: executionArgs.args.id }) - .returning('*') + .returning(columnsToReturn) .execute(); const formattedRestoredRecords = formatResult( @@ -77,6 +84,7 @@ export class GraphqlQueryRestoreOneResolverService extends GraphqlQueryBaseResol workspaceDataSource: executionArgs.workspaceDataSource, roleId, shouldBypassPermissionChecks: executionArgs.isExecutedByApiKey, + selectedFields: executionArgs.graphqlQuerySelectedFieldsResult.select, }); } diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-update-many-resolver.service.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-update-many-resolver.service.ts index f1e5e324b..f32c8efe1 100644 --- a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-update-many-resolver.service.ts +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-update-many-resolver.service.ts @@ -16,12 +16,13 @@ import { GraphqlQueryRunnerExceptionCode, } from 'src/engine/api/graphql/graphql-query-runner/errors/graphql-query-runner.exception'; import { ObjectRecordsToGraphqlConnectionHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/object-records-to-graphql-connection.helper'; +import { buildColumnsToReturn } from 'src/engine/api/graphql/graphql-query-runner/utils/build-columns-to-return'; import { assertIsValidUuid } from 'src/engine/api/graphql/workspace-query-runner/utils/assert-is-valid-uuid.util'; import { assertMutationNotOnRemoteObject } from 'src/engine/metadata-modules/object-metadata/utils/assert-mutation-not-on-remote-object.util'; +import { getObjectMetadataFromObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/utils/get-object-metadata-from-object-metadata-Item-with-field-maps'; import { formatData } from 'src/engine/twenty-orm/utils/format-data.util'; import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util'; import { computeTableName } from 'src/engine/utils/compute-table-name.util'; -import { getObjectMetadataFromObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/utils/get-object-metadata-from-object-metadata-Item-with-field-maps'; @Injectable() export class GraphqlQueryUpdateManyResolverService extends GraphqlQueryBaseResolverService< @@ -79,9 +80,15 @@ export class GraphqlQueryUpdateManyResolverService extends GraphqlQueryBaseResol objectMetadataItemWithFieldMaps, ); + const columnsToReturn = buildColumnsToReturn({ + select: executionArgs.graphqlQuerySelectedFieldsResult.select, + relations: executionArgs.graphqlQuerySelectedFieldsResult.relations, + objectMetadataItemWithFieldMaps, + }); + const nonFormattedUpdatedObjectRecords = await queryBuilder .update(data) - .returning('*') + .returning(columnsToReturn) .execute(); const formattedUpdatedRecords = formatResult( @@ -114,6 +121,7 @@ export class GraphqlQueryUpdateManyResolverService extends GraphqlQueryBaseResol workspaceDataSource: executionArgs.workspaceDataSource, roleId, shouldBypassPermissionChecks: executionArgs.isExecutedByApiKey, + selectedFields: executionArgs.graphqlQuerySelectedFieldsResult.select, }); } diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-update-one-resolver.service.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-update-one-resolver.service.ts index 3b5d2a40a..b57c66cf7 100644 --- a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-update-one-resolver.service.ts +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-update-one-resolver.service.ts @@ -16,6 +16,7 @@ import { GraphqlQueryRunnerExceptionCode, } from 'src/engine/api/graphql/graphql-query-runner/errors/graphql-query-runner.exception'; import { ObjectRecordsToGraphqlConnectionHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/object-records-to-graphql-connection.helper'; +import { buildColumnsToReturn } from 'src/engine/api/graphql/graphql-query-runner/utils/build-columns-to-return'; import { assertIsValidUuid } from 'src/engine/api/graphql/workspace-query-runner/utils/assert-is-valid-uuid.util'; import { assertMutationNotOnRemoteObject } from 'src/engine/metadata-modules/object-metadata/utils/assert-mutation-not-on-remote-object.util'; import { getObjectMetadataFromObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/utils/get-object-metadata-from-object-metadata-Item-with-field-maps'; @@ -63,10 +64,16 @@ export class GraphqlQueryUpdateOneResolverService extends GraphqlQueryBaseResolv ); } + const columnsToReturn = buildColumnsToReturn({ + select: executionArgs.graphqlQuerySelectedFieldsResult.select, + relations: executionArgs.graphqlQuerySelectedFieldsResult.relations, + objectMetadataItemWithFieldMaps, + }); + const nonFormattedUpdatedObjectRecords = await queryBuilder .update(data) .where({ id: executionArgs.args.id }) - .returning('*') + .returning(columnsToReturn) .execute(); const formattedUpdatedRecords = formatResult( @@ -106,6 +113,7 @@ export class GraphqlQueryUpdateOneResolverService extends GraphqlQueryBaseResolv workspaceDataSource: executionArgs.workspaceDataSource, roleId, shouldBypassPermissionChecks: executionArgs.isExecutedByApiKey, + selectedFields: executionArgs.graphqlQuerySelectedFieldsResult.select, }); } diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/utils/__tests__/build-columns-to-select.spec.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/utils/__tests__/build-columns-to-select.spec.ts new file mode 100644 index 000000000..84578ea05 --- /dev/null +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/utils/__tests__/build-columns-to-select.spec.ts @@ -0,0 +1,266 @@ +import { FieldMetadataType } from 'twenty-shared/types'; + +import { RelationType } from 'src/engine/metadata-modules/field-metadata/interfaces/relation-type.interface'; + +import { buildColumnsToSelect } from 'src/engine/api/graphql/graphql-query-runner/utils/build-columns-to-select'; + +describe('buildColumnsToSelect', () => { + const mockObjectMetadataItemWithFieldMaps: any = { + id: 'a55d8cad-4c3d-4c8a-82b5-539a36de5605', + standardId: '20202020-e674-48e5-a542-72570eee7213', + dataSourceId: 'd161a12f-1daa-4a0b-887c-96a48a3d9ecf', + nameSingular: 'person', + namePlural: 'people', + labelSingular: 'Person', + labelPlural: 'People', + description: 'A person', + icon: 'IconUser', + standardOverrides: null, + targetTableName: 'DEPRECATED', + isCustom: false, + isRemote: false, + isActive: true, + isSystem: false, + isAuditLogged: true, + isSearchable: true, + duplicateCriteria: [ + ['nameFirstName', 'nameLastName'], + ['linkedinLinkPrimaryLinkUrl'], + ['emailsPrimaryEmail'], + ], + shortcut: 'P', + labelIdentifierFieldMetadataId: '92414583-c6a6-4c98-bae7-6ce318bd3423', + imageIdentifierFieldMetadataId: '6bfcecc4-1866-4254-ba6b-6f22246819bb', + isLabelSyncedWithName: false, + workspaceId: '20202020-1c25-4d02-bf25-6aeccf7ea419', + createdAt: new Date('2025-07-10T10:58:54.536Z'), + updatedAt: new Date('2025-07-10T10:58:54.536Z'), + indexMetadatas: [], + fieldsById: { + '92414583-c6a6-4c98-bae7-6ce318bd3423': { + id: '92414583-c6a6-4c98-bae7-6ce318bd3423', + type: FieldMetadataType.FULL_NAME, + name: 'name', + label: 'Name', + defaultValue: { + lastName: "''", + firstName: "''", + }, + description: "Contact's name", + icon: 'IconUser', + standardOverrides: null, + options: undefined, + settings: undefined, + isCustom: false, + isActive: true, + isSystem: false, + isNullable: true, + isUnique: false, + workspaceId: '20202020-1c25-4d02-bf25-6aeccf7ea419', + isLabelSyncedWithName: true, + relationTargetFieldMetadataId: undefined, + relationTargetObjectMetadataId: undefined, + objectMetadataId: 'a55d8cad-4c3d-4c8a-82b5-539a36de5605', + createdAt: new Date('2025-07-10T10:58:54.536Z'), + updatedAt: new Date('2025-07-10T10:58:54.536Z'), + }, + '30dc1370-bb1a-42e3-abbc-a40edf1c6796': { + id: '30dc1370-bb1a-42e3-abbc-a40edf1c6796', + type: FieldMetadataType.RELATION, + name: 'company', + label: 'Company', + defaultValue: null, + description: "Contact's company", + icon: 'IconBuildingSkyscraper', + standardOverrides: null, + options: undefined, + settings: { + relationType: RelationType.MANY_TO_ONE, + joinColumnName: 'companyId', + }, + isCustom: false, + isActive: true, + isSystem: false, + isNullable: true, + isUnique: false, + workspaceId: '20202020-1c25-4d02-bf25-6aeccf7ea419', + isLabelSyncedWithName: true, + relationTargetFieldMetadataId: '83cd9e7f-dfbc-4b93-a0a7-4c04b76d2009', + relationTargetObjectMetadataId: '9af20778-2f2c-4e22-ae83-2e77e479b57c', + objectMetadataId: 'a55d8cad-4c3d-4c8a-82b5-539a36de5605', + createdAt: new Date('2025-07-10T10:58:54.536Z'), + updatedAt: new Date('2025-07-10T10:58:54.536Z'), + }, + }, + fieldIdByName: { + name: '92414583-c6a6-4c98-bae7-6ce318bd3423', + company: '30dc1370-bb1a-42e3-abbc-a40edf1c6796', + }, + fieldIdByJoinColumnName: { + companyId: '30dc1370-bb1a-42e3-abbc-a40edf1c6796', + }, + }; + + it('should build columns to select with relation fields', () => { + const select = { + nameFirstName: true, + company: { + id: true, + name: true, + }, + }; + + const relations = { + company: {}, + }; + + const result = buildColumnsToSelect({ + select, + relations, + objectMetadataItemWithFieldMaps: mockObjectMetadataItemWithFieldMaps, + }); + + expect(result).toEqual({ + nameFirstName: true, + companyId: true, + id: true, + }); + }); + + it('should build columns to select without relation fields', () => { + const select = { + nameFirstName: true, + nameLastName: true, + }; + + const relations = {}; + + const result = buildColumnsToSelect({ + select, + relations, + objectMetadataItemWithFieldMaps: mockObjectMetadataItemWithFieldMaps, + }); + + expect(result).toEqual({ + nameFirstName: true, + nameLastName: true, + id: true, + }); + }); + + it('should filter out non-boolean values from select', () => { + const select = { + nameFirstName: true, + nameLastName: false, + company: { id: true }, + }; + + const relations = {}; + + const result = buildColumnsToSelect({ + select, + relations, + objectMetadataItemWithFieldMaps: mockObjectMetadataItemWithFieldMaps, + }); + + expect(result).toEqual({ + nameFirstName: true, + id: true, + }); + }); + + it('should handle relation field that is not a relation type', () => { + const select = { + nameFirstName: true, + }; + + const relations = { + name: {}, // This is not a relation field + }; + + const result = buildColumnsToSelect({ + select, + relations, + objectMetadataItemWithFieldMaps: mockObjectMetadataItemWithFieldMaps, + }); + + expect(result).toEqual({ + nameFirstName: true, + id: true, + }); + }); + + it('should handle relation field that is not MANY_TO_ONE', () => { + const mockObjectMetadataWithOneToMany: any = { + ...mockObjectMetadataItemWithFieldMaps, + fieldsById: { + ...mockObjectMetadataItemWithFieldMaps.fieldsById, + '30dc1370-bb1a-42e3-abbc-a40edf1c6796': { + ...mockObjectMetadataItemWithFieldMaps.fieldsById[ + '30dc1370-bb1a-42e3-abbc-a40edf1c6796' + ], + settings: { + relationType: RelationType.ONE_TO_MANY, + joinColumnName: 'companyId', + }, + }, + }, + }; + + const select = { + nameFirstName: true, + }; + + const relations = { + company: {}, + }; + + const result = buildColumnsToSelect({ + select, + relations, + objectMetadataItemWithFieldMaps: mockObjectMetadataWithOneToMany, + }); + + expect(result).toEqual({ + nameFirstName: true, + id: true, + }); + }); + + it('should handle relation field without joinColumnName', () => { + const mockObjectMetadataWithoutJoinColumn: any = { + ...mockObjectMetadataItemWithFieldMaps, + fieldsById: { + ...mockObjectMetadataItemWithFieldMaps.fieldsById, + '30dc1370-bb1a-42e3-abbc-a40edf1c6796': { + ...mockObjectMetadataItemWithFieldMaps.fieldsById[ + '30dc1370-bb1a-42e3-abbc-a40edf1c6796' + ], + settings: { + relationType: RelationType.MANY_TO_ONE, + joinColumnName: null, + }, + }, + }, + }; + + const select = { + nameFirstName: true, + }; + + const relations = { + company: {}, + }; + + const result = buildColumnsToSelect({ + select, + relations, + objectMetadataItemWithFieldMaps: mockObjectMetadataWithoutJoinColumn, + }); + + expect(result).toEqual({ + nameFirstName: true, + id: true, + }); + }); +}); diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/utils/build-columns-to-return.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/utils/build-columns-to-return.ts new file mode 100644 index 000000000..79826892d --- /dev/null +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/utils/build-columns-to-return.ts @@ -0,0 +1,22 @@ +import { buildColumnsToSelect } from 'src/engine/api/graphql/graphql-query-runner/utils/build-columns-to-select'; +import { ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps'; + +export const buildColumnsToReturn = ({ + select, + relations, + objectMetadataItemWithFieldMaps, +}: { + select: Record; + relations: Record; + objectMetadataItemWithFieldMaps: ObjectMetadataItemWithFieldMaps; +}): string[] => { + return Object.entries( + buildColumnsToSelect({ + select, + relations, + objectMetadataItemWithFieldMaps, + }), + ) + .filter(([_columnName, value]) => value === true) + .map(([columnName]) => columnName); +}; diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/utils/build-columns-to-select.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/utils/build-columns-to-select.ts new file mode 100644 index 000000000..cf8e0f79f --- /dev/null +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/utils/build-columns-to-select.ts @@ -0,0 +1,74 @@ +import { FieldMetadataType } from 'twenty-shared/types'; + +import { RelationType } from 'src/engine/metadata-modules/field-metadata/interfaces/relation-type.interface'; + +import { InternalServerError } from 'src/engine/core-modules/graphql/utils/graphql-errors.util'; +import { ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps'; +import { isFieldMetadataInterfaceOfType } from 'src/engine/utils/is-field-metadata-of-type.util'; + +export const buildColumnsToSelect = ({ + select, + relations, + objectMetadataItemWithFieldMaps, +}: { + select: Record; + relations: Record; + objectMetadataItemWithFieldMaps: ObjectMetadataItemWithFieldMaps; +}) => { + const requiredRelationColumns = getRequiredRelationColumns( + relations, + objectMetadataItemWithFieldMaps, + ); + + const fieldsToSelect: Record = Object.entries(select) + .filter( + ([_columnName, value]) => value === true && typeof value !== 'object', + ) + .reduce((acc, [columnName]) => ({ ...acc, [columnName]: true }), {}); + + for (const columnName of requiredRelationColumns) { + fieldsToSelect[columnName] = true; + } + + return { ...fieldsToSelect, id: true }; +}; + +const getRequiredRelationColumns = ( + relations: Record, + objectMetadataItem: ObjectMetadataItemWithFieldMaps, +): string[] => { + const requiredColumns: string[] = []; + + for (const [relationFieldName, _] of Object.entries(relations)) { + const fieldMetadataId = objectMetadataItem.fieldIdByName[relationFieldName]; + + if (!fieldMetadataId) { + throw new InternalServerError( + `Field metadata not found for relation field name: ${relationFieldName}`, + ); + } + + const fieldMetadata = objectMetadataItem.fieldsById[fieldMetadataId]; + + if (!fieldMetadata) { + throw new InternalServerError( + `Field metadata not found for relation field name: ${relationFieldName}`, + ); + } + + if ( + !isFieldMetadataInterfaceOfType(fieldMetadata, FieldMetadataType.RELATION) + ) { + continue; + } + + if ( + fieldMetadata.settings?.relationType === RelationType.MANY_TO_ONE && + fieldMetadata.settings?.joinColumnName + ) { + requiredColumns.push(fieldMetadata.settings.joinColumnName); + } + } + + return requiredColumns; +}; diff --git a/packages/twenty-server/src/engine/twenty-orm/repository/workspace-select-query-builder.ts b/packages/twenty-server/src/engine/twenty-orm/repository/workspace-select-query-builder.ts index c67acd7be..5273a1e3d 100644 --- a/packages/twenty-server/src/engine/twenty-orm/repository/workspace-select-query-builder.ts +++ b/packages/twenty-server/src/engine/twenty-orm/repository/workspace-select-query-builder.ts @@ -32,6 +32,10 @@ export class WorkspaceSelectQueryBuilder< this.shouldBypassPermissionChecks = shouldBypassPermissionChecks; } + getFindOptions() { + return this.findOptions; + } + override clone(): this { const clonedQueryBuilder = super.clone();