Skip to content
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

Fix source input edge case #507

Merged
merged 1 commit into from
Jan 14, 2024
Merged
Show file tree
Hide file tree
Changes from all 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 @@ -258,8 +258,12 @@ internal class NadelNewBatchHydrator(
extractNode(sourceId, instruction)
}
} else {
val (sourceId, instruction) = sourceIdsPairedWithInstruction.single()
extractNode(sourceId, instruction)
if (sourceIdsPairedWithInstruction.isEmpty()) {
JsonNode.Null
} else {
val (sourceId, instruction) = sourceIdsPairedWithInstruction.single()
extractNode(sourceId, instruction)
}
}
} else {
if (sourceIdsPairedWithInstruction == null) {
Expand All @@ -272,8 +276,12 @@ internal class NadelNewBatchHydrator(
},
)
} else {
val (sourceId, instruction) = sourceIdsPairedWithInstruction.single()
extractNode(sourceId, instruction)
if (sourceIdsPairedWithInstruction.isEmpty()) {
JsonNode.Null
} else {
val (sourceId, instruction) = sourceIdsPairedWithInstruction.single()
extractNode(sourceId, instruction)
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package graphql.nadel.tests.hooks

import graphql.nadel.Nadel
import graphql.nadel.NadelExecutionHints
import graphql.nadel.engine.blueprint.NadelGenericHydrationInstruction
import graphql.nadel.engine.transform.artificial.NadelAliasHelper
import graphql.nadel.engine.transform.result.json.JsonNode
import graphql.nadel.hooks.NadelExecutionHooks
import graphql.nadel.tests.EngineTestHook
import graphql.nadel.tests.UseHook

@UseHook
class `new-batching-absent-source-input` : 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 <T : NadelGenericHydrationInstruction> getHydrationInstruction(
instructions: List<T>,
sourceId: JsonNode,
userContext: Any?,
): T {
val type = (sourceId.value as String).substringBefore("/")

return instructions
.first {
it.actorService.name.startsWith(type, ignoreCase = true)
}
}

override fun <T : NadelGenericHydrationInstruction> getHydrationInstruction(
instructions: List<T>,
parentNode: JsonNode,
aliasHelper: NadelAliasHelper,
userContext: Any?,
): T? {
val nodeString = parentNode.value.toString()

return when {
nodeString.contains("comment/") -> instructions.single {
it.actorFieldDef.name.contains("comment")
}
nodeString.contains("issue/") -> instructions.single {
it.actorFieldDef.name.contains("issue")
}
else -> null
}
}
},
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package graphql.nadel.tests.hooks

import graphql.nadel.Nadel
import graphql.nadel.NadelExecutionHints
import graphql.nadel.engine.blueprint.NadelGenericHydrationInstruction
import graphql.nadel.engine.transform.result.json.JsonNode
import graphql.nadel.hooks.NadelExecutionHooks
import graphql.nadel.tests.EngineTestHook
import graphql.nadel.tests.UseHook

@UseHook
class `new-batching-no-source-inputs` : 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 <T : NadelGenericHydrationInstruction> getHydrationInstruction(
instructions: List<T>,
sourceId: JsonNode,
userContext: Any?,
): T {
val type = (sourceId.value as String).substringBefore("/")

return instructions
.first {
it.actorService.name.startsWith(type, ignoreCase = true)
}
}
},
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,215 @@
name: "new batching absent source input"
enabled: true
# language=GraphQL
overallSchema:
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
}
# language=GraphQL
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"
Copy link
Contributor

Choose a reason for hiding this comment

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

that is not a legal answer, is it? I mean there is no fragment/type selector involved and therefore every key must be present.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yeah I was creating tests with different theories with how the bug was occurring.

I just left it in anyway. I don't think there's much harm.

(I eventually just pulled in the data from the logs and got an exact replication).

},
{
"__typename__batch_hydration__content": "Activity",
"batch_hydration__content__contentId": ""
},
{
"__typename__batch_hydration__content": "Activity",
"batch_hydration__content__contentId": "comment/9001"
},
{
"__typename__batch_hydration__content": "Activity",
"batch_hydration__content__contentId": "issue/1234"
}
]
},
"extensions": {}
}
- serviceName: "issues"
request:
# language=GraphQL
query: |
{
issuesByIds(ids: ["issue/1234"]) {
__typename
id
batch_hydration__content__id: id
title
}
}
variables: { }
# language=JSON
response: |-
{
"data": {
"issuesByIds": [
{
"__typename": "Issue",
"id": "issue/1234",
"batch_hydration__content__id": "issue/1234",
"title": "One Two Three Four"
}
]
},
"extensions": {}
}
- serviceName: "comments"
request:
# language=GraphQL
query: |
{
commentsByIds(ids: ["comment/9001"]) {
__typename
content
id
batch_hydration__content__id: id
}
}
variables: { }
# language=JSON
response: |-
{
"data": {
"commentsByIds": [
{
"__typename": "Comment",
"id": "comment/9001",
"batch_hydration__content__id": "comment/9001",
"content": "It's over 9000"
}
]
},
"extensions": {}
}
# language=JSON
response: |-
{
"data": {
"activity": [
{
"content": null
},
{
"content": null
},
{
"content": {
"__typename": "Comment",
"id": "comment/9001",
"content": "It's over 9000"
}
},
{
"content": {
"__typename": "Issue",
"id": "issue/1234",
"title": "One Two Three Four"
}
}
]
},
"errors": []
}
Loading
Loading