From 0145089ff7b44491e0a57b32cb8d51b54f2bc1da Mon Sep 17 00:00:00 2001 From: Franklin Wang Date: Thu, 7 Dec 2023 10:41:03 +1300 Subject: [PATCH] Support single source ID in new batch hydrator --- .../hydration/batch/NadelNewBatchHydrator.kt | 30 ++- ...source-ids-going-to-different-services.kt} | 0 .../hooks/new-batching-single-source-id.kt | 53 ++++ .../new-batching-single-source-id.yml | 235 ++++++++++++++++++ 4 files changed, 309 insertions(+), 9 deletions(-) rename test/src/test/kotlin/graphql/nadel/tests/hooks/{`new-batching-multiple-source-ids-going-to-different-services`.kt => new-batching-multiple-source-ids-going-to-different-services.kt} (100%) create mode 100644 test/src/test/kotlin/graphql/nadel/tests/hooks/new-batching-single-source-id.kt create mode 100644 test/src/test/resources/fixtures/hydration/new batching/new-batching-single-source-id.yml diff --git a/lib/src/main/java/graphql/nadel/engine/transform/hydration/batch/NadelNewBatchHydrator.kt b/lib/src/main/java/graphql/nadel/engine/transform/hydration/batch/NadelNewBatchHydrator.kt index 22459f3b2..7bd8168ac 100644 --- a/lib/src/main/java/graphql/nadel/engine/transform/hydration/batch/NadelNewBatchHydrator.kt +++ b/lib/src/main/java/graphql/nadel/engine/transform/hydration/batch/NadelNewBatchHydrator.kt @@ -231,15 +231,16 @@ internal class NadelNewBatchHydrator( fieldName = state.hydratedField.name, ) + val fieldSource = instructions + .first() + .actorInputValueDefs + .asSequence() + .map { + it.valueSource + } + .singleOfType() + return if (executionBlueprint.engineSchema.getField(coords)!!.type.unwrapNonNull().isList) { - val fieldSource = instructions - .first() - .actorInputValueDefs - .asSequence() - .map { - it.valueSource - } - .singleOfType() // todo: move this to validation instructions @@ -261,7 +262,18 @@ internal class NadelNewBatchHydrator( sourceId to instruction } } else { - throw UnsupportedOperationException("todo") + // todo: determine what to do here in the longer term, this hook should probably be replaced + val instruction = state.executionContext.hooks.getHydrationInstruction( + instructions = instructions, + parentNode = parentNode, + aliasHelper = state.aliasHelper, + userContext = state.executionContext.userContext, + )!! + + extractValues(parentNode, fieldSource, state.aliasHelper) + .map { sourceId -> + sourceId to instruction + } } } diff --git a/test/src/test/kotlin/graphql/nadel/tests/hooks/`new-batching-multiple-source-ids-going-to-different-services`.kt b/test/src/test/kotlin/graphql/nadel/tests/hooks/new-batching-multiple-source-ids-going-to-different-services.kt similarity index 100% rename from test/src/test/kotlin/graphql/nadel/tests/hooks/`new-batching-multiple-source-ids-going-to-different-services`.kt rename to test/src/test/kotlin/graphql/nadel/tests/hooks/new-batching-multiple-source-ids-going-to-different-services.kt diff --git a/test/src/test/kotlin/graphql/nadel/tests/hooks/new-batching-single-source-id.kt b/test/src/test/kotlin/graphql/nadel/tests/hooks/new-batching-single-source-id.kt new file mode 100644 index 000000000..c4f581010 --- /dev/null +++ b/test/src/test/kotlin/graphql/nadel/tests/hooks/new-batching-single-source-id.kt @@ -0,0 +1,53 @@ +package graphql.nadel.tests.hooks + +import graphql.nadel.Nadel +import graphql.nadel.NadelExecutionHints +import graphql.nadel.engine.blueprint.NadelGenericHydrationInstruction +import graphql.nadel.engine.blueprint.hydration.NadelHydrationActorInputDef.ValueSource +import graphql.nadel.engine.transform.artificial.NadelAliasHelper +import graphql.nadel.engine.transform.result.json.JsonNode +import graphql.nadel.engine.transform.result.json.JsonNodeExtractor +import graphql.nadel.engine.util.singleOfType +import graphql.nadel.hooks.NadelExecutionHooks +import graphql.nadel.tests.EngineTestHook +import graphql.nadel.tests.UseHook + +@UseHook +class `new-batching-single-source-id` : EngineTestHook { + override fun makeExecutionHints(builder: NadelExecutionHints.Builder): NadelExecutionHints.Builder { + return super.makeExecutionHints(builder) + .newBatchHydrationGrouping { true } + } + + override fun makeNadel(builder: Nadel.Builder): Nadel.Builder { + return super.makeNadel(builder) + .executionHooks( + object : NadelExecutionHooks { + override fun getHydrationInstruction( + instructions: List, + parentNode: JsonNode, + aliasHelper: NadelAliasHelper, + userContext: Any?, + ): T? { + return instructions + .single { instruction -> + val fieldSource = instruction.actorInputValueDefs + .asSequence() + .map { + it.valueSource + } + .singleOfType() + + val sourceNodes = JsonNodeExtractor.getNodesAt( + parentNode, + aliasHelper.getQueryPath(fieldSource.queryPathToField) + ) + val sourceIdType = (sourceNodes.single().value as String).substringBefore("/") + + instruction.actorService.name.startsWith(sourceIdType) + } + } + }, + ) + } +} diff --git a/test/src/test/resources/fixtures/hydration/new batching/new-batching-single-source-id.yml b/test/src/test/resources/fixtures/hydration/new batching/new-batching-single-source-id.yml new file mode 100644 index 000000000..258ba8995 --- /dev/null +++ b/test/src/test/resources/fixtures/hydration/new batching/new-batching-single-source-id.yml @@ -0,0 +1,235 @@ +name: "new batching single source id" +enabled: true +overallSchema: + # language=GraphQL + activity: | + type Query { + activity: [Activity] + } + union ActivityContent = Issue | Comment + type Activity { + id: ID! + contentId: ID! + content: ActivityContent + @hydrated( + service: "comments" + field: "commentsByIds" + arguments: [ + {name: "ids" value: "$source.contentId"} + ] + ) + @hydrated( + service: "issues" + field: "issuesByIds" + arguments: [ + {name: "ids" value: "$source.contentId"} + ] + ) + } + # language=GraphQL + issues: | + type Query { + issuesByIds(ids: [ID!]!): [Issue!] + } + type Issue { + id: ID! + title: String + } + # language=GraphQL + comments: | + type Query { + commentsByIds(ids: [ID!]!): [Comment!] + } + type Comment { + id: ID! + content: String + } +underlyingSchema: + # language=GraphQL + activity: | + type Query { + activity: [Activity] + } + type Activity { + id: ID! + contentId: ID! + } + # language=GraphQL + issues: | + type Query { + issuesByIds(ids: [ID!]!): [Issue!] + } + type Issue { + id: ID! + title: String + } + # language=GraphQL + comments: | + type Query { + commentsByIds(ids: [ID!]!): [Comment!] + } + type Comment { + id: ID! + content: String + } +# language=GraphQL +query: | + { + activity { + content { + __typename + ... on Issue { + id + title + } + ... on Comment { + id + content + } + } + } + } +variables: { } +serviceCalls: + - serviceName: "activity" + request: + # language=GraphQL + query: | + { + activity { + __typename__batch_hydration__content: __typename + batch_hydration__content__contentId: contentId + batch_hydration__content__contentId: contentId + } + } + variables: { } + # language=JSON + response: |- + { + "data": { + "activity": [ + { + "__typename__batch_hydration__content": "Activity", + "batch_hydration__content__contentId": "issue/4000" + }, + { + "__typename__batch_hydration__content": "Activity", + "batch_hydration__content__contentId": "issue/8080" + }, + { + "__typename__batch_hydration__content": "Activity", + "batch_hydration__content__contentId": "issue/7496" + }, + { + "__typename__batch_hydration__content": "Activity", + "batch_hydration__content__contentId": "comment/1234" + } + ] + }, + "extensions": {} + } + - serviceName: "issues" + request: + # language=GraphQL + query: | + { + issuesByIds(ids: ["issue/4000", "issue/8080", "issue/7496"]) { + __typename + id + batch_hydration__content__id: id + title + } + } + variables: { } + # language=JSON + response: |- + { + "data": { + "issuesByIds": [ + { + "__typename": "Issue", + "id": "issue/4000", + "batch_hydration__content__id": "issue/4000", + "title": "Four Thousand" + }, + { + "__typename": "Issue", + "id": "issue/8080", + "batch_hydration__content__id": "issue/8080", + "title": "Eighty Eighty" + }, + { + "__typename": "Issue", + "id": "issue/7496", + "batch_hydration__content__id": "issue/7496", + "title": "Seven Four Nine Six" + } + ] + }, + "extensions": {} + } + - serviceName: "comments" + request: + # language=GraphQL + query: | + { + commentsByIds(ids: ["comment/1234"]) { + __typename + content + id + batch_hydration__content__id: id + } + } + variables: { } + # language=JSON + response: |- + { + "data": { + "commentsByIds": [ + { + "__typename": "Comment", + "id": "comment/1234", + "batch_hydration__content__id": "comment/1234", + "content": "One Two Three Four" + } + ] + }, + "extensions": {} + } +# language=JSON +response: |- + { + "data": { + "activity": [ + { + "content": { + "__typename": "Issue", + "id": "issue/4000", + "title": "Four Thousand" + } + }, + { + "content": { + "__typename": "Issue", + "id": "issue/8080", + "title": "Eighty Eighty" + } + }, + { + "content": { + "__typename": "Issue", + "id": "issue/7496", + "title": "Seven Four Nine Six" + } + }, + { + "content": { + "__typename": "Comment", + "id": "comment/1234", + "content": "One Two Three Four" + } + } + ] + }, + "errors": [] + }