Skip to content

Commit b45c4ea

Browse files
Copilotxperiandri
andauthored
Switch documentId to deterministic SHA-256 string
Agent-Logs-Url: https://github.com/fsprojects/FSharp.Data.GraphQL/sessions/1cf6548c-e575-44c8-832d-e662b59c9121 Co-authored-by: xperiandri <2365592+xperiandri@users.noreply.github.com>
1 parent fdb0f4e commit b45c4ea

12 files changed

Lines changed: 42 additions & 35 deletions

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -230,7 +230,7 @@ printfn "Custom data: %A\n" result.CustomData
230230
231231
// Errors: <null>
232232
233-
// Custom data: map [("documentId", 1221427401)]
233+
// Custom data: map [("documentId", "84fbf8cde7d1ce2c00b8e92e5f3472919b89c97c8c853b6c95619a0cb7fb3c6f")]
234234
```
235235

236236
For more information about how to use the client provider, see the [examples folder](samples/client-provider).

docs/execution-pipeline.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,8 @@ The execution phase can be performed using one of the two strategies:
5252

5353
The result of a GraphQL query execution is a `GQLResponse` object with the following fields:
5454

55-
- `documentId`: which is the hash code of the query's AST document - it can be used to implement execution plan caching (persistent queries).
55+
- `documentId`: deterministic SHA-256 hash (lowercase hex string) of the canonical query document - it can be used to implement execution plan caching (persistent queries).
5656
- `data`: optional, a formatted GraphQL response matching the requested query (`KeyValuePair seq`). Absent in case of an error that does not allow continuing processing and returning any GraphQL results.
5757
- `errors`: optional, contains a list of errors (`GQLProblemDetails`) that occurred during query execution.
5858

59-
This result can then be serialized and returned to the client.
59+
This result can then be serialized and returned to the client.

src/FSharp.Data.GraphQL.Client.DesignTime/ProvidedTypesHelper.fs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -799,7 +799,7 @@ module internal Provider =
799799
match validationResult with
800800
| ValidationError msgs -> failwith (formatValidationExceptionMessage msgs)
801801
| Success -> ()
802-
let key = { DocumentId = queryAst.GetHashCode(); SchemaId = schema.GetHashCode() }
802+
let key = { DocumentId = DocumentId.fromDocument queryAst; SchemaId = schema.GetHashCode() }
803803
let refMaker = lazy Validation.Ast.validateDocument schema queryAst
804804
if clientQueryValidation then
805805
refMaker.Force

src/FSharp.Data.GraphQL.Server/Executor.fs

Lines changed: 1 addition & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,7 @@ namespace FSharp.Data.GraphQL
33
open System
44
open System.Collections.Concurrent
55
open System.Collections.Immutable
6-
open System.Buffers.Binary
7-
open System.Security.Cryptography
86
open System.Runtime.InteropServices
9-
open System.Text
107
open System.Text.Json
118
open FsToolkit.ErrorHandling
129

@@ -83,18 +80,8 @@ type Executor<'Root>(schema: ISchema<'Root>, middlewares : IExecutorMiddleware s
8380
let middlewaresList = Seq.toList middlewares
8481

8582
/// Generates a deterministic document identifier from the canonical query string.
86-
/// The full SHA-256 hash is folded into 32 bits to preserve the existing int32 documentId contract.
8783
let getDocumentId (document : Document) =
88-
let canonicalQuery = document.ToQueryString()
89-
let queryBytes = Encoding.UTF8.GetBytes canonicalQuery
90-
let hash = SHA256.HashData queryBytes
91-
[ 0 .. 7 ]
92-
|> List.fold
93-
(fun acc index ->
94-
let start = index * 4
95-
let hashChunk = BinaryPrimitives.ReadInt32BigEndian(hash.AsSpan(start, 4))
96-
acc ^^^ hashChunk)
97-
0
84+
DocumentId.fromDocument document
9885

9986
let rec runMiddlewares (phaseSel : IExecutorMiddleware -> ('ctx -> ('ctx -> 'res) -> 'res) option)
10087
(initialCtx : 'ctx)

src/FSharp.Data.GraphQL.Server/IO.fs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ open FSharp.Data.GraphQL.Types
1010
type Output = IDictionary<string, obj>
1111

1212
type GQLResponse =
13-
{ DocumentId: int
13+
{ DocumentId: string
1414
Data : Output Skippable
1515
Errors : GQLProblemDetails list Skippable }
1616
static member Direct(documentId, data, errors) =
@@ -27,7 +27,7 @@ type GQLResponse =
2727
Errors = Include errors }
2828

2929
type GQLExecutionResult =
30-
{ DocumentId: int
30+
{ DocumentId: string
3131
Content : GQLResponseContent
3232
Metadata : Metadata }
3333
static member Direct(documentId, data, errors, meta) =
@@ -59,7 +59,7 @@ type GQLExecutionResult =
5959
static member Error(documentId, msg, meta) =
6060
GQLExecutionResult.RequestError(documentId, [ GQLProblemDetails.Create msg ], meta)
6161

62-
static member ErrorFromException(documentId : int, ex : Exception, meta : Metadata) =
62+
static member ErrorFromException(documentId : string, ex : Exception, meta : Metadata) =
6363
GQLExecutionResult.RequestError(documentId, [ GQLProblemDetails.Create (ex.Message, ex) ], meta)
6464

6565
static member Invalid(documentId, errors, meta) =

src/FSharp.Data.GraphQL.Shared/FSharp.Data.GraphQL.Shared.fsproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@
5555
<Compile Include="AsyncVal.fs" />
5656
<Compile Include="Ast.fs" />
5757
<Compile Include="AstExtensions.fs" />
58+
<Compile Include="Helpers\DocumentId.fs" />
5859
<Compile Include="TypeSystem.fs" />
5960
<Compile Include="SchemaDefinitions.fs" />
6061
<Compile Include="SchemaDefinitionsExtensions.fs" />
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
module FSharp.Data.GraphQL.DocumentId
2+
3+
open System.Globalization
4+
open System.Security.Cryptography
5+
open System.Text
6+
open FSharp.Data.GraphQL.Ast
7+
open FSharp.Data.GraphQL.Ast.Extensions
8+
9+
let private formatByteAsLowerHex (value : byte) =
10+
value.ToString("x2", CultureInfo.InvariantCulture)
11+
12+
let fromDocument (document : Document) =
13+
let canonicalQuery = document.ToQueryString()
14+
let queryBytes = Encoding.UTF8.GetBytes canonicalQuery
15+
use sha256 = SHA256.Create()
16+
let hash = sha256.ComputeHash queryBytes
17+
hash
18+
|> Array.map formatByteAsLowerHex
19+
|> String.concat ""

src/FSharp.Data.GraphQL.Shared/TypeSystem.fs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -671,7 +671,7 @@ and PlanningContext = {
671671
RootDef : ObjectDef
672672
Document : Document
673673
Operation : OperationDefinition
674-
DocumentId : int
674+
DocumentId : string
675675
Metadata : Metadata
676676
}
677677

@@ -888,7 +888,7 @@ and SchemaCompileContext = {
888888
/// It is used by the execution process to execute an operation.
889889
and ExecutionPlan = {
890890
/// Unique identifier of the current execution plan.
891-
DocumentId : int
891+
DocumentId : string
892892
/// AST definition of current operation.
893893
Operation : OperationDefinition
894894
/// Definition of the root type (either query or mutation) used by the

src/FSharp.Data.GraphQL.Shared/ValidationResultCache.fs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ open FSharp.Data.GraphQL
44
open System
55

66
type ValidationResultKey =
7-
{ DocumentId : int
7+
{ DocumentId : string
88
SchemaId : int }
99

1010
type ValidationResultProducer =

tests/FSharp.Data.GraphQL.IntegrationTests/integration-introspection.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"documentId": 1890859023,
2+
"documentId": "547d9dc982284840b3e020dfcbf43ae96cef7595afa007145ed954794363d148",
33
"data": {
44
"__schema": {
55
"queryType": {

0 commit comments

Comments
 (0)