Skip to content

v0.4.1

  • v0.4.1
  • 5882d37
  • Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature.
  • Choose a tag to compare

  • v0.4.1
  • 5882d37
  • Choose a tag to compare

  • Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature.
@robotgryphon robotgryphon tagged this 15 Sep 16:40
A compiled chain sent its predicate as one variable of the schema's filter
input type — `where: $v0`, with `{"v0":{"name":{"eq":"Ada"}}}` beside it. A
server decides what a query costs before it coerces a single variable, so a
cost or complexity rule reading that sees an opaque input object and has to
assume the worst of it. The document now says what it means:

    query($v0: String) { people(where: { name: { eq: $v0 } }) { name age } }

with `{"v0":"Ada"}`. The shape was always a compile-time fact; it was being
written into the wrong half of the request.

FilterSkeleton already walked the predicate to render the payload's JSON. It
renders the GraphQL literal in the same walk, from the same clauses, with the
same hole numbering — two spellings of one traversal rather than two traversals
that have to agree. QueryDocumentWriter declares a variable per value instead of
one for the filter, and the payload writes each value with nothing around it.

Both properties the design rests on survive, which is what made this safe:

- One document per query shape. The values are still variables, so
  `Where(p => p.Age > 30)` and `Where(p => p.Age > 99)` still print the same
  text and one APQ hash still covers every predicate. Only structure moved, and
  structure was already fixed at compile time.
- Injection is still structurally impossible. Nothing is interpolated into the
  document at run time; it is still one u8 literal written when you built.

The cost is that a variable in a document has to declare its type, and what the
schema calls a comparison's value is now inferred rather than left to the server.
GraphQLTypeFacts.ScalarName maps only what HotChocolate's defaults make certain
and answers null for anything a schema could reasonably call several things —
char, TimeSpan, Uri, TimeOnly, the unsigned integers. An unnamed value, or a
field name the GraphQL grammar cannot carry unquoted, puts the whole filter back
in one variable on its own, per query and without a diagnostic: that form needs
no name at all.

What no build-time check can see is a type that maps cleanly to the wrong thing —
a string against a field the schema types as ID declares `$v0: String`, and a
variable may not be used where a stricter type is wanted. GraphQLQueryOptions
.InlineFilter turns the inlining off per query for that case. It is the first
option here that exists for a guess rather than for a fact.

The frozen-body assertions are updated, and so are the comments claiming they
were agreed with the runtime translation — that is no longer true of the filter
half, and what they pin now is the compiled output itself.

Verified with dotnet build, dotnet test (230 passed, 32 new), the AOT sample
published with -warnaserror and run, and the package sample published and run
against locally packed packages.


Claude-Session: https://claude.ai/code/session_01ASkfXAsnZA4GVfAiyNNAjm

Co-authored-by: Claude <noreply@anthropic.com>
Assets 2
Loading