From d0f88756c34c5a3375a666df091c09b7597f2293 Mon Sep 17 00:00:00 2001 From: prathamesh0 <42446521+prathamesh0@users.noreply.github.com> Date: Mon, 5 Aug 2024 16:32:40 +0530 Subject: [PATCH] Handle object and list filters on nested GQL selections and update codegen (#532) * Handle object type for where clause on nested GQL selections * Handle list type for where clause on nested GQL selections * Generate GQL schema types with arguments on plural fields --- packages/codegen/src/schema.ts | 45 +++++++++++++++++++--- packages/util/src/graph/database.ts | 58 ++++++++++++++--------------- 2 files changed, 68 insertions(+), 35 deletions(-) diff --git a/packages/codegen/src/schema.ts b/packages/codegen/src/schema.ts index f5800905..b03e716a 100644 --- a/packages/codegen/src/schema.ts +++ b/packages/codegen/src/schema.ts @@ -189,6 +189,8 @@ export class Schema { } _addSubgraphSchemaQueries (subgraphTypeDefs: ReadonlyArray): void { + const subgraphTypeArgsMap = new Map(); + for (const subgraphTypeDef of subgraphTypeDefs) { // Filtering out enums. if (subgraphTypeDef.kind !== 'ObjectTypeDefinition') { @@ -303,21 +305,30 @@ export class Schema { let pluralQueryName = pluralize(queryName); pluralQueryName = (pluralQueryName === queryName) ? `${pluralQueryName}s` : pluralQueryName; + const queryArgs = { + where: `${subgraphType}_filter`, + orderBy: subgraphTypeOrderByEnum, + orderDirection: ORDER_DIRECTION, + first: { type: GraphQLInt, defaultValue: 100 }, + skip: { type: GraphQLInt, defaultValue: 0 } + }; + queryObject[pluralQueryName] = { // Get type composer object for return type from the schema composer. type: this._composer.getAnyTC(subgraphType).NonNull.List.NonNull, args: { block: BLOCK_HEIGHT, - where: `${subgraphType}_filter`, - orderBy: subgraphTypeOrderByEnum, - orderDirection: ORDER_DIRECTION, - first: { type: GraphQLInt, defaultValue: 100 }, - skip: { type: GraphQLInt, defaultValue: 0 } + ...queryArgs } }; - this._composer.Query.addFields(queryObject); + + // Save the args for this type in a map (type -> args) for further usage. + subgraphTypeArgsMap.set(subgraphType, queryArgs); } + + // Add args on plural fields for subgraph types. + this._addSubgraphPluralFieldArgs(subgraphTypeDefs, subgraphTypeArgsMap); } _getDetailsForSubgraphField (fieldType: ComposeOutputType): { @@ -381,6 +392,28 @@ export class Schema { }); } + _addSubgraphPluralFieldArgs (subgraphTypeDefs: ReadonlyArray, subgraphTypeArgsMap: Map): void { + for (const subgraphTypeDef of subgraphTypeDefs) { + // Filtering out enums. + if (subgraphTypeDef.kind !== 'ObjectTypeDefinition') { + continue; + } + + const subgraphType = subgraphTypeDef.name.value; + const subgraphTypeComposer = this._composer.getOTC(subgraphType); + + // Process each field on the type. + Object.entries(subgraphTypeComposer.getFields()).forEach(([fieldName, field]) => { + const { isArray, entityType } = this._getDetailsForSubgraphField(field.type); + + // Set args if it's a plural field of some entity type. + if (entityType && isArray) { + subgraphTypeComposer.setFieldArgs(fieldName, subgraphTypeArgsMap.get(entityType) || {}); + } + }); + } + } + /** * Adds basic types to the schema and typemapping. */ diff --git a/packages/util/src/graph/database.ts b/packages/util/src/graph/database.ts index 27d1d63f..9bf72d27 100644 --- a/packages/util/src/graph/database.ts +++ b/packages/util/src/graph/database.ts @@ -17,7 +17,7 @@ import { } from 'typeorm'; import { ColumnMetadata } from 'typeorm/metadata/ColumnMetadata'; import { RawSqlResultsToEntityTransformer } from 'typeorm/query-builder/transformer/RawSqlResultsToEntityTransformer'; -import { ArgumentNode, FieldNode, GraphQLResolveInfo, SelectionNode, IntValueNode, EnumValueNode, ObjectValueNode, ObjectFieldNode } from 'graphql'; +import { ArgumentNode, FieldNode, GraphQLResolveInfo, SelectionNode, IntValueNode, EnumValueNode, ObjectValueNode, ObjectFieldNode, ValueNode } from 'graphql'; import _ from 'lodash'; import debug from 'debug'; @@ -281,7 +281,7 @@ export class GraphDatabase { childSelections = childSelections.filter(selection => !(selection.kind === 'Field' && selection.name.value === '__typename')); // Parse selection's arguments - let { where: relationWhere, queryOptions: relationQueryOptions } = this._getSelectionFieldArguments(selection, queryInfo); + let { where: relationWhere, queryOptions: relationQueryOptions } = this._getGQLSelectionFieldArguments(selection, queryInfo); if (isDerived) { const where: Where = { @@ -813,7 +813,7 @@ export class GraphDatabase { childSelections = childSelections.filter(selection => !(selection.kind === 'Field' && selection.name.value === '__typename')); // Parse selection's arguments - let { where: relationWhere, queryOptions: relationQueryOptions } = this._getSelectionFieldArguments(selection, queryInfo); + let { where: relationWhere, queryOptions: relationQueryOptions } = this._getGQLSelectionFieldArguments(selection, queryInfo); if (isDerived) { const where: Where = { @@ -1456,14 +1456,14 @@ export class GraphDatabase { }, []); } - _getSelectionFieldArguments (fieldNode: FieldNode, queryInfo: GraphQLResolveInfo): { where: Where, queryOptions: QueryOptions } { + _getGQLSelectionFieldArguments (fieldNode: FieldNode, queryInfo: GraphQLResolveInfo): { where: Where, queryOptions: QueryOptions } { let where: Where = {}; const queryOptions: QueryOptions = {}; fieldNode.arguments?.forEach((arg: ArgumentNode) => { switch (arg.name.value) { case 'where': - where = this.buildFilter(this._buildWhereFromArgumentNode(arg, queryInfo)); + where = this.buildFilter(this._buildWhereFromGQLArgValue((arg.value as ObjectValueNode), queryInfo)); break; case 'first': { @@ -1500,33 +1500,33 @@ export class GraphDatabase { return { where, queryOptions }; } - _buildWhereFromArgumentNode (arg: ArgumentNode, queryInfo: GraphQLResolveInfo): { [key: string]: any } { - // TODO: Handle all types of filters on nested fields - - return (arg.value as ObjectValueNode).fields.reduce((acc: { [key: string]: any }, fieldNode: ObjectFieldNode) => { - switch (fieldNode.value.kind) { - case 'BooleanValue' : - case 'EnumValue' : - case 'FloatValue' : - case 'IntValue' : - case 'StringValue' : - acc[fieldNode.name.value] = fieldNode.value.value; - break; + _buildWhereFromGQLArgValue (whereArgValue: ObjectValueNode, queryInfo: GraphQLResolveInfo): { [key: string]: any } { + return whereArgValue.fields.reduce((acc: { [key: string]: any }, fieldNode: ObjectFieldNode) => { + acc[fieldNode.name.value] = this._parseGQLFieldValue(fieldNode.value, queryInfo); + return acc; + }, {}); + } - case 'NullValue': - acc[fieldNode.name.value] = null; - break; + _parseGQLFieldValue (value: ValueNode, queryInfo: GraphQLResolveInfo): any { + switch (value.kind) { + case 'BooleanValue': + case 'EnumValue': + case 'FloatValue': + case 'IntValue': + case 'StringValue': + return value.value; - case 'Variable': - acc[fieldNode.name.value] = queryInfo.variableValues[fieldNode.value.name.value]; - break; + case 'NullValue': + return null; - case 'ListValue': - case 'ObjectValue': - throw new Error(`Nested filter type ${fieldNode.value.kind} not supported`); - } + case 'Variable': + return queryInfo.variableValues[value.name.value]; - return acc; - }, {}); + case 'ListValue': + return value.values.map((valueNode) => this._parseGQLFieldValue(valueNode, queryInfo)); + + case 'ObjectValue': + return this._buildWhereFromGQLArgValue(value, queryInfo); + } } }