Skip to content

feat!: support fragments on query level - #650

Merged
stuebingerb merged 1 commit into
mainfrom
feature/support-fragments-on-query
Jun 16, 2026
Merged

feat!: support fragments on query level#650
stuebingerb merged 1 commit into
mainfrom
feature/support-fragments-on-query

Conversation

@stuebingerb

@stuebingerb stuebingerb commented Jun 14, 2026

Copy link
Copy Markdown
Owner

Resolves #406
Resolves #645

BREAKING CHANGE: ExecutionPlan, RequestInterpreter and ParallelRequestExecutor are no longer public classes

@coderabbitai

coderabbitai Bot commented Jun 14, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: a311380e-4d4b-4820-8827-b266d053923c

📥 Commits

Reviewing files that changed from the base of the PR and between dbb44e7 and e24fedb.

📒 Files selected for processing (8)
  • kgraphql/api/kgraphql.api
  • kgraphql/src/main/kotlin/de/stuebingerb/kgraphql/schema/execution/ExecutionPlan.kt
  • kgraphql/src/main/kotlin/de/stuebingerb/kgraphql/schema/execution/ParallelRequestExecutor.kt
  • kgraphql/src/main/kotlin/de/stuebingerb/kgraphql/schema/execution/RequestExecutor.kt
  • kgraphql/src/main/kotlin/de/stuebingerb/kgraphql/schema/structure/RequestInterpreter.kt
  • kgraphql/src/test/kotlin/de/stuebingerb/kgraphql/integration/BaseSchemaTest.kt
  • kgraphql/src/test/kotlin/de/stuebingerb/kgraphql/integration/QueryTest.kt
  • kgraphql/src/test/kotlin/de/stuebingerb/kgraphql/specification/language/FragmentsSpecificationTest.kt
💤 Files with no reviewable changes (1)
  • kgraphql/api/kgraphql.api
✅ Files skipped from review due to trivial changes (1)
  • kgraphql/src/main/kotlin/de/stuebingerb/kgraphql/schema/execution/RequestExecutor.kt
🚧 Files skipped from review as they are similar to previous changes (5)
  • kgraphql/src/test/kotlin/de/stuebingerb/kgraphql/integration/BaseSchemaTest.kt
  • kgraphql/src/main/kotlin/de/stuebingerb/kgraphql/schema/execution/ExecutionPlan.kt
  • kgraphql/src/test/kotlin/de/stuebingerb/kgraphql/integration/QueryTest.kt
  • kgraphql/src/main/kotlin/de/stuebingerb/kgraphql/schema/structure/RequestInterpreter.kt
  • kgraphql/src/main/kotlin/de/stuebingerb/kgraphql/schema/execution/ParallelRequestExecutor.kt

📝 Walkthrough

Summary by CodeRabbit

  • Breaking Changes

    • Removed several execution-related types from the exposed public API surface.
  • Refactor

    • Updated internal request execution planning and interpretation to streamline how selections, fragments, and variables are processed.
  • Bug Fixes

    • Improved fragment type-condition matching (including direct type equality) and refined error reporting for type mismatches and access checks.
    • Ensured nullable operation errors still allow partial results, while non-nullable errors correctly nullify top-level data.
  • Tests

    • Expanded fragment coverage for query, mutation, and subscription operations (including conditional spreads).
    • Split and strengthened nullable vs non-nullable error propagation tests.

Walkthrough

Marks ExecutionPlan, ParallelRequestExecutor, RequestExecutor, and RequestInterpreter as internal, removes them from the public API. Refactors ExecutionPlan to carry root and declaredVariables, rewrites ParallelRequestExecutor.executePlan for centralized parallel dispatch and spec-compliant non-nullable error propagation, fixes root-level fragment handling and type-condition matching, and adds tests.

Changes

Execution Internalization, Plan Refactor, and Behavioral Fixes

Layer / File(s) Summary
Public API surface reduction and visibility changes
kgraphql/api/kgraphql.api, kgraphql/src/main/kotlin/.../execution/RequestExecutor.kt, kgraphql/src/main/kotlin/.../execution/ParallelRequestExecutor.kt, kgraphql/src/main/kotlin/.../structure/RequestInterpreter.kt
Removes ExecutionPlan, ParallelRequestExecutor, RequestExecutor, and RequestInterpreter from the public API listing and adds internal modifier to each.
ExecutionPlan refactor and RequestInterpreter plan-building
kgraphql/src/main/kotlin/.../execution/ExecutionPlan.kt, kgraphql/src/main/kotlin/.../structure/RequestInterpreter.kt
ExecutionPlan drops isSubscription, changes operations element type to Execution, and gains root: Type and declaredVariables. RequestInterpreter.createExecutionPlan routes each selection through handleSelectionFieldOrFragment, removes the variables parameter from handleSelection, and sets variables to null on Execution.Node construction.
ParallelRequestExecutor executePlan dispatch and behavioral fixes
kgraphql/src/main/kotlin/.../execution/ParallelRequestExecutor.kt
suspendExecute delegates to a new executePlan that runs children in parallel under ExecutionMode.Normal, merges JSON results, and converts ExecutionError into a null data node. Fragment type-condition matching adds explicit equality check. Error messages include expected type name. Access control strips Type.OperationObject from parent. FunctionWrapper.invoke infers subscription state from schema.model.subscriptionType.
Tests for fragments and nullable/non-nullable error propagation
kgraphql/src/test/kotlin/.../integration/BaseSchemaTest.kt, kgraphql/src/test/kotlin/.../integration/QueryTest.kt, kgraphql/src/test/kotlin/.../specification/language/FragmentsSpecificationTest.kt
Adds testProperty3 to Inner. Splits the existing error-propagation test into nullable (partial data preserved) and non-nullable (top-level data becomes null) variants. Adds fragment tests at Query and Mutation root levels and updates conditional fragment expected payloads.

Possibly related PRs

  • stuebingerb/KGraphQL#611: Defines/moves the schema-level ErrorHandler that ParallelRequestExecutor.handleException delegates to, directly intersecting with this PR's error-propagation refactor.
  • stuebingerb/KGraphQL#649: Addresses insufficient validation of fragment type conditions in RequestInterpreter, directly overlapping with this PR's fragment type-condition matching fix and handleSelectionFieldOrFragment changes.
  • stuebingerb/KGraphQL#610: Modifies ParallelRequestExecutor execution flow and ExecutionError handling logic, at the same code level as the executePlan centralization and null-node error conversion in this PR.

Suggested reviewers

  • mervyn-mccreight
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 14.58% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title is fully related to the main change: it clearly describes adding fragment support at the query level, which is the primary feature introduced in this PR.
Description check ✅ Passed The description is related to the changeset, providing clear reference to issues (#406, #645) and highlighting the breaking change regarding API visibility modifications.
Linked Issues check ✅ Passed The PR successfully addresses both linked issues: #406 (fragments on query level) by routing selections through handleSelectionFieldOrFragment and adding comprehensive fragment tests, and #645 (null propagation) by splitting tests into nullable/non-nullable scenarios.
Out of Scope Changes check ✅ Passed All changes align with linked issue objectives: API internalization reduces public surface, execution plan refactoring supports proper fragment handling, and test additions verify fragment and null propagation behavior.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/support-fragments-on-query

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@codecov

codecov Bot commented Jun 14, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 89.13043% with 5 lines in your changes missing coverage. Please review.
✅ Project coverage is 84.23%. Comparing base (beccbd0) to head (e24fedb).

Files with missing lines Patch % Lines
...raphql/schema/execution/ParallelRequestExecutor.kt 86.48% 3 Missing and 2 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #650      +/-   ##
==========================================
+ Coverage   84.22%   84.23%   +0.01%     
==========================================
  Files         151      151              
  Lines        5000     4991       -9     
  Branches      857      858       +1     
==========================================
- Hits         4211     4204       -7     
+ Misses        491      489       -2     
  Partials      298      298              

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@kgraphql/src/main/kotlin/com/apurebase/kgraphql/schema/execution/ParallelRequestExecutor.kt`:
- Around line 373-374: The ktlint formatter is failing due to missing whitespace
formatting. In the ParallelRequestExecutor.kt file, locate the checkAccess
method call on node.field where takeUnless is used with a lambda expression. The
lambda expression `{ it is Type.OperationObject}` is missing a space before the
closing brace. Add a space before the `}` to match ktlint formatting standards,
changing it to `{ it is Type.OperationObject }`.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 540d2d32-3692-4efa-89d2-cabb438346e6

📥 Commits

Reviewing files that changed from the base of the PR and between f608ae3 and a068dce.

📒 Files selected for processing (8)
  • kgraphql/api/kgraphql.api
  • kgraphql/src/main/kotlin/com/apurebase/kgraphql/schema/execution/ExecutionPlan.kt
  • kgraphql/src/main/kotlin/com/apurebase/kgraphql/schema/execution/ParallelRequestExecutor.kt
  • kgraphql/src/main/kotlin/com/apurebase/kgraphql/schema/execution/RequestExecutor.kt
  • kgraphql/src/main/kotlin/com/apurebase/kgraphql/schema/structure/RequestInterpreter.kt
  • kgraphql/src/test/kotlin/com/apurebase/kgraphql/integration/BaseSchemaTest.kt
  • kgraphql/src/test/kotlin/com/apurebase/kgraphql/integration/QueryTest.kt
  • kgraphql/src/test/kotlin/com/apurebase/kgraphql/specification/language/FragmentsSpecificationTest.kt
💤 Files with no reviewable changes (1)
  • kgraphql/api/kgraphql.api

@stuebingerb
stuebingerb force-pushed the feature/support-fragments-on-query branch from a068dce to 8e1e2ae Compare June 14, 2026 15:52
@github-actions

github-actions Bot commented Jun 14, 2026

Copy link
Copy Markdown

🐰 Bencher Report

Branchfeature/support-fragments-on-query
Testbedubuntu-latest
Click to view all benchmark results
BenchmarkThroughputBenchmark Result
operations / second (ops/s)
(Result Δ%)
Lower Boundary
operations / second (ops/s)
(Limit %)
de.stuebingerb.kgraphql.FunctionExecutionBenchmark.benchmarkFunctionExecution📈 view plot
🚷 view threshold
6,065,684.22 ops/s
(+1.48%)Baseline: 5,976,979.68 ops/s
5,319,190.89 ops/s
(87.69%)
de.stuebingerb.kgraphql.ParallelExecutionBenchmark.queryBenchmark📈 view plot
🚷 view threshold
1.30 ops/s
(+0.06%)Baseline: 1.30 ops/s
1.30 ops/s
(99.76%)
de.stuebingerb.kgraphql.QueryBenchmark.executionError📈 view plot
🚷 view threshold
15,383.43 ops/s
(-6.33%)Baseline: 16,422.68 ops/s
14,416.53 ops/s
(93.71%)
de.stuebingerb.kgraphql.QueryBenchmark.inputFromDocument📈 view plot
🚷 view threshold
19,482.95 ops/s
(+1.01%)Baseline: 19,288.76 ops/s
15,323.65 ops/s
(78.65%)
de.stuebingerb.kgraphql.QueryBenchmark.inputFromVariable📈 view plot
🚷 view threshold
18,736.52 ops/s
(+0.08%)Baseline: 18,721.13 ops/s
15,227.00 ops/s
(81.27%)
de.stuebingerb.kgraphql.QueryBenchmark.largeList📈 view plot
🚷 view threshold
4.84 ops/s
(-1.47%)Baseline: 4.91 ops/s
4.51 ops/s
(93.21%)
de.stuebingerb.kgraphql.QueryBenchmark.largeListWithFragment📈 view plot
🚷 view threshold
5.40 ops/s
(-0.46%)Baseline: 5.42 ops/s
4.56 ops/s
(84.42%)
de.stuebingerb.kgraphql.QueryBenchmark.manyChildren📈 view plot
🚷 view threshold
187.13 ops/s
(-2.87%)Baseline: 192.66 ops/s
177.48 ops/s
(94.84%)
de.stuebingerb.kgraphql.QueryBenchmark.manyChildrenWithFragment📈 view plot
🚷 view threshold
210.03 ops/s
(+6.41%)Baseline: 197.38 ops/s
29.31 ops/s
(13.95%)
de.stuebingerb.kgraphql.QueryBenchmark.manyDataChildren📈 view plot
🚷 view threshold
8.95 ops/s
(+0.12%)Baseline: 8.94 ops/s
8.78 ops/s
(98.16%)
de.stuebingerb.kgraphql.QueryBenchmark.manyOperations📈 view plot
🚷 view threshold
325.47 ops/s
(+8.09%)Baseline: 301.12 ops/s
173.73 ops/s
(53.38%)
de.stuebingerb.kgraphql.QueryBenchmark.manyOperationsWithFragment📈 view plot
🚷 view threshold
330.19 ops/s
(+1.51%)Baseline: 325.27 ops/s
279.00 ops/s
(84.50%)
de.stuebingerb.kgraphql.QueryBenchmark.nestedObject📈 view plot
🚷 view threshold
7,597.00 ops/s
(+1.95%)Baseline: 7,451.37 ops/s
4,032.03 ops/s
(53.07%)
de.stuebingerb.kgraphql.RequestCachingBenchmark.invalidRequest📈 view plot
🚷 view threshold
143,568.00 ops/s
(+0.07%)Baseline: 143,467.08 ops/s
121,615.36 ops/s
(84.71%)
de.stuebingerb.kgraphql.RequestCachingBenchmark.largeRequest📈 view plot
🚷 view threshold
7,623.20 ops/s
(+3.60%)Baseline: 7,358.55 ops/s
5,958.07 ops/s
(78.16%)
de.stuebingerb.kgraphql.RequestCachingBenchmark.smallRequest📈 view plot
🚷 view threshold
10,511.14 ops/s
(-1.03%)Baseline: 10,620.57 ops/s
9,401.20 ops/s
(89.44%)
de.stuebingerb.kgraphql.SimpleExecutionOverheadBenchmark.benchmark📈 view plot
🚷 view threshold
465,960.26 ops/s
(-0.02%)Baseline: 466,032.24 ops/s
412,105.50 ops/s
(88.44%)
🐰 View full continuous benchmarking report in Bencher

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

No issues found across 8 files

Architecture diagram
sequenceDiagram
    participant Client as GraphQL Client
    participant Exe as Schema execution
    participant Int as RequestInterpreter
    participant Plan as ExecutionPlan
    participant Exec as ParallelRequestExecutor
    participant Load as Loader cache

    Note over Client,Exec: NEW: Query-level fragment support

    Client->>Exe: execute request

    Exe->>Int: createExecutionPlan(ast, operationName, variables)
    Int->>Int: parse operation selectionSet
    Int->>Int: CHANGED: handleSelectionFieldOrFragment()<br/>for each selection (FieldNode or FragmentNode)
    Int-->>Exe: ExecutionPlan(operations, root, declaredVariables)

    Note over Exe,Exec: CHANGED: root and declaredVariables now tracked

    Exe->>Exec: suspendExecute(plan, variables, context)
    Exec->>Exec: create ExecutionContext with declaredVariables
    Exec->>Plan: iterate over operations

    loop for each operation
        alt Execution.Fragment
            Exec->>Exec: handleFragment(ctx, root, operation)
            Exec->>Load: check fragment directives (skip/include)
            alt fragment included
                Exec->>Exec: recursively resolve fragment elements<br/>as property nodes or sub-fragments
                Exec-->>Exec: merge results into parent object
            end
        else Execution.Node
            Exec->>Exec: handleProperty(ctx, root, operation, root)
            Exec->>Load: check direct field include/skip
            alt field included
                Exec->>Exec: invoke resolver, create child nodes
                Exec-->>Exec: return key-value pair for merging
            end
        end
    end

    Exec->>Exec: merge all deferred results into single objectNode
    alt executionMode = Normal
        Exec->>Exec: mapIndexedParallel() for concurrent execution
    else executionMode = Serial
        Exec->>Exec: sequential execution
    end

    alt any non-nullable operation errors
        Exec->>Exec: raise ExecutionError → data = null
    else success
        Exec-->>Exe: serialized JSON result
    end

    Exe-->>Client: HTTP response with data
Loading

Re-trigger cubic

@stuebingerb stuebingerb added the breaking Introduces a breaking change label Jun 14, 2026
@stuebingerb
stuebingerb force-pushed the feature/support-fragments-on-query branch from 8e1e2ae to dbb44e7 Compare June 15, 2026 10:02

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (1)
kgraphql/src/test/kotlin/de/stuebingerb/kgraphql/integration/QueryTest.kt (1)

1086-1100: ⚡ Quick win

Strengthen the non-nullable root error assertion

This test only checks data == null (Line [1099]); also assert an errors entry exists so the test can’t pass on unrelated null-data regressions.

Suggested test hardening
-        response.deserialize()["data"] shouldBe null
+        val deserialized = response.deserialize()
+        deserialized["data"] shouldBe null
+        (deserialized["errors"] as? List<*>)?.isNotEmpty() shouldBe true
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@kgraphql/src/test/kotlin/de/stuebingerb/kgraphql/integration/QueryTest.kt`
around lines 1086 - 1100, The test function `errors during non-nullable
operation execution should stop other operations` only validates that the
response data is null but does not verify that the errors field is present. Add
an additional assertion after the existing data check to confirm that
response.deserialize()["errors"] exists and is not null/empty. This will ensure
the test cannot pass due to unrelated data-null regressions and truly validates
that errors were returned as expected.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@kgraphql/src/main/kotlin/de/stuebingerb/kgraphql/schema/execution/ParallelRequestExecutor.kt`:
- Line 338: The condition at line 338 in the ParallelRequestExecutor.kt file
performs a null-dereference when accessing __typename on a JsonNode. The
expression value["__typename"].textValue() will throw when the __typename field
is absent from delegated or remote payloads. Modify the fragment type matching
logic to safely check if __typename exists and is not null before calling
textValue() on it, such as by verifying the field is present and non-null before
dereferencing, or using a safe navigation approach to handle the case where
__typename is missing.
- Around line 521-523: The subscriptionArgs construction in the map operation at
line 522 unconditionally casts all children to Execution.Node and accesses
aliasOrKey, but fragments in the subscription selection are not Execution.Node
instances and will throw ClassCastException. Filter the children collection to
only process Execution.Node instances (by checking the type before casting or
using filterIsInstance) before mapping to extract their aliasOrKey values,
ensuring fragment children are properly skipped during subscription args
collection.

---

Nitpick comments:
In `@kgraphql/src/test/kotlin/de/stuebingerb/kgraphql/integration/QueryTest.kt`:
- Around line 1086-1100: The test function `errors during non-nullable operation
execution should stop other operations` only validates that the response data is
null but does not verify that the errors field is present. Add an additional
assertion after the existing data check to confirm that
response.deserialize()["errors"] exists and is not null/empty. This will ensure
the test cannot pass due to unrelated data-null regressions and truly validates
that errors were returned as expected.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: df6a4bd1-325e-45a3-9de7-91665804ac34

📥 Commits

Reviewing files that changed from the base of the PR and between a068dce and dbb44e7.

📒 Files selected for processing (8)
  • kgraphql/api/kgraphql.api
  • kgraphql/src/main/kotlin/de/stuebingerb/kgraphql/schema/execution/ExecutionPlan.kt
  • kgraphql/src/main/kotlin/de/stuebingerb/kgraphql/schema/execution/ParallelRequestExecutor.kt
  • kgraphql/src/main/kotlin/de/stuebingerb/kgraphql/schema/execution/RequestExecutor.kt
  • kgraphql/src/main/kotlin/de/stuebingerb/kgraphql/schema/structure/RequestInterpreter.kt
  • kgraphql/src/test/kotlin/de/stuebingerb/kgraphql/integration/BaseSchemaTest.kt
  • kgraphql/src/test/kotlin/de/stuebingerb/kgraphql/integration/QueryTest.kt
  • kgraphql/src/test/kotlin/de/stuebingerb/kgraphql/specification/language/FragmentsSpecificationTest.kt
💤 Files with no reviewable changes (1)
  • kgraphql/api/kgraphql.api
✅ Files skipped from review due to trivial changes (1)
  • kgraphql/src/main/kotlin/de/stuebingerb/kgraphql/schema/execution/RequestExecutor.kt

Resolves #406
Resolves #645

BREAKING CHANGE: `ExecutionPlan`, `RequestInterpreter` and
`ParallelRequestExecutor` are no longer public classes
@stuebingerb
stuebingerb force-pushed the feature/support-fragments-on-query branch from dbb44e7 to e24fedb Compare June 15, 2026 12:29
@stuebingerb
stuebingerb merged commit 5e06752 into main Jun 16, 2026
13 checks passed
@stuebingerb
stuebingerb deleted the feature/support-fragments-on-query branch June 16, 2026 18:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

breaking Introduces a breaking change

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Incorrect null propagation for root fields Fragments on query level do not work

2 participants