Conversation
|
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## master #691 +/- ##
==========================================
+ Coverage 88.11% 88.36% +0.25%
==========================================
Files 84 84
Lines 12945 13125 +180
==========================================
+ Hits 11406 11598 +192
+ Misses 1539 1527 -12 🚀 New features to boost your workflow:
|
goccy
force-pushed
the
feat/field-selection
branch
from
September 30, 2026 10:41
b1140fa to
91dbb39
Compare
…elds to write FieldQuery is replaced by Selection, which selects the fields of a value as a selection set of GraphQL does. This is a breaking change of the API: FieldQuery, FieldQueryString, BuildFieldQuery, BuildSubFieldQuery, SetFieldQueryToContext and FieldQueryFromContext are removed. - A selection is made by Select / Field / FieldOf, or parsed from the syntax of a GraphQL selection set by ParseSelection, and given by the encode option WithSelection instead of a context. - The fields are written in the order of the selection, with their aliases. - The selection of a slice, an array, a map or a pointer is the one of its elements, and the one of an interface value is the one of the value it holds (issue #390). The value of an interface was filtered by the whole query before. - The fields of a struct embedded as a value are written where they are selected; the ones of a struct embedded by a pointer stay together. - MarshalJSON(context.Context) gets the selection of its value by SelectionFromContext, and the selected field by SelectedFieldFromContext, with the value given by SelectionField.With, such as the arguments of a GraphQL field. - The code compiled for a selection is kept by the selection itself, so it is freed with the selection instead of growing a table per type. - MarshalContext now uses the table of the recent types of the runtime context, which a context used to turn off because of the field query. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A GraphQL server written from scratch on json.Selection, and the one which gqlgen generates for the same schema and data. TestSameResponses checks that both write the same bytes for queries with aliases, fragments, a union, @Skip / @include, __typename, arguments by literals, defaults and variables, and merged fields. The benchmarks compare one request at a time, requests from all the CPUs, and resolvers fetching from a store with a latency. README.md has the results and what a product built on it would still need. The example is a module of its own, so that gqlgen is not a dependency of go-json; it needs Go 1.26, as gqlgen does. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ata loaders or batches The go-json server is split into a small GraphQL engine (package gql) and the models and resolvers of the schema on it. The engine serves a request in two phases: the fields with resolvers (gql.Field) are found from the selection level by level over the whole response and resolved concurrently, and then the response is written by one MarshalContext with the selection, each field writing its resolved value. - A data loader gathers the fetches of a level, as with gqlgen. - A batch resolver resolves the field of all the values of a level by one call, without the wait of a data loader. - A value reached twice by the same selected field is resolved once. - A resolver error is written as null, with its path and its place in the query. The gqlgen server gets the data loaders of dataloadgen, which gqlgen's documents recommend, and the follow-schema layout, so that its resolvers are kept when the code is generated again. The schema gets a field whose resolver fails, to compare the errors. TestSameResponses compares five variants (gqlgen with and without data loaders, the engine with batches, one call per field, and data loaders), and a benchmark measures resolvers fetching from a store with a latency. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
goccy
force-pushed
the
feat/field-selection
branch
from
October 1, 2026 10:24
91dbb39 to
087a3e9
Compare
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
FieldQueryis replaced bySelection, which selects the fields of a value as a GraphQL selection set does.examples/graphqlbuilds a GraphQL server on it from scratch and compares it with the server which gqlgen generates, to see whether such a server is worth a product of its own.Fixes #390.
Breaking changes
Removed:
FieldQuery,FieldQueryString,BuildFieldQuery,BuildSubFieldQuery,SetFieldQueryToContext,FieldQueryFromContext.json.BuildFieldQuery("A", json.BuildSubFieldQuery("B").Fields("C"))json.Select(json.Field("A"), json.Field("B", json.Field("C")))orjson.ParseSelection("A B { C }")json.MarshalContext(json.SetFieldQueryToContext(ctx, q), v)json.MarshalWithOption(v, json.WithSelection(sel))(orMarshalContext(ctx, v, json.WithSelection(sel)))json.FieldQueryFromContext(ctx)inMarshalJSON(ctx)json.SelectionFromContext(ctx)What a selection does
Field("name").As("displayName")).FieldOf(name, sel)selects a field by an existing selection, andSelect()with no field selects nothing ({}).MarshalJSON(context.Context)gets the selection of its value (SelectionFromContext) and the selected field (SelectedFieldFromContext), with a value given bySelectionField.With, such as the arguments of a GraphQL field.MarshalContextnow uses the runtime context's table of recently encoded types, which the field query used to turn off.examples/graphql
A separate module (it needs Go 1.26 for gqlgen, which is not a dependency of go-json).
TestSameResponseschecks that both servers write the same bytes for queries with aliases, fragments, a union,@skip/@include,__typename, literal / default / variable arguments and merged fields.Results (Apple M5, arm64, a loaded machine, so the ratios are what to read; the details are in
examples/graphql/README.md):Writing the example showed what a product built on this would still need: resolvers write through a nested
MarshalContext, whose output is checked again; unions are selected per value in the resolver; resolver errors stop the whole response instead of producingnullplus a path; resolvers run serially and must batch their fetches.Builds on #692
The engine of the example finds a resolved value by the address of its field, which relies on #692 (merged):
a pointer-receiver marshaler of a pointer-sized type is called with the address of the value.
examples/graphql: resolve phase with concurrency and data loaders
The go-json server is now a small engine (
examples/graphql/gql) plus the models and resolvers of the schema. A request is served in two phases:MarshalContextwith the selection.TestSameResponsescompares five variants (gqlgen with and without dataloadgen, and the engine with batches, with one call per field, and with data loaders), including resolver errors with their paths.With a 1 ms fetch latency, the engine takes the same time as gqlgen for the same way of fetching (
three_levels: 3.9 ms with 3 fetches in batches, vs gqlgen 3.8 ms with 31 fetches or 6.5 ms with data loaders). One request at a time it is 3.9x-17x faster in CPU, with 5x-3600x fewer allocations.Tests
go test ./...with Go 1.25.0, 1.26.2 and 1.27.1, and-racefor the selection tests and the examplemake lint🤖 Generated with Claude Code