From 971b1f977cef8a97e6ccad65610ba4a5ab35f2b1 Mon Sep 17 00:00:00 2001 From: Greg MacWilliam Date: Thu, 4 Nov 2021 20:02:13 -0400 Subject: [PATCH] add _bramble_id as a universal implementation hint. --- docs/algorithms.md | 4 +- execution_test.go | 259 +++++++++++++++++++++++++----------------- plan.go | 88 ++++++-------- plan_fixtures_test.go | 21 ++-- plan_test.go | 134 ++++++++++------------ query_execution.go | 14 +-- 6 files changed, 270 insertions(+), 250 deletions(-) diff --git a/docs/algorithms.md b/docs/algorithms.md index d8ec6310..452031c5 100644 --- a/docs/algorithms.md +++ b/docs/algorithms.md @@ -260,8 +260,8 @@ function ExtractSelectionSet(ctx, insertionPoint, parentType, selectionSet, loca append childrenSteps to the result's query plan steps } } - if parentType is a boundary type and the result selectionSet doesn't have an "id" field { - add the "id" field to the result selectionSet, aliased to "_id" + if parentType is a boundary type { + add the "id" field to the result selectionSet, aliased as "_bramble_id" } return result :: (ast.SelectionSet, []QueryPlanStep) } diff --git a/execution_test.go b/execution_test.go index c05bbb96..cf2dabd8 100644 --- a/execution_test.go +++ b/execution_test.go @@ -601,7 +601,7 @@ func TestFederatedQueryFragmentSpreads(t *testing.T) { "snapshot": { "id": "100", "name": "foo", - "gizmos": [{ "id": "GIZMO1" }], + "gizmos": [{ "_bramble_id": "GIZMO1", "id": "GIZMO1" }], "__typename": "GizmoImplementation" } } @@ -613,7 +613,7 @@ func TestFederatedQueryFragmentSpreads(t *testing.T) { "snapshot": { "id": "100", "name": "foo", - "gadgets": [{ "id": "GADGET1" }], + "gadgets": [{ "_bramble_id": "GADGET1", "id": "GADGET1" }], "__typename": "GadgetImplementation" } } @@ -653,6 +653,7 @@ func TestFederatedQueryFragmentSpreads(t *testing.T) { { "data": { "_0": { + "_bramble_id": "GIZMO1", "id": "GIZMO1", "name": "Gizmo #1" } @@ -664,6 +665,7 @@ func TestFederatedQueryFragmentSpreads(t *testing.T) { "data": { "_result": [ { + "_bramble_id": "GADGET1", "id": "GADGET1", "name": "Gadget #1", "agents": [ @@ -940,6 +942,7 @@ func TestQueryExecutionMultipleServices(t *testing.T) { w.Write([]byte(`{ "data": { "movie": { + "_bramble_id": "1", "id": "1", "title": "Test title" } @@ -963,6 +966,7 @@ func TestQueryExecutionMultipleServices(t *testing.T) { w.Write([]byte(`{ "data": { "_0": { + "_bramble_id": "1", "id": "1", "release": 2007 } @@ -1207,6 +1211,7 @@ func TestQueryWithArrayBoundaryFieldsAndMultipleChildrenSteps(t *testing.T) { w.Write([]byte(`{ "data": { "randomMovie": { + "_bramble_id": "1", "id": "1", "title": "Movie 1" } @@ -1217,9 +1222,9 @@ func TestQueryWithArrayBoundaryFieldsAndMultipleChildrenSteps(t *testing.T) { w.Write([]byte(`{ "data": { "_result": [ - { "id": "2", "title": "Movie 2" }, - { "id": "3", "title": "Movie 3" }, - { "id": "4", "title": "Movie 4" } + { "_bramble_id": "2", "id": "2", "title": "Movie 2" }, + { "_bramble_id": "3", "id": "3", "title": "Movie 3" }, + { "_bramble_id": "4", "id": "4", "title": "Movie 4" } ] } } @@ -1243,11 +1248,11 @@ func TestQueryWithArrayBoundaryFieldsAndMultipleChildrenSteps(t *testing.T) { "data": { "_result": [ { - "_id": "1", + "_bramble_id": "1", "compTitles": [ - {"id": "2"}, - {"id": "3"}, - {"id": "4"} + {"_bramble_id": "2", "id": "2"}, + {"_bramble_id": "3", "id": "3"}, + {"_bramble_id": "4", "id": "4"} ] } ] @@ -1314,11 +1319,13 @@ func TestQueryWithBoundaryFieldsAndNullsAboveInsertionPoint(t *testing.T) { "ns": { "movies": [ { + "_bramble_id": "MOVIE1", "id": "MOVIE1", "title": "Movie #1", - "director": { "id": "DIRECTOR1" } + "director": { "_bramble_id": "DIRECTOR1", "id": "DIRECTOR1" } }, { + "_bramble_id": "MOVIE2", "id": "MOVIE2", "title": "Movie #2", "director": null @@ -1348,7 +1355,7 @@ func TestQueryWithBoundaryFieldsAndNullsAboveInsertionPoint(t *testing.T) { w.Write([]byte(`{ "data": { "_0": { - "_id": "DIRECTOR1", + "_bramble_id": "DIRECTOR1", "name": "David Fincher" } } @@ -1396,31 +1403,31 @@ func TestExtractBoundaryIDs(t *testing.T) { dataJSON := `{ "gizmos": [ { - "id": "1", + "_bramble_id": "1", "name": "Gizmo 1", "owner": { - "_id": "1" + "_bramble_id": "1" } }, { - "id": "2", + "_bramble_id": "2", "name": "Gizmo 2", "owner": { - "id": "1" + "_bramble_id": "1" } }, { - "id": "3", + "_bramble_id": "3", "name": "Gizmo 3", "owner": { - "_id": "2" + "_bramble_id": "2" } }, { - "id": "4", + "_bramble_id": "4", "name": "Gizmo 4", "owner": { - "id": "5" + "_bramble_id": "5" } } ] @@ -1440,7 +1447,7 @@ func TestTrimInsertionPointForNestedBoundaryQuery(t *testing.T) { "id": "1", "name": "Gizmo 1", "owner": { - "_id": "1" + "_bramble_id": "1" } }, { @@ -1454,7 +1461,7 @@ func TestTrimInsertionPointForNestedBoundaryQuery(t *testing.T) { "id": "3", "name": "Gizmo 3", "owner": { - "_id": "2" + "_bramble_id": "2" } }, { @@ -1495,7 +1502,7 @@ func TestBuildBoundaryQueryDocuments(t *testing.T) { ids := []string{"1", "2", "3"} selectionSet := []ast.Selection{ &ast.Field{ - Alias: "_id", + Alias: "_bramble_id", Name: "id", Definition: schema.Types["Owner"].Fields.ForName("id"), ObjectDefinition: schema.Types["Owner"], @@ -1515,7 +1522,7 @@ func TestBuildBoundaryQueryDocuments(t *testing.T) { InsertionPoint: []string{"gizmos", "owner"}, Then: nil, } - expected := []string{`{ _result: getOwners(ids: ["1", "2", "3"]) { _id: id name } }`} + expected := []string{`{ _result: getOwners(ids: ["1", "2", "3"]) { _bramble_id: id name } }`} ctx := testContextWithoutVariables(nil) docs, err := buildBoundaryQueryDocuments(ctx, schema, step, ids, boundaryField, 1) require.NoError(t, err) @@ -1545,7 +1552,7 @@ func TestBuildNonArrayBoundaryQueryDocuments(t *testing.T) { ids := []string{"1", "2", "3"} selectionSet := []ast.Selection{ &ast.Field{ - Alias: "_id", + Alias: "_bramble_id", Name: "id", Definition: schema.Types["Owner"].Fields.ForName("id"), ObjectDefinition: schema.Types["Owner"], @@ -1565,7 +1572,7 @@ func TestBuildNonArrayBoundaryQueryDocuments(t *testing.T) { InsertionPoint: []string{"gizmos", "owner"}, Then: nil, } - expected := []string{`{ _0: getOwner(id: "1") { _id: id name } _1: getOwner(id: "2") { _id: id name } _2: getOwner(id: "3") { _id: id name } }`} + expected := []string{`{ _0: getOwner(id: "1") { _bramble_id: id name } _1: getOwner(id: "2") { _bramble_id: id name } _2: getOwner(id: "3") { _bramble_id: id name } }`} ctx := testContextWithoutVariables(nil) docs, err := buildBoundaryQueryDocuments(ctx, schema, step, ids, boundaryField, 10) require.NoError(t, err) @@ -1595,7 +1602,7 @@ func TestBuildBatchedNonArrayBoundaryQueryDocuments(t *testing.T) { ids := []string{"1", "2", "3"} selectionSet := []ast.Selection{ &ast.Field{ - Alias: "_id", + Alias: "_bramble_id", Name: "id", Definition: schema.Types["Owner"].Fields.ForName("id"), ObjectDefinition: schema.Types["Owner"], @@ -1615,7 +1622,7 @@ func TestBuildBatchedNonArrayBoundaryQueryDocuments(t *testing.T) { InsertionPoint: []string{"gizmos", "owner"}, Then: nil, } - expected := []string{`{ _0: getOwner(id: "1") { _id: id name } _1: getOwner(id: "2") { _id: id name } }`, `{ _2: getOwner(id: "3") { _id: id name } }`} + expected := []string{`{ _0: getOwner(id: "1") { _bramble_id: id name } _1: getOwner(id: "2") { _bramble_id: id name } }`, `{ _2: getOwner(id: "3") { _bramble_id: id name } }`} ctx := testContextWithoutVariables(nil) docs, err := buildBoundaryQueryDocuments(ctx, schema, step, ids, boundaryField, 2) require.NoError(t, err) @@ -1626,6 +1633,7 @@ func TestMergeExecutionResults(t *testing.T) { t.Run("merges single map", func(t *testing.T) { inputMap := jsonToInterfaceMap(`{ "gizmo": { + "_bramble_id": "1", "id": "1", "color": "Gizmo A" } @@ -1690,8 +1698,12 @@ func TestMergeExecutionResults(t *testing.T) { t.Run("merges mid level array", func(t *testing.T) { inputMapA := jsonToInterfaceMap(`{ "gizmo": { - "id": "1", - "gadgets": [{"id": "GADGET1", "owner": { "id": "OWNER1" }}, {"id": "GADGET3", "owner": { "id": "OWNER3" }}, {"id": "GADGET2", "owner": null}] + "_bramble_id": "1", + "gadgets": [ + {"_bramble_id": "GADGET1", "owner": { "_bramble_id": "OWNER1" }}, + {"_bramble_id": "GADGET3", "owner": { "_bramble_id": "OWNER3" }}, + {"_bramble_id": "GADGET2", "owner": null} + ] } }`) @@ -1703,7 +1715,7 @@ func TestMergeExecutionResults(t *testing.T) { inputMapB := jsonToInterfaceSlice(`[ { - "id": "OWNER1", + "_bramble_id": "OWNER1", "name": "008" } ]`) @@ -1721,24 +1733,24 @@ func TestMergeExecutionResults(t *testing.T) { "gizmo": { "gadgets": [ { - "id": "GADGET1", + "_bramble_id": "GADGET1", "owner": { - "id": "OWNER1", + "_bramble_id": "OWNER1", "name": "008" } }, { - "id": "GADGET3", + "_bramble_id": "GADGET3", "owner": { - "id": "OWNER3" + "_bramble_id": "OWNER3" } }, { - "id": "GADGET2", + "_bramble_id": "GADGET2", "owner": null } ], - "id": "1" + "_bramble_id": "1" } }`) @@ -1746,11 +1758,19 @@ func TestMergeExecutionResults(t *testing.T) { require.Equal(t, expected, mergedMap) }) - t.Run("merges nested mid level array", func(t *testing.T) { + t.Run("merges nested mid-level array", func(t *testing.T) { inputMapA := jsonToInterfaceMap(`{ "gizmo": { - "id": "1", - "gadgets": [[{"id": "GADGET1", "owner": { "id": "OWNER1" }}, {"id": "GADGET3", "owner": { "id": "OWNER3" }}], [{"id": "GADGET2", "owner": null}]] + "_bramble_id": "1", + "gadgets": [ + [ + {"_bramble_id": "GADGET1", "owner": { "_bramble_id": "OWNER1" }}, + {"_bramble_id": "GADGET3", "owner": { "_bramble_id": "OWNER3" }} + ], + [ + {"_bramble_id": "GADGET2", "owner": null} + ] + ] } }`) @@ -1762,7 +1782,7 @@ func TestMergeExecutionResults(t *testing.T) { inputMapB := jsonToInterfaceSlice(`[ { - "id": "OWNER1", + "_bramble_id": "OWNER1", "name": "008" } ]`) @@ -1781,27 +1801,27 @@ func TestMergeExecutionResults(t *testing.T) { "gadgets": [ [ { - "id": "GADGET1", + "_bramble_id": "GADGET1", "owner": { - "id": "OWNER1", + "_bramble_id": "OWNER1", "name": "008" } }, { - "id": "GADGET3", + "_bramble_id": "GADGET3", "owner": { - "id": "OWNER3" + "_bramble_id": "OWNER3" } } ], [ { - "id": "GADGET2", + "_bramble_id": "GADGET2", "owner": null } ] ], - "id": "1" + "_bramble_id": "1" } }`) @@ -1815,7 +1835,7 @@ func TestMergeExecutionResults(t *testing.T) { "id": "1", "color": "Gizmo A", "owner": { - "_id": "1" + "_bramble_id": "1" } } }`) @@ -1828,7 +1848,7 @@ func TestMergeExecutionResults(t *testing.T) { inputSliceB := jsonToInterfaceSlice(`[ { - "_id": "1", + "_bramble_id": "1", "name": "Owner A" } ]`) @@ -1846,7 +1866,7 @@ func TestMergeExecutionResults(t *testing.T) { "id": "1", "color": "Gizmo A", "owner": { - "_id": "1", + "_bramble_id": "1", "name": "Owner A" } } @@ -1863,21 +1883,21 @@ func TestMergeExecutionResults(t *testing.T) { "id": "1", "color": "RED", "owner": { - "_id": "4" + "_bramble_id": "4" } }, { "id": "2", "color": "GREEN", "owner": { - "_id": "5" + "_bramble_id": "5" } }, { "id": "3", "color": "BLUE", "owner": { - "_id": "6" + "_bramble_id": "6" } } ] @@ -1891,15 +1911,15 @@ func TestMergeExecutionResults(t *testing.T) { inputSliceB := jsonToInterfaceSlice(`[ { - "_id": "4", + "_bramble_id": "4", "name": "Owner A" }, { - "_id": "5", + "_bramble_id": "5", "name": "Owner B" }, { - "_id": "6", + "_bramble_id": "6", "name": "Owner C" } ]`) @@ -1918,7 +1938,7 @@ func TestMergeExecutionResults(t *testing.T) { "id": "1", "color": "RED", "owner": { - "_id": "4", + "_bramble_id": "4", "name": "Owner A" } }, @@ -1926,7 +1946,7 @@ func TestMergeExecutionResults(t *testing.T) { "id": "2", "color": "GREEN", "owner": { - "_id": "5", + "_bramble_id": "5", "name": "Owner B" } }, @@ -1934,7 +1954,7 @@ func TestMergeExecutionResults(t *testing.T) { "id": "3", "color": "BLUE", "owner": { - "_id": "6", + "_bramble_id": "6", "name": "Owner C" } } @@ -1952,21 +1972,21 @@ func TestMergeExecutionResults(t *testing.T) { "id": "1", "color": "RED", "owner": { - "_id": "4" + "_bramble_id": "4" } }, { "id": "2", "color": "GREEN", "owner": { - "_id": "5" + "_bramble_id": "5" } }, { "id": "3", "color": "BLUE", "owner": { - "_id": "6" + "_bramble_id": "6" } } ] @@ -1980,15 +2000,15 @@ func TestMergeExecutionResults(t *testing.T) { inputSliceB := jsonToInterfaceSlice(`[ { - "_id": "4", + "_bramble_id": "4", "name": "Owner A" }, { - "_id": "5", + "_bramble_id": "5", "name": "Owner B" }, { - "_id": "6", + "_bramble_id": "6", "name": "Owner C" } ]`) @@ -2007,7 +2027,7 @@ func TestMergeExecutionResults(t *testing.T) { "id": "1", "color": "RED", "owner": { - "_id": "4", + "_bramble_id": "4", "name": "Owner A" } }, @@ -2015,7 +2035,7 @@ func TestMergeExecutionResults(t *testing.T) { "id": "2", "color": "GREEN", "owner": { - "_id": "5", + "_bramble_id": "5", "name": "Owner B" } }, @@ -2023,7 +2043,7 @@ func TestMergeExecutionResults(t *testing.T) { "id": "3", "color": "BLUE", "owner": { - "_id": "6", + "_bramble_id": "6", "name": "Owner C" } } @@ -2034,28 +2054,28 @@ func TestMergeExecutionResults(t *testing.T) { require.Equal(t, expected, mergedMap) }) - t.Run("allows using both 'id' and '_id'", func(t *testing.T) { + t.Run("merges using '_bramble_id'", func(t *testing.T) { inputMapA := jsonToInterfaceMap(`{ "gizmos": [ { - "id": "1", + "_bramble_id": "1", "color": "RED", "owner": { - "id": "4" + "_bramble_id": "4" } }, { - "id": "2", + "_bramble_id": "2", "color": "GREEN", "owner": { - "id": "5" + "_bramble_id": "5" } }, { - "id": "3", + "_bramble_id": "3", "color": "BLUE", "owner": { - "_id": "6" + "_bramble_id": "6" } } ] @@ -2069,15 +2089,15 @@ func TestMergeExecutionResults(t *testing.T) { inputSliceB := jsonToInterfaceSlice(`[ { - "_id": "4", + "_bramble_id": "4", "name": "Owner A" }, { - "id": "5", + "_bramble_id": "5", "name": "Owner B" }, { - "id": "6", + "_bramble_id": "6", "name": "Owner C" } ]`) @@ -2093,26 +2113,26 @@ func TestMergeExecutionResults(t *testing.T) { expected := jsonToInterfaceMap(`{ "gizmos": [ { - "id": "1", + "_bramble_id": "1", "color": "RED", "owner": { - "id": "4", + "_bramble_id": "4", "name": "Owner A" } }, { - "id": "2", + "_bramble_id": "2", "color": "GREEN", "owner": { - "id": "5", + "_bramble_id": "5", "name": "Owner B" } }, { - "id": "3", + "_bramble_id": "3", "color": "BLUE", "owner": { - "_id": "6", + "_bramble_id": "6", "name": "Owner C" } } @@ -3608,9 +3628,9 @@ func TestQueryExecutionWithMultipleBoundaryQueries(t *testing.T) { w.Write([]byte(`{ "data": { "movies": [ - { "id": "1", "title": "Test title 1" }, - { "id": "2", "title": "Test title 2" }, - { "id": "3", "title": "Test title 3" } + { "_bramble_id": "1", "id": "1", "title": "Test title 1" }, + { "_bramble_id": "2", "id": "2", "title": "Test title 2" }, + { "_bramble_id": "3", "id": "3", "title": "Test title 3" } ] } } @@ -3624,9 +3644,9 @@ func TestQueryExecutionWithMultipleBoundaryQueries(t *testing.T) { json.NewDecoder(r.Body).Decode(&q) w.Write([]byte(`{ "data": { - "_0": { "id": "1", "release": 2007 }, - "_1": { "id": "2", "release": 2008 }, - "_2": { "id": "3", "release": 2009 } + "_0": { "_bramble_id": "1", "id": "1", "release": 2007 }, + "_1": { "_bramble_id": "2", "id": "2", "release": 2008 }, + "_2": { "_bramble_id": "3", "id": "3", "release": 2009 } } } `)) @@ -3697,20 +3717,22 @@ func TestQueryExecutionMultipleServicesWithArray(t *testing.T) { } res += fmt.Sprintf(` "_%d": { + "_bramble_id": "%s", "id": "%s", "title": "title %s" - }`, i, id, id) + }`, i, id, id, id) } w.Write([]byte(fmt.Sprintf(`{ "data": { %s } }`, res))) } else { w.Write([]byte(fmt.Sprintf(`{ "data": { "movie": { + "_bramble_id": "%s", "id": "%s", "title": "title %s" } } - }`, ids[0], ids[0]))) + }`, ids[0], ids[0], ids[0]))) } }), }, @@ -3729,20 +3751,23 @@ func TestQueryExecutionMultipleServicesWithArray(t *testing.T) { w.Write([]byte(`{ "data": { "_0": { + "_bramble_id": "1", "id": "1", "compTitles": [ { + "_bramble_id": "2", "id": "2", "compTitles": [ - { "id": "3" }, - { "id": "4" } + { "_bramble_id": "3", "id": "3" }, + { "_bramble_id": "4", "id": "4" } ] }, { + "_bramble_id": "3", "id": "3", "compTitles": [ - { "id": "4" }, - { "id": "5" } + { "_bramble_id": "4", "id": "4" }, + { "_bramble_id": "5", "id": "5" } ] } ] @@ -3890,20 +3915,22 @@ func TestQueryExecutionMultipleServicesWithNestedArrays(t *testing.T) { } res += fmt.Sprintf(` "_%d": { + "_bramble_id": "%s", "id": "%s", "title": "title %s" - }`, i, id, id) + }`, i, id, id, id) } w.Write([]byte(fmt.Sprintf(`{ "data": { %s } }`, res))) } else { w.Write([]byte(fmt.Sprintf(`{ "data": { "movie": { + "_bramble_id": "%s", "id": "%s", "title": "title %s" } } - }`, ids[0], ids[0]))) + }`, ids[0], ids[0], ids[0]))) } }), }, @@ -3922,12 +3949,15 @@ func TestQueryExecutionMultipleServicesWithNestedArrays(t *testing.T) { w.Write([]byte(`{ "data": { "_0": { + "_bramble_id": "1", "id": "1", "compTitles": [[ { + "_bramble_id": "2", "id": "2" }, { + "_bramble_id": "3", "id": "3" } ]] @@ -3988,6 +4018,7 @@ func TestQueryExecutionEmptyBoundaryResponse(t *testing.T) { w.Write([]byte(`{ "data": { "movie": { + "_bramble_id": "1", "id": "1", "title": "Test title" } @@ -4121,15 +4152,19 @@ func TestQueryExecutionWithInputObject(t *testing.T) { title otherMovie(arg: {id: "2", title: "another title"}) { title + _bramble_id: id } + _bramble_id: id } }`, q["query"]) w.Write([]byte(`{ "data": { "movie": { + "_bramble_id": "1", "id": "1", "title": "Test title", "otherMovie": { + "_bramble_id": "2", "title": "another title" } } @@ -4153,6 +4188,7 @@ func TestQueryExecutionWithInputObject(t *testing.T) { w.Write([]byte(`{ "data": { "_0": { + "_bramble_id": "1", "id": "1", "release": 2007 } @@ -4204,6 +4240,7 @@ func TestQueryExecutionMultipleObjects(t *testing.T) { w.Write([]byte(`{ "data": { "movie": { + "_bramble_id": "1", "id": "1", "title": "Test title" } @@ -4230,8 +4267,8 @@ func TestQueryExecutionMultipleObjects(t *testing.T) { w.Write([]byte(`{ "data": { "movies": [ - { "id": "1", "release": 2007 }, - { "id": "2", "release": 2018 } + { "_bramble_id": "1", "id": "1", "release": 2007 }, + { "_bramble_id": "2", "id": "2", "release": 2018 } ] } } @@ -4240,6 +4277,7 @@ func TestQueryExecutionMultipleObjects(t *testing.T) { w.Write([]byte(`{ "data": { "_0": { + "_bramble_id": "1", "id": "1", "release": 2007 } @@ -4359,6 +4397,7 @@ func TestQueryExecutionMultipleServicesWithSkipFalseDirectives(t *testing.T) { w.Write([]byte(`{ "data": { "movie": { + "_bramble_id": "1", "id": "1" } } @@ -4383,6 +4422,7 @@ func TestQueryExecutionMultipleServicesWithSkipFalseDirectives(t *testing.T) { w.Write([]byte(`{ "data": { "_0": { + "_bramble_id": "1", "id": "1", "title": "no soup for you", "gizmo": { @@ -4506,6 +4546,7 @@ func TestQueryExecutionMultipleServicesWithIncludeTrueDirectives(t *testing.T) { w.Write([]byte(`{ "data": { "movie": { + "_bramble_id": "1", "id": "1" } } @@ -4530,6 +4571,7 @@ func TestQueryExecutionMultipleServicesWithIncludeTrueDirectives(t *testing.T) { w.Write([]byte(`{ "data": { "_0": { + "_bramble_id": "1", "id": "1", "title": "yada yada yada", "gizmo": { @@ -4591,11 +4633,12 @@ func TestMutationExecution(t *testing.T) { handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { var q map[string]string json.NewDecoder(r.Body).Decode(&q) - assertQueriesEqual(t, schema1, `mutation { updateTitle(id: "2", title: "New title") { _id: id title } }`, q["query"]) + assertQueriesEqual(t, schema1, `mutation { updateTitle(id: "2", title: "New title") { title _bramble_id: id } }`, q["query"]) w.Write([]byte(`{ "data": { "updateTitle": { + "_bramble_id": "2", "id": "2", "title": "New title" } @@ -4617,6 +4660,7 @@ func TestMutationExecution(t *testing.T) { w.Write([]byte(`{ "data": { "_0": { + "_bramble_id": "2", "id": "2", "release": 2007 } @@ -4682,7 +4726,7 @@ func TestQueryExecutionWithUnions(t *testing.T) { w.Write([]byte(`{ "data": { "_0": { - "_id": "2", + "_bramble_id": "2", "pet": { "name": "felix", "age": 2, @@ -4711,7 +4755,7 @@ func TestQueryExecutionWithUnions(t *testing.T) { w.Write([]byte(`{ "data": { "person": { - "_id": "2", + "_bramble_id": "2", "name": "Bob" } } @@ -4787,7 +4831,7 @@ func TestQueryExecutionWithNamespaces(t *testing.T) { w.Write([]byte(`{ "data": { "_0": { - "_id": "CA7", + "_bramble_id": "CA7", "name": "Felix" } } @@ -4838,6 +4882,7 @@ func TestQueryExecutionWithNamespaces(t *testing.T) { "animals": { "cats": { "searchCat": { + "_bramble_id": "CA7", "id": "CA7" } } @@ -4932,6 +4977,7 @@ func TestQueryWithBoundaryFields(t *testing.T) { w.Write([]byte(`{ "data": { "movie": { + "_bramble_id": "1", "id": "1", "title": "Test title" } @@ -4955,6 +5001,7 @@ func TestQueryWithBoundaryFields(t *testing.T) { w.Write([]byte(`{ "data": { "_0": { + "_bramble_id": "1", "id": "1", "release": 2007 } @@ -5052,14 +5099,17 @@ func TestQueryWithArrayBoundaryFields(t *testing.T) { "data": { "randomMovies": [ { + "_bramble_id": "1", "id": "1", "title": "Movie 1" }, { + "_bramble_id": "2", "id": "2", "title": "Movie 2" }, { + "_bramble_id": "3", "id": "3", "title": "Movie 3" } @@ -5085,14 +5135,17 @@ func TestQueryWithArrayBoundaryFields(t *testing.T) { "data": { "_result": [ { + "_bramble_id": "1", "id": "1", "release": 2007 }, { + "_bramble_id": "2", "id": "2", "release": 2008 }, { + "_bramble_id": "3", "id": "3", "release": 2009 } diff --git a/plan.go b/plan.go index 3cbae527..41a179f2 100644 --- a/plan.go +++ b/plan.go @@ -8,6 +8,7 @@ import ( "github.com/99designs/gqlgen/graphql" "github.com/vektah/gqlparser/v2/ast" + "github.com/vektah/gqlparser/v2/gqlerror" ) // QueryPlanStep is a single execution step @@ -66,7 +67,7 @@ func Plan(ctx *PlanningContext) (*QueryPlan, error) { return nil, fmt.Errorf("not implemented") } - steps, err := createSteps(ctx, nil, parentType, "", ctx.Operation.SelectionSet, false) + steps, err := createSteps(ctx, nil, parentType, "", ctx.Operation.SelectionSet) if err != nil { return nil, err } @@ -75,7 +76,7 @@ func Plan(ctx *PlanningContext) (*QueryPlan, error) { }, nil } -func createSteps(ctx *PlanningContext, insertionPoint []string, parentType, parentLocation string, selectionSet ast.SelectionSet, childstep bool) ([]*QueryPlanStep, error) { +func createSteps(ctx *PlanningContext, insertionPoint []string, parentType string, parentLocation string, selectionSet ast.SelectionSet) ([]*QueryPlanStep, error) { var result []*QueryPlanStep routedSelectionSet, err := routeSelectionSet(ctx, parentType, parentLocation, selectionSet) @@ -84,7 +85,7 @@ func createSteps(ctx *PlanningContext, insertionPoint []string, parentType, pare } for location, selectionSet := range routedSelectionSet { - selectionSetForLocation, childrenSteps, err := extractSelectionSet(ctx, insertionPoint, parentType, selectionSet, location, childstep) + selectionSetForLocation, childrenSteps, err := extractSelectionSet(ctx, insertionPoint, parentType, selectionSet, location) if err != nil { return nil, err @@ -115,13 +116,23 @@ func createSteps(ctx *PlanningContext, insertionPoint []string, parentType, pare return result, nil } -func extractSelectionSet(ctx *PlanningContext, insertionPoint []string, parentType string, input ast.SelectionSet, location string, childstep bool) (ast.SelectionSet, []*QueryPlanStep, error) { +var reservedAliases = map[string]string{ + "__typename": "__typename", + "_bramble_id": "id", +} + +func extractSelectionSet(ctx *PlanningContext, insertionPoint []string, parentType string, input ast.SelectionSet, location string) (ast.SelectionSet, []*QueryPlanStep, error) { var selectionSetResult []ast.Selection var childrenStepsResult []*QueryPlanStep var remoteSelections []ast.Selection for _, selection := range input { switch selection := selection.(type) { case *ast.Field: + for reservedAlias, requiredName := range reservedAliases { + if selection.Alias == reservedAlias && selection.Name != requiredName { + return nil, nil, gqlerror.Errorf("%s.%s: alias \"%s\" is reserved for system use", strings.Join(insertionPoint, "."), reservedAlias, reservedAlias) + } + } if parentType != queryObjectName && parentType != mutationObjectName && ctx.IsBoundary[parentType] && selection.Name == "id" { selectionSetResult = append(selectionSetResult, selection) continue @@ -129,7 +140,7 @@ func extractSelectionSet(ctx *PlanningContext, insertionPoint []string, parentTy loc, err := ctx.Locations.URLFor(parentType, location, selection.Name) if err != nil { // namespace - subSS, steps, err := extractSelectionSet(ctx, append(insertionPoint, selection.Name), selection.Definition.Type.Name(), selection.SelectionSet, location, childstep) + subSS, steps, err := extractSelectionSet(ctx, append(insertionPoint, selection.Name), selection.Definition.Type.Name(), selection.SelectionSet, location) if err != nil { return nil, nil, err } @@ -153,7 +164,6 @@ func extractSelectionSet(ctx *PlanningContext, insertionPoint []string, parentTy selection.Definition.Type.Name(), selection.SelectionSet, location, - childstep, ) if err != nil { return nil, nil, err @@ -169,14 +179,11 @@ func extractSelectionSet(ctx *PlanningContext, insertionPoint []string, parentTy selection.TypeCondition, selection.SelectionSet, location, - childstep, ) if err != nil { return nil, nil, err } - if !selectionSetHasFieldNamed(selectionSet, "__typename") { - selectionSet = append(selectionSet, &ast.Field{Alias: "__typename", Name: "__typename", Definition: &ast.FieldDefinition{Name: "__typename", Type: ast.NamedType("String", nil)}}) - } + selectionSet = append(selectionSet, &ast.Field{Alias: "__typename", Name: "__typename", Definition: &ast.FieldDefinition{Name: "__typename", Type: ast.NamedType("String", nil)}}) inlineFragment := *selection inlineFragment.SelectionSet = selectionSet selectionSetResult = append(selectionSetResult, &inlineFragment) @@ -188,14 +195,11 @@ func extractSelectionSet(ctx *PlanningContext, insertionPoint []string, parentTy selection.Definition.TypeCondition, selection.Definition.SelectionSet, location, - childstep, ) if err != nil { return nil, nil, err } - if !selectionSetHasFieldNamed(selectionSet, "__typename") { - selectionSet = append(selectionSet, &ast.Field{Alias: "__typename", Name: "__typename", Definition: &ast.FieldDefinition{Name: "__typename", Type: ast.NamedType("String", nil)}}) - } + selectionSet = append(selectionSet, &ast.Field{Alias: "__typename", Name: "__typename", Definition: &ast.FieldDefinition{Name: "__typename", Type: ast.NamedType("String", nil)}}) inlineFragment := ast.InlineFragment{ TypeCondition: selection.Definition.TypeCondition, SelectionSet: selectionSet, @@ -209,7 +213,7 @@ func extractSelectionSet(ctx *PlanningContext, insertionPoint []string, parentTy if len(remoteSelections) > 0 { // Create child steps for all remote field selections - childrenSteps, err := createSteps(ctx, insertionPoint, parentType, location, remoteSelections, true) + childrenSteps, err := createSteps(ctx, insertionPoint, parentType, location, remoteSelections) if err != nil { return nil, nil, err } @@ -234,10 +238,10 @@ func extractSelectionSet(ctx *PlanningContext, insertionPoint []string, parentTy } parentDef := ctx.Schema.Types[parentType] - // For abstract types, add an id fragment for all possible boundary - // implementations. This assures that abstract boundaries always return - // with an id, even if they didn't make a selection on the returned type. if parentDef.IsAbstractType() { + // For abstract types, add an id fragment for all possible boundary + // implementations. This assures that abstract boundaries always return + // with an id, even if they didn't make a selection on the returned type for implementationName, abstractTypes := range ctx.Schema.Implements { if !ctx.IsBoundary[implementationName] { continue @@ -247,40 +251,28 @@ func extractSelectionSet(ctx *PlanningContext, insertionPoint []string, parentTy continue } implementationType := ctx.Schema.Types[implementationName] - possibleId := &ast.InlineFragment{ - TypeCondition: implementationName, - SelectionSet: []ast.Selection{ - &ast.Field{ - Alias: "_id", - Name: "id", - Definition: implementationType.Fields.ForName("id"), - }, - }, - ObjectDefinition: implementationType, + + if idDef := implementationType.Fields.ForName("id"); idDef != nil { + possibleId := &ast.InlineFragment{ + TypeCondition: implementationName, + SelectionSet: []ast.Selection{&ast.Field{Alias: "_bramble_id", Name: "id", Definition: idDef}}, + ObjectDefinition: implementationType, + } + selectionSetResult = append(selectionSetResult, possibleId) } - selectionSetResult = append([]ast.Selection{possibleId}, selectionSetResult...) break } } - // Otherwise, add an id selection to boundary types where the result - // will be merged with another step (i.e.: has children or is a child step). - } else if parentType != queryObjectName && - parentType != mutationObjectName && - ctx.IsBoundary[parentType] && - (childstep || len(childrenStepsResult) > 0) && - parentDef.Fields.ForName("id") != nil && - !selectionSetHasFieldNamed(selectionSetResult, "id") { - id := &ast.Field{ - Alias: "_id", - Name: "id", - Definition: parentDef.Fields.ForName("id"), + } else if parentType != queryObjectName && parentType != mutationObjectName && ctx.IsBoundary[parentType] { + // Otherwise, add an id selection to all boundary types + if idDef := parentDef.Fields.ForName("id"); idDef != nil { + selectionSetResult = append(selectionSetResult, &ast.Field{Alias: "_bramble_id", Name: "id", Definition: idDef}) } - selectionSetResult = append([]ast.Selection{id}, selectionSetResult...) } return selectionSetResult, childrenStepsResult, nil } -func routeSelectionSet(ctx *PlanningContext, parentType, parentLocation string, input ast.SelectionSet) (map[string]ast.SelectionSet, error) { +func routeSelectionSet(ctx *PlanningContext, parentType string, parentLocation string, input ast.SelectionSet) (map[string]ast.SelectionSet, error) { result := map[string]ast.SelectionSet{} if parentLocation == "" { // if we're at the root, we extract the selection set for each service @@ -360,16 +352,6 @@ func filterSelectionSetByLoc(ctx *PlanningContext, ss ast.SelectionSet, loc, par return res } -func selectionSetHasFieldNamed(selectionSet []ast.Selection, fieldName string) bool { - for _, selection := range selectionSet { - field, ok := selection.(*ast.Field) - if ok && field.Name == fieldName { - return true - } - } - return false -} - // FieldURLMap maps fields to service URLs type FieldURLMap map[string]string diff --git a/plan_fixtures_test.go b/plan_fixtures_test.go index 1ce43edc..506b699f 100644 --- a/plan_fixtures_test.go +++ b/plan_fixtures_test.go @@ -264,7 +264,7 @@ var PlanTestFixture6 = &PlanTestFixture{ } -func (f *PlanTestFixture) Plan(t *testing.T, query string) *QueryPlan { +func (f *PlanTestFixture) Plan(t *testing.T, query string) (*QueryPlan, error) { t.Helper() schema := gqlparser.MustLoadSchema(&ast.Source{Name: "fixture", Input: f.Schema}) operation := gqlparser.MustLoadQuery(schema, query) @@ -274,13 +274,19 @@ func (f *PlanTestFixture) Plan(t *testing.T, query string) *QueryPlan { "B": {Name: "B", ServiceURL: "B"}, "C": {Name: "C", ServiceURL: "C"}, }}) - require.NoError(t, err) - actual.SortSteps() - return actual + return actual, err } func (f *PlanTestFixture) Check(t *testing.T, query string, expectedJSON string) { - assert.JSONEq(t, expectedJSON, jsonMustMarshal(f.Plan(t, query))) + plan, err := f.Plan(t, query) + require.NoError(t, err) + plan.SortSteps() + assert.JSONEq(t, expectedJSON, jsonMustMarshal(plan)) +} + +func (f *PlanTestFixture) CheckError(t *testing.T, query string) { + _, err := f.Plan(t, query) + require.Error(t, err) } func (f *PlanTestFixture) CheckUnorderedRootFieldSelections(t *testing.T, query string, expectedSelections []string) { @@ -288,9 +294,10 @@ func (f *PlanTestFixture) CheckUnorderedRootFieldSelections(t *testing.T, query Variables: map[string]interface{}{}, }) - result := f.Plan(t, query) - rootField := result.RootSteps[0].SelectionSet[0].(*ast.Field) + result, err := f.Plan(t, query) + require.NoError(t, err) + rootField := result.RootSteps[0].SelectionSet[0].(*ast.Field) assert.Equal(t, len(rootField.SelectionSet), len(expectedSelections)) for _, expectedSelection := range expectedSelections { diff --git a/plan_test.go b/plan_test.go index d5e8eb20..9294464d 100644 --- a/plan_test.go +++ b/plan_test.go @@ -13,7 +13,7 @@ func TestQueryPlanA(t *testing.T) { { "ServiceURL": "A", "ParentType": "Query", - "SelectionSet": "{ movies { id title } }", + "SelectionSet": "{ movies { id title _bramble_id: id } }", "InsertionPoint": null, "Then": null } @@ -29,13 +29,13 @@ func TestQueryPlanAB1(t *testing.T) { { "ServiceURL": "A", "ParentType": "Query", - "SelectionSet": "{ movies { id } }", + "SelectionSet": "{ movies { id _bramble_id: id } }", "InsertionPoint": null, "Then": [ { "ServiceURL": "B", "ParentType": "Movie", - "SelectionSet": "{ _id: id compTitles(limit: 42) { id } }", + "SelectionSet": "{ compTitles(limit: 42) { id _bramble_id: id } _bramble_id: id }", "InsertionPoint": ["movies"], "Then": null } @@ -53,13 +53,13 @@ func TestQueryPlanAB2(t *testing.T) { { "ServiceURL": "A", "ParentType": "Query", - "SelectionSet": "{ movies { id } }", + "SelectionSet": "{ movies { id _bramble_id: id } }", "InsertionPoint": null, "Then": [ { "ServiceURL": "B", "ParentType": "Movie", - "SelectionSet": "{ _id: id compTitles(limit: 42) { id compTitles(limit: 666) { id } } }", + "SelectionSet": "{ compTitles(limit: 42) { id compTitles(limit: 666) { id _bramble_id: id } _bramble_id: id } _bramble_id: id }", "InsertionPoint": ["movies"], "Then": null } @@ -77,19 +77,19 @@ func TestQueryPlanABA1(t *testing.T) { { "ServiceURL": "A", "ParentType": "Query", - "SelectionSet": "{ movies { id } }", + "SelectionSet": "{ movies { id _bramble_id: id } }", "InsertionPoint": null, "Then": [ { "ServiceURL": "B", "ParentType": "Movie", - "SelectionSet": "{ _id: id compTitles(limit: 42) { id } }", + "SelectionSet": "{ compTitles(limit: 42) { id _bramble_id: id } _bramble_id: id }", "InsertionPoint": ["movies"], "Then": [ { "ServiceURL": "A", "ParentType": "Movie", - "SelectionSet": "{ _id: id title }", + "SelectionSet": "{ title _bramble_id: id }", "InsertionPoint": ["movies", "compTitles"], "Then": null } @@ -109,26 +109,26 @@ func TestQueryPlanABA2(t *testing.T) { { "ServiceURL": "A", "ParentType": "Query", - "SelectionSet": "{ movies { id } }", + "SelectionSet": "{ movies { id _bramble_id: id } }", "InsertionPoint": null, "Then": [ { "ServiceURL": "B", "ParentType": "Movie", - "SelectionSet": "{ _id: id compTitles(limit: 42) { id compTitles(limit: 666) { id } } }", + "SelectionSet": "{ compTitles(limit: 42) { id compTitles(limit: 666) { id _bramble_id: id } _bramble_id: id } _bramble_id: id }", "InsertionPoint": ["movies"], "Then": [ { "ServiceURL": "A", "ParentType": "Movie", - "SelectionSet": "{ _id: id title }", + "SelectionSet": "{ title _bramble_id: id }", "InsertionPoint": ["movies", "compTitles", "compTitles"], "Then": null }, { "ServiceURL": "A", "ParentType": "Movie", - "SelectionSet": "{ _id: id title }", + "SelectionSet": "{ title _bramble_id: id }", "InsertionPoint": ["movies", "compTitles"], "Then": null } @@ -148,7 +148,7 @@ func TestQueryPlanAC(t *testing.T) { { "ServiceURL": "A", "ParentType": "Query", - "SelectionSet": "{ movies { id title } }", + "SelectionSet": "{ movies { id title _bramble_id: id } }", "InsertionPoint": null, "Then": null }, @@ -171,13 +171,13 @@ func TestQueryPlanWithAliases(t *testing.T) { { "ServiceURL": "A", "ParentType": "Query", - "SelectionSet": "{ a1: movies { a2: id a3: title } }", + "SelectionSet": "{ a1: movies { a2: id a3: title _bramble_id: id } }", "InsertionPoint": null, "Then": [ { "ServiceURL": "B", "ParentType": "Movie", - "SelectionSet": "{ _id: id a4: compTitles(limit: 42) { a5: id } }", + "SelectionSet": "{ a4: compTitles(limit: 42) { a5: id _bramble_id: id } _bramble_id: id }", "InsertionPoint": ["a1"], "Then": null } @@ -195,7 +195,7 @@ func TestQueryPlanWithTypename(t *testing.T) { { "ServiceURL": "A", "ParentType": "Query", - "SelectionSet": "{ movies { id title __typename } }", + "SelectionSet": "{ movies { id title __typename _bramble_id: id } }", "InsertionPoint": null, "Then": null }, @@ -241,7 +241,7 @@ func TestQueryPlanOptionalArgument(t *testing.T) { { "ServiceURL": "A", "ParentType": "Query", - "SelectionSet": "{ movies { id title(language: French) } }", + "SelectionSet": "{ movies { id title(language: French) _bramble_id: id } }", "InsertionPoint": null, "Then": null } @@ -264,7 +264,7 @@ func TestQueryPlanInlineFragment(t *testing.T) { { "ServiceURL": "A", "ParentType": "Query", - "SelectionSet": "{ movies { ... on Movie { id title(language: French) __typename } } }", + "SelectionSet": "{ movies { ... on Movie { id title(language: French) _bramble_id: id __typename } _bramble_id: id } }", "InsertionPoint": null, "Then": null } @@ -288,7 +288,7 @@ func TestQueryPlanInlineFragmentDoesNotDuplicateTypename(t *testing.T) { { "ServiceURL": "A", "ParentType": "Query", - "SelectionSet": "{ movies { ... on Movie { __typename id title(language: French) } } }", + "SelectionSet": "{ movies { ... on Movie { __typename id title(language: French) _bramble_id: id __typename } _bramble_id: id } }", "InsertionPoint": null, "Then": null } @@ -314,13 +314,13 @@ func TestQueryPlanInlineFragmentPlan(t *testing.T) { { "ServiceURL": "A", "ParentType": "Query", - "SelectionSet": "{ movies { _id: id ... on Movie { id title(language: French) __typename } } }", + "SelectionSet": "{ movies { ... on Movie { id title(language: French) _bramble_id: id __typename } _bramble_id: id } }", "InsertionPoint": null, "Then": [ { "ServiceURL": "B", "ParentType": "Movie", - "SelectionSet": "{ _id: id compTitles(limit: 42) { id } }", + "SelectionSet": "{ compTitles(limit: 42) { id _bramble_id: id } _bramble_id: id }", "InsertionPoint": ["movies"], "Then": null } @@ -347,7 +347,7 @@ func TestQueryPlanFragmentSpread1(t *testing.T) { { "ServiceURL": "A", "ParentType": "Query", - "SelectionSet": "{ movies { ... on Movie { id title(language: French) __typename } } }", + "SelectionSet": "{ movies { ... on Movie { id title(language: French) _bramble_id: id __typename } _bramble_id: id } }", "InsertionPoint": null, "Then": null } @@ -357,32 +357,6 @@ func TestQueryPlanFragmentSpread1(t *testing.T) { PlanTestFixture1.Check(t, query, plan) } -func TestQueryPlanFragmentSpread1DontDuplicateTypename(t *testing.T) { - query := ` - fragment Frag on Movie { - id - __typename - title(language: French) - } - { - movies { - ...Frag - } - }` - plan := `{ - "RootSteps": [ - { - "ServiceURL": "A", - "ParentType": "Query", - "SelectionSet": "{ movies { ... on Movie { id __typename title(language: French) } } }", - "InsertionPoint": null, - "Then": null - } - ] - }` - PlanTestFixture1.Check(t, query, plan) -} - func TestQueryPlanFragmentSpread2(t *testing.T) { query := ` fragment Frag on Query { @@ -399,7 +373,7 @@ func TestQueryPlanFragmentSpread2(t *testing.T) { { "ServiceURL": "A", "ParentType": "Query", - "SelectionSet": "{ movies { id title(language: French) } }", + "SelectionSet": "{ movies { id title(language: French) _bramble_id: id } }", "InsertionPoint": null, "Then": null } @@ -426,19 +400,19 @@ func TestQueryPlanCompleteDeepTraversal(t *testing.T) { { "ServiceURL": "A", "ParentType": "Query", - "SelectionSet": "{ shop1 { name products { _id: id } } }", + "SelectionSet": "{ shop1 { name products { _bramble_id: id } } }", "InsertionPoint": null, "Then": [ { "ServiceURL": "B", "ParentType": "Product", - "SelectionSet": "{ _id: id name collection { _id: id } }", + "SelectionSet": "{ name collection { _bramble_id: id } _bramble_id: id }", "InsertionPoint": ["shop1", "products"], "Then": [ { "ServiceURL": "C", "ParentType": "Collection", - "SelectionSet": "{ _id: id name }", + "SelectionSet": "{ name _bramble_id: id }", "InsertionPoint": ["shop1", "products", "collection"], "Then": null } @@ -468,13 +442,13 @@ func TestQueryPlanMergeInsertionPointSteps(t *testing.T) { { "ServiceURL": "A", "ParentType": "Query", - "SelectionSet": "{ shop1 { products { _id: id } products { _id: id } } }", + "SelectionSet": "{ shop1 { products { _bramble_id: id } products { _bramble_id: id } } }", "InsertionPoint": null, "Then": [ { "ServiceURL": "B", "ParentType": "Product", - "SelectionSet": "{ _id: id name _id: id name }", + "SelectionSet": "{ name _bramble_id: id name _bramble_id: id }", "InsertionPoint": ["shop1", "products"], "Then": null } @@ -494,8 +468,8 @@ func TestQueryPlanExpandAbstractTypesWithPossibleBoundaryIds(t *testing.T) { }` rootFieldSelections := []string{ "name", - "... on Lion { _id: id }", - "... on Snake { _id: id }", + "... on Lion { _bramble_id: id }", + "... on Snake { _bramble_id: id }", } PlanTestFixture3.CheckUnorderedRootFieldSelections(t, query, rootFieldSelections) } @@ -515,10 +489,10 @@ func TestQueryPlanInlineFragmentSpreadOfInterface(t *testing.T) { }` rootFieldSelections := []string{ "name", - "... on Lion { _id: id }", - "... on Snake { _id: id }", - "... on Lion { maneColor __typename }", - "... on Snake { _id: id __typename }", + "... on Lion { _bramble_id: id }", + "... on Snake { _bramble_id: id }", + "... on Lion { maneColor _bramble_id: id __typename }", + "... on Snake { _bramble_id: id __typename }", } PlanTestFixture3.CheckUnorderedRootFieldSelections(t, query, rootFieldSelections) } @@ -530,7 +504,7 @@ func TestQueryPlanSkipDirective(t *testing.T) { { "ServiceURL": "A", "ParentType": "Query", - "SelectionSet": "{ movies { id title @skip(if: false) } }", + "SelectionSet": "{ movies { id title @skip(if: false) _bramble_id: id } }", "InsertionPoint": null, "Then": null } @@ -546,7 +520,7 @@ func TestQueryPlanIncludeDirective(t *testing.T) { { "ServiceURL": "A", "ParentType": "Query", - "SelectionSet": "{ movies { id title @include(if: true) } }", + "SelectionSet": "{ movies { id title @include(if: true) _bramble_id: id } }", "InsertionPoint": null, "Then": null } @@ -562,7 +536,7 @@ func TestQueryPlanSkipAndIncludeDirective(t *testing.T) { { "ServiceURL": "A", "ParentType": "Query", - "SelectionSet": "{ movies { id title @skip(if: false) @include(if: true) } }", + "SelectionSet": "{ movies { id title @skip(if: false) @include(if: true) _bramble_id: id } }", "InsertionPoint": null, "Then": null } @@ -578,13 +552,13 @@ func TestQueryPlanSkipAndIncludeDirectiveInChildStep(t *testing.T) { { "ServiceURL": "A", "ParentType": "Query", - "SelectionSet": "{ movies { id } }", + "SelectionSet": "{ movies { id _bramble_id: id } }", "InsertionPoint": null, "Then": [ { "ServiceURL": "B", "ParentType": "Movie", - "SelectionSet": "{ _id: id compTitles(limit: 42) { id @skip(if: false) @include(if: true) } }", + "SelectionSet": "{ compTitles(limit: 42) { id @skip(if: false) @include(if: true) _bramble_id: id } _bramble_id: id }", "InsertionPoint": ["movies"], "Then": null } @@ -602,13 +576,13 @@ func TestQueryPlanSupportsAliasing(t *testing.T) { { "ServiceURL": "A", "ParentType": "Query", - "SelectionSet": "{ foo: movies { id aliasTitle: title } }", + "SelectionSet": "{ foo: movies { id aliasTitle: title _bramble_id: id } }", "InsertionPoint": null, "Then": [ { "ServiceURL": "B", "ParentType": "Movie", - "SelectionSet": "{ _id: id bar: compTitles(limit: 42) { id } }", + "SelectionSet": "{ bar: compTitles(limit: 42) { id _bramble_id: id } _bramble_id: id }", "InsertionPoint": [ "foo" ], @@ -616,7 +590,7 @@ func TestQueryPlanSupportsAliasing(t *testing.T) { { "ServiceURL": "A", "ParentType": "Movie", - "SelectionSet": "{ _id: id compTitleAliasTitle: title }", + "SelectionSet": "{ compTitleAliasTitle: title _bramble_id: id }", "InsertionPoint": [ "foo", "bar" @@ -686,13 +660,13 @@ func TestQueryPlanSupportsMutations(t *testing.T) { { "ServiceURL": "A", "ParentType": "Mutation", - "SelectionSet": "{ updateTitle(id: \"2\", title: \"New title\") { _id: id title } }", + "SelectionSet": "{ updateTitle(id: \"2\", title: \"New title\") { title _bramble_id: id } }", "InsertionPoint": null, "Then": [ { "ServiceURL": "B", "ParentType": "Movie", - "SelectionSet": "{ _id: id release }", + "SelectionSet": "{ release _bramble_id: id }", "InsertionPoint": [ "updateTitle" ], @@ -712,13 +686,13 @@ func TestQueryPlanWithPaginatedBoundaryType(t *testing.T) { { "ServiceURL": "A", "ParentType": "Query", - "SelectionSet": "{ foo { foos { cursor page { id name } } } }", + "SelectionSet": "{ foo { foos { cursor page { id name _bramble_id: id } } } }", "InsertionPoint": null, "Then": [ { "ServiceURL": "B", "ParentType": "Foo", - "SelectionSet": "{ _id: id size }", + "SelectionSet": "{ size _bramble_id: id }", "InsertionPoint": [ "foo", "foos", "page" ], "Then": null } @@ -762,7 +736,7 @@ func TestQueryPlanWithNestedNamespaces(t *testing.T) { date: String! } - type CompTitle { + type CompTitle @boundary { id: ID! score: Int! } @@ -805,13 +779,13 @@ func TestQueryPlanWithNestedNamespaces(t *testing.T) { { "ServiceURL": "A", "ParentType": "Mutation", - "SelectionSet": "{ firstLevel { secondLevel { movie { id compTitles { id } releases { date } } } } }", + "SelectionSet": "{ firstLevel { secondLevel { movie { id compTitles { id _bramble_id: id } releases { date _bramble_id: id } _bramble_id: id } } } }", "InsertionPoint": null, "Then": [ { "ServiceURL": "B", "ParentType": "CompTitle", - "SelectionSet": "{ _id: id score }", + "SelectionSet": "{ score _bramble_id: id }", "InsertionPoint": [ "firstLevel", "secondLevel", @@ -844,7 +818,7 @@ func TestQueryPlanNoUnnessecaryID(t *testing.T) { { "ServiceURL": "A", "ParentType": "Query", - "SelectionSet": "{ movies { title } }", + "SelectionSet": "{ movies { title _bramble_id: id } }", "InsertionPoint": null, "Then": null } @@ -852,3 +826,11 @@ func TestQueryPlanNoUnnessecaryID(t *testing.T) { } `) } + +func TestQueryPlanValidateReservedIdAlias(t *testing.T) { + PlanTestFixture1.CheckError(t, "{ movies { _bramble_id: title } }") +} + +func TestQueryPlanValidateReservedTypenameAlias(t *testing.T) { + PlanTestFixture1.CheckError(t, "{ movies { __typename: title } }") +} diff --git a/query_execution.go b/query_execution.go index 4146d245..983da01e 100644 --- a/query_execution.go +++ b/query_execution.go @@ -283,10 +283,10 @@ func (q *queryExecution) createGQLErrors(step *QueryPlanStep, err error) gqlerro // crawling for ids: // [ // { -// "_id": "MOVIE1", +// "_bramble_id": "MOVIE1", // "compTitles": [ // { -// "_id": "1" +// "_bramble_id": "1" // } // ] // } @@ -520,7 +520,7 @@ func mergeExecutionResultsRec(src interface{}, dst interface{}, insertionPoint [ } if srcID == dstID { for k, v := range result { - if k == "_id" || k == "id" { + if k == "_bramble_id" { continue } @@ -570,15 +570,11 @@ func mergeExecutionResultsRec(src interface{}, dst interface{}, insertionPoint [ } func boundaryIDFromMap(boundaryMap map[string]interface{}) (string, error) { - id, ok := boundaryMap["_id"].(string) + id, ok := boundaryMap["_bramble_id"].(string) if ok { return id, nil } - id, ok = boundaryMap["id"].(string) - if ok { - return id, nil - } - return "", errors.New("boundaryIDFromMap: 'id' or '_id' not found") + return "", errors.New("boundaryIDFromMap: \"_bramble_id\" not found") } func getBoundaryFieldResults(src []interface{}) ([]map[string]interface{}, error) {