From 3b44d3f025d6933f5998768fedad92dbfee3287e Mon Sep 17 00:00:00 2001 From: Anurag Date: Tue, 27 Oct 2020 18:46:30 +0530 Subject: [PATCH] feat(Query): Enable persistent queries in dgraph (#6788) Co-authored-by: Abhimanyu Singh Gaur <12651351+abhimanyusinghgaur@users.noreply.github.com> --- dgraph/cmd/alpha/run_test.go | 6 +- dgraph/cmd/bulk/systest/test-bulk-schema.sh | 2 + edgraph/graphql.go | 121 ++++++++++++++++++++ ee/acl/acl_test.go | 42 +++++-- graphql/admin/admin.go | 8 ++ graphql/e2e/common/admin.go | 84 ++++++++++++++ graphql/e2e/common/common.go | 10 +- graphql/e2e/common/query.go | 41 +++++++ graphql/e2e/directives/schema_response.json | 21 ++++ graphql/e2e/normal/schema_response.json | 21 ++++ graphql/e2e/schema/schema_test.go | 23 +++- graphql/schema/request.go | 12 +- graphql/web/http.go | 23 +++- schema/schema.go | 19 +++ systest/backup/encryption/backup_test.go | 5 +- systest/backup/filesystem/backup_test.go | 5 +- systest/backup/minio/backup_test.go | 5 +- systest/export/export_test.go | 6 + systest/mutations_test.go | 4 +- systest/queries_test.go | 12 +- worker/export.go | 4 + x/keys.go | 13 ++- x/x.go | 10 +- 23 files changed, 459 insertions(+), 38 deletions(-) diff --git a/dgraph/cmd/alpha/run_test.go b/dgraph/cmd/alpha/run_test.go index 298d56e4370..1b41d85ec85 100644 --- a/dgraph/cmd/alpha/run_test.go +++ b/dgraph/cmd/alpha/run_test.go @@ -247,7 +247,7 @@ func TestDeletePredicate(t *testing.T) { testutil.CompareJSON(t, `{"data":{"schema":[`+ `{"predicate":"age","type":"default"},`+ `{"predicate":"name","type":"string","index":true, "tokenizer":["term"]},`+ - x.AclPredicates+","+x.GraphqlPredicates+","+x.CorsPredicate+","+ + x.AclPredicates+","+x.GraphqlPredicates+","+x.CorsPredicate+","+x.PersistedQueryPredicate+","+ `{"predicate":"dgraph.type","type":"string","index":true, "tokenizer":["exact"], "list":true}],`+x.InitialTypes+`}}`, output) @@ -1077,7 +1077,7 @@ func TestListTypeSchemaChange(t *testing.T) { res, err = runGraphqlQuery(q) require.NoError(t, err) testutil.CompareJSON(t, `{"data":{"schema":[`+ - x.AclPredicates+","+x.GraphqlPredicates+","+x.CorsPredicate+","+ + x.AclPredicates+","+x.GraphqlPredicates+","+x.CorsPredicate+","+x.PersistedQueryPredicate+","+ `{"predicate":"occupations","type":"string"},`+ `{"predicate":"dgraph.type", "type":"string", "index":true, "tokenizer": ["exact"], "list":true}],`+x.InitialTypes+`}}`, res) @@ -1325,7 +1325,7 @@ func TestDropAll(t *testing.T) { require.NoError(t, err) testutil.CompareJSON(t, `{"data":{"schema":[`+ - x.AclPredicates+","+x.GraphqlPredicates+","+x.CorsPredicate+","+ + x.AclPredicates+","+x.GraphqlPredicates+","+x.CorsPredicate+","+x.PersistedQueryPredicate+","+ `{"predicate":"dgraph.type", "type":"string", "index":true, "tokenizer":["exact"], "list":true}],`+x.InitialTypes+`}}`, output) diff --git a/dgraph/cmd/bulk/systest/test-bulk-schema.sh b/dgraph/cmd/bulk/systest/test-bulk-schema.sh index 66510ab0161..d124f04f19f 100755 --- a/dgraph/cmd/bulk/systest/test-bulk-schema.sh +++ b/dgraph/cmd/bulk/systest/test-bulk-schema.sh @@ -201,6 +201,8 @@ EOF diff <(LC_ALL=C sort all_dbs.out | uniq -c) - < 0 { + if err := json.Unmarshal(storedQuery.Json, shaQueryRes); err != nil { + return err + } + } + + if len(shaQueryRes.Me) == 0 { + if query == "" { + return errors.New("PersistedQueryNotFound") + } + if match, err := hashMatches(query, sha256Hash); err != nil { + return err + } else if !match { + return errors.New("provided sha does not match query") + } + + req := &api.Request{ + Mutations: []*api.Mutation{ + { + Set: []*api.NQuad{ + { + Subject: "_:a", + Predicate: "dgraph.graphql.p_query", + ObjectValue: &api.Value{Val: &api.Value_StrVal{StrVal: query}}, + }, + { + Subject: "_:a", + Predicate: "dgraph.graphql.p_sha256hash", + ObjectValue: &api.Value{Val: &api.Value_StrVal{StrVal: sha256Hash}}, + }, + { + Subject: "_:a", + Predicate: "dgraph.type", + ObjectValue: &api.Value{Val: &api.Value_StrVal{ + StrVal: "dgraph.graphql.persisted_query"}}, + }, + }, + }, + }, + CommitNow: true, + } + + _, err := (&Server{}).doQuery(context.WithValue(ctx, IsGraphql, true), req, NoAuthorize) + return err + + } + + if len(shaQueryRes.Me) != 1 { + return fmt.Errorf("same sha returned %d queries", len(shaQueryRes.Me)) + } + + if len(query) > 0 && shaQueryRes.Me[0].PersistedQuery != query { + return errors.New("query does not match persisted query") + } + + gqlReq.Query = shaQueryRes.Me[0].PersistedQuery + return nil + +} + +func hashMatches(query, sha256Hash string) (bool, error) { + hasher := sha256.New() + _, err := hasher.Write([]byte(query)) + if err != nil { + return false, err + } + hashGenerated := hex.EncodeToString(hasher.Sum(nil)) + return hashGenerated == sha256Hash, nil +} diff --git a/ee/acl/acl_test.go b/ee/acl/acl_test.go index 47d58fcbe93..459c5e915da 100644 --- a/ee/acl/acl_test.go +++ b/ee/acl/acl_test.go @@ -299,7 +299,7 @@ func getGrootAndGuardiansUid(t *testing.T, dg *dgo.Dgraph) (string, string) { GrootUser []userNode `json:"grootUser"` } - resp,err := txn.Query(ctx, grootUserQuery) + resp, err := txn.Query(ctx, grootUserQuery) require.NoError(t, err, "groot user query failed") var userResp userQryResp @@ -939,7 +939,7 @@ func TestUnauthorizedDeletion(t *testing.T) { err = userClient.Login(ctx, userid, userpassword) require.NoError(t, err) - _, err = deleteUsingNQuad(userClient, "<" + nodeUID + ">", "<" + unAuthPred + ">", "*") + _, err = deleteUsingNQuad(userClient, "<"+nodeUID+">", "<"+unAuthPred+">", "*") require.Error(t, err) require.Contains(t, err.Error(), "PermissionDenied") @@ -1395,7 +1395,7 @@ func TestDeleteQueryWithACLPermissions(t *testing.T) { require.NoError(t, err) // delete S * * (user now has permission to name and age) - _, err = deleteUsingNQuad(userClient, "<" + nodeUID + ">", "*", "*") + _, err = deleteUsingNQuad(userClient, "<"+nodeUID+">", "*", "*") require.NoError(t, err) accessJwt, _, err = testutil.HttpLogin(&testutil.LoginParams{ @@ -1416,7 +1416,7 @@ func TestDeleteQueryWithACLPermissions(t *testing.T) { time.Sleep(5 * time.Second) // delete S * * (user now has permission to name, age and dgraph.type) - _, err = deleteUsingNQuad(userClient, "<" + nodeUID + ">", "*", "*") + _, err = deleteUsingNQuad(userClient, "<"+nodeUID+">", "*", "*") require.NoError(t, err) accessJwt, _, err = testutil.HttpLogin(&testutil.LoginParams{ @@ -2146,6 +2146,16 @@ func TestSchemaQueryWithACL(t *testing.T) { ], "upsert": true }, + { + "predicate":"dgraph.graphql.p_query", + "type":"string" + }, + { + "predicate":"dgraph.graphql.p_sha256hash", + "type":"string", + "index":true, + "tokenizer":["exact"] + }, { "predicate": "dgraph.graphql.schema", "type": "string" @@ -2231,6 +2241,17 @@ func TestSchemaQueryWithACL(t *testing.T) { ], "name": "dgraph.graphql.history" }, + { + "fields": [ + { + "name": "dgraph.graphql.p_query" + }, + { + "name": "dgraph.graphql.p_sha256hash" + } + ], + "name": "dgraph.graphql.persisted_query" + }, { "fields": [ { @@ -2289,6 +2310,10 @@ func TestSchemaQueryWithACL(t *testing.T) { "fields": [], "name": "dgraph.graphql.history" }, + { + "fields":[], + "name":"dgraph.graphql.persisted_query" + }, { "fields": [], "name": "dgraph.type.Group" @@ -3221,7 +3246,6 @@ func TestDeleteGrootUserShouldFail(t *testing.T) { }) require.NoError(t, err, "login failed") - resp := deleteUser(t, accessJwt, "groot", false) require.Contains(t, resp.Errors.Error(), "guardians group and groot user cannot be deleted.") @@ -3252,12 +3276,12 @@ func TestDeleteGrootAndGuardiansUsingDelNQuadShouldFail(t *testing.T) { grootUid, guardiansUid := getGrootAndGuardiansUid(t, dg) // Try deleting groot user - _, err = deleteUsingNQuad(dg, "<" + grootUid + ">", "*", "*") + _, err = deleteUsingNQuad(dg, "<"+grootUid+">", "*", "*") require.Error(t, err, "Deleting groot user should have returned an error") require.Contains(t, err.Error(), "Properties of guardians group and groot user cannot be deleted") // Try deleting guardians group - _, err = deleteUsingNQuad(dg, "<" + guardiansUid + ">", "*", "*") + _, err = deleteUsingNQuad(dg, "<"+guardiansUid+">", "*", "*") require.Error(t, err, "Deleting guardians group should have returned an error") require.Contains(t, err.Error(), "Properties of guardians group and groot user cannot be deleted") } @@ -3291,7 +3315,7 @@ func TestDropAllShouldResetGuardiansAndGroot(t *testing.T) { // Try Drop All op := api.Operation{ DropAll: true, - DropOp: api.Operation_ALL, + DropOp: api.Operation_ALL, } if err := dg.Alter(ctx, &op); err != nil { t.Fatalf("Unable to drop all. Error:%v", err) @@ -3302,7 +3326,7 @@ func TestDropAllShouldResetGuardiansAndGroot(t *testing.T) { // Try Drop Data op = api.Operation{ - DropOp: api.Operation_DATA, + DropOp: api.Operation_DATA, } if err := dg.Alter(ctx, &op); err != nil { t.Fatalf("Unable to drop data. Error:%v", err) diff --git a/graphql/admin/admin.go b/graphql/admin/admin.go index 7bce32b1c04..288033b2894 100644 --- a/graphql/admin/admin.go +++ b/graphql/admin/admin.go @@ -79,6 +79,14 @@ const ( created_at: DateTime! @dgraph(pred: "dgraph.graphql.schema_created_at") } + """ + PersistedQuery contains the query and sha256hash of the query. + """ + type PersistedQuery @dgraph(type: "dgraph.graphql.persisted_query") { + query: String! @dgraph(pred: "dgraph.graphql.p_query") + sha256Hash: String! @id @dgraph(pred: "dgraph.graphql.p_sha256hash") + } + """ A NodeState is the state of an individual node in the Dgraph cluster. """ diff --git a/graphql/e2e/common/admin.go b/graphql/e2e/common/admin.go index 045eb21ccc1..ceb755d6a27 100644 --- a/graphql/e2e/common/admin.go +++ b/graphql/e2e/common/admin.go @@ -53,6 +53,16 @@ const ( ], "upsert": true }, + { + "predicate":"dgraph.graphql.p_query", + "type":"string" + }, + { + "predicate":"dgraph.graphql.p_sha256hash", + "type":"string", + "index":true, + "tokenizer":["exact"] + }, { "predicate": "dgraph.graphql.schema", "type": "string" @@ -104,6 +114,17 @@ const ( } ], "name": "dgraph.graphql.history" + }, + { + "fields": [ + { + "name": "dgraph.graphql.p_query" + }, + { + "name": "dgraph.graphql.p_sha256hash" + } + ], + "name": "dgraph.graphql.persisted_query" } ] }` @@ -128,6 +149,16 @@ const ( ], "upsert": true }, + { + "predicate":"dgraph.graphql.p_query", + "type":"string" + }, + { + "predicate":"dgraph.graphql.p_sha256hash", + "type":"string", + "index":true, + "tokenizer":["exact"] + }, { "predicate": "dgraph.graphql.schema", "type": "string" @@ -187,6 +218,17 @@ const ( } ], "name": "dgraph.graphql.history" + }, + { + "fields": [ + { + "name": "dgraph.graphql.p_query" + }, + { + "name": "dgraph.graphql.p_sha256hash" + } + ], + "name": "dgraph.graphql.persisted_query" } ] }` @@ -226,6 +268,16 @@ const ( ], "upsert": true }, + { + "predicate":"dgraph.graphql.p_query", + "type":"string" + }, + { + "predicate":"dgraph.graphql.p_sha256hash", + "type":"string", + "index":true, + "tokenizer":["exact"] + }, { "predicate": "dgraph.graphql.schema", "type": "string" @@ -288,6 +340,17 @@ const ( } ], "name": "dgraph.graphql.history" + }, + { + "fields": [ + { + "name": "dgraph.graphql.p_query" + }, + { + "name": "dgraph.graphql.p_sha256hash" + } + ], + "name": "dgraph.graphql.persisted_query" } ] }` @@ -335,6 +398,16 @@ const ( ], "upsert": true }, + { + "predicate":"dgraph.graphql.p_query", + "type":"string" + }, + { + "predicate":"dgraph.graphql.p_sha256hash", + "type":"string", + "index":true, + "tokenizer":["exact"] + }, { "predicate": "dgraph.graphql.schema", "type": "string" @@ -400,6 +473,17 @@ const ( } ], "name": "dgraph.graphql.history" + }, + { + "fields": [ + { + "name": "dgraph.graphql.p_query" + }, + { + "name": "dgraph.graphql.p_sha256hash" + } + ], + "name": "dgraph.graphql.persisted_query" } ] }` diff --git a/graphql/e2e/common/common.go b/graphql/e2e/common/common.go index 0963ceed569..7da98f174be 100644 --- a/graphql/e2e/common/common.go +++ b/graphql/e2e/common/common.go @@ -28,6 +28,8 @@ import ( "testing" "time" + "github.com/dgraph-io/dgraph/graphql/schema" + "github.com/dgraph-io/dgo/v200" "github.com/dgraph-io/dgo/v200/protos/api" "github.com/dgraph-io/dgraph/testutil" @@ -82,9 +84,10 @@ const ( // header to the same. type GraphQLParams struct { - Query string `json:"query"` - OperationName string `json:"operationName"` - Variables map[string]interface{} `json:"variables"` + Query string `json:"query"` + OperationName string `json:"operationName"` + Variables map[string]interface{} `json:"variables"` + Extensions *schema.RequestExtensions `json:"extensions,omitempty"` acceptGzip bool gzipEncoding bool Headers http.Header @@ -341,6 +344,7 @@ func RunAll(t *testing.T) { t.Run("alias works for queries", queryWithAlias) t.Run("cascade directive", queryWithCascade) t.Run("query geo near filter", queryGeoNearFilter) + t.Run("persisted query", persistedQuery) // mutation tests t.Run("add mutation", addMutation) diff --git a/graphql/e2e/common/query.go b/graphql/e2e/common/query.go index 8f19b919183..987f0b146b5 100644 --- a/graphql/e2e/common/query.go +++ b/graphql/e2e/common/query.go @@ -28,6 +28,8 @@ import ( "testing" "time" + "github.com/dgraph-io/dgraph/graphql/schema" + "github.com/dgraph-io/dgraph/testutil" "github.com/dgraph-io/dgraph/x" "github.com/google/go-cmp/cmp" @@ -2310,3 +2312,42 @@ func queryGeoNearFilter(t *testing.T) { // Cleanup deleteGqlType(t, "Hotel", map[string]interface{}{}, 3, nil) } + +func persistedQuery(t *testing.T) { + queryCountryParams := &GraphQLParams{ + Extensions: &schema.RequestExtensions{PersistedQuery: schema.PersistedQuery{ + Sha256Hash: "shaWithoutAnyPersistedQuery", + }}, + } + gqlResponse := queryCountryParams.ExecuteAsPost(t, GraphqlURL) + require.Len(t, gqlResponse.Errors, 1) + require.Contains(t, gqlResponse.Errors[0].Message, "PersistedQueryNotFound") + + queryCountryParams = &GraphQLParams{ + Query: `query ($countryName: String){ + queryCountry(filter: {name: {eq: $countryName}}) { + name + } + }`, + Variables: map[string]interface{}{"countryName": "Bangladesh"}, + Extensions: &schema.RequestExtensions{PersistedQuery: schema.PersistedQuery{ + Sha256Hash: "incorrectSha", + }}, + } + gqlResponse = queryCountryParams.ExecuteAsPost(t, GraphqlURL) + require.Len(t, gqlResponse.Errors, 1) + require.Contains(t, gqlResponse.Errors[0].Message, "provided sha does not match query") + + queryCountryParams.Extensions.PersistedQuery.Sha256Hash = "bbc0af44f82ce5c38e775f7f14c71e5eba1936b12b3e66c452ee262ef147f1ed" + gqlResponse = queryCountryParams.ExecuteAsPost(t, GraphqlURL) + RequireNoGQLErrors(t, gqlResponse) + + queryCountryParams.Query = "" + gqlResponse = queryCountryParams.ExecuteAsPost(t, GraphqlURL) + RequireNoGQLErrors(t, gqlResponse) + + // test get method as well + queryCountryParams.Extensions = nil + gqlResponse = queryCountryParams.ExecuteAsGet(t, GraphqlURL+`?extensions={"persistedQuery":{"sha256Hash":"bbc0af44f82ce5c38e775f7f14c71e5eba1936b12b3e66c452ee262ef147f1ed"}}`) + RequireNoGQLErrors(t, gqlResponse) +} diff --git a/graphql/e2e/directives/schema_response.json b/graphql/e2e/directives/schema_response.json index d1178d0234c..993d6e99945 100644 --- a/graphql/e2e/directives/schema_response.json +++ b/graphql/e2e/directives/schema_response.json @@ -165,6 +165,16 @@ "list": true, "upsert": true }, + { + "predicate":"dgraph.graphql.p_query", + "type":"string" + }, + { + "predicate":"dgraph.graphql.p_sha256hash", + "type":"string", + "index":true, + "tokenizer":["exact"] + }, { "predicate": "dgraph.graphql.schema", "type": "string" @@ -689,6 +699,17 @@ ], "name": "dgraph.graphql.history" }, + { + "fields": [ + { + "name": "dgraph.graphql.p_query" + }, + { + "name": "dgraph.graphql.p_sha256hash" + } + ], + "name": "dgraph.graphql.persisted_query" + }, { "fields": [ { diff --git a/graphql/e2e/normal/schema_response.json b/graphql/e2e/normal/schema_response.json index cf25ee44b83..66b1b34c5d4 100644 --- a/graphql/e2e/normal/schema_response.json +++ b/graphql/e2e/normal/schema_response.json @@ -422,6 +422,16 @@ "predicate": "dgraph.graphql.schema_history", "type": "string" }, + { + "predicate":"dgraph.graphql.p_query", + "type":"string" + }, + { + "predicate":"dgraph.graphql.p_sha256hash", + "type":"string", + "index":true, + "tokenizer":["exact"] + }, { "predicate": "dgraph.graphql.xid", "type": "string", @@ -871,6 +881,17 @@ ], "name": "dgraph.graphql.history" }, + { + "fields": [ + { + "name": "dgraph.graphql.p_query" + }, + { + "name": "dgraph.graphql.p_sha256hash" + } + ], + "name": "dgraph.graphql.persisted_query" + }, { "fields": [ { diff --git a/graphql/e2e/schema/schema_test.go b/graphql/e2e/schema/schema_test.go index 452801f2940..01f2c34a1e3 100644 --- a/graphql/e2e/schema/schema_test.go +++ b/graphql/e2e/schema/schema_test.go @@ -219,6 +219,16 @@ func TestConcurrentSchemaUpdates(t *testing.T) { ], "upsert": true }, + { + "predicate":"dgraph.graphql.p_query", + "type":"string" + }, + { + "predicate":"dgraph.graphql.p_sha256hash", + "type":"string", + "index":true, + "tokenizer":["exact"] + }, { "predicate": "dgraph.graphql.schema", "type": "string" @@ -278,7 +288,18 @@ func TestConcurrentSchemaUpdates(t *testing.T) { } ], "name": "dgraph.graphql.history" - } + }, + { + "fields": [ + { + "name": "dgraph.graphql.p_query" + }, + { + "name": "dgraph.graphql.p_sha256hash" + } + ], + "name": "dgraph.graphql.persisted_query" + } ] }`, tcases[lastSuccessTcaseIdx].dgraphSchema) finalGraphQLSchema := tcases[lastSuccessTcaseIdx].graphQLSchema diff --git a/graphql/schema/request.go b/graphql/schema/request.go index 6d92a0d8cbc..c0eb9d7715b 100644 --- a/graphql/schema/request.go +++ b/graphql/schema/request.go @@ -35,8 +35,18 @@ type Request struct { Query string `json:"query"` OperationName string `json:"operationName"` Variables map[string]interface{} `json:"variables"` + Extensions RequestExtensions + Header http.Header +} + +// RequestExtensions represents extensions recieved in requests +type RequestExtensions struct { + PersistedQuery PersistedQuery +} - Header http.Header +// PersistedQuery represents the query struct received from clients like Apollo +type PersistedQuery struct { + Sha256Hash string } // Operation finds the operation in req, if it is a valid request for GraphQL diff --git a/graphql/web/http.go b/graphql/web/http.go index 522546f0992..31234eaa75e 100644 --- a/graphql/web/http.go +++ b/graphql/web/http.go @@ -28,6 +28,7 @@ import ( "strings" "time" + "github.com/dgraph-io/dgraph/edgraph" "github.com/dgraph-io/dgraph/graphql/api" "github.com/dgraph-io/dgraph/graphql/authorization" "github.com/dgraph-io/dgraph/graphql/resolve" @@ -211,12 +212,16 @@ func (gh *graphqlHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { gqlReq, err := getRequest(ctx, r) if err != nil { - res = schema.ErrorResponse(err) - } else { - gqlReq.Header = r.Header - res = gh.resolver.Resolve(ctx, gqlReq) + write(w, schema.ErrorResponse(err), strings.Contains(r.Header.Get("Accept-Encoding"), "gzip")) + return + } + + if err = edgraph.ProcessPersistedQuery(ctx, gqlReq); err != nil { + write(w, schema.ErrorResponse(err), strings.Contains(r.Header.Get("Accept-Encoding"), "gzip")) + return } + res = gh.resolver.Resolve(ctx, gqlReq) write(w, res, strings.Contains(r.Header.Get("Accept-Encoding"), "gzip")) } @@ -253,6 +258,15 @@ func getRequest(ctx context.Context, r *http.Request) (*schema.Request, error) { query := r.URL.Query() gqlReq.Query = query.Get("query") gqlReq.OperationName = query.Get("operationName") + if extensions, ok := query["extensions"]; ok { + if len(extensions) > 0 { + d := json.NewDecoder(strings.NewReader(extensions[0])) + d.UseNumber() + if err := d.Decode(&gqlReq.Extensions); err != nil { + return nil, errors.Wrap(err, "Not a valid GraphQL request body") + } + } + } variables, ok := query["variables"] if ok { d := json.NewDecoder(strings.NewReader(variables[0])) @@ -292,6 +306,7 @@ func getRequest(ctx context.Context, r *http.Request) (*schema.Request, error) { return nil, errors.New("Unrecognised request method. Please use GET or POST for GraphQL requests") } + gqlReq.Header = r.Header return gqlReq, nil } diff --git a/schema/schema.go b/schema/schema.go index dc513581158..f0f2b65b7c4 100644 --- a/schema/schema.go +++ b/schema/schema.go @@ -577,6 +577,17 @@ func initialTypesInternal(all bool) []*pb.TypeUpdate { ValueType: pb.Posting_DATETIME, }, }, + }, &pb.TypeUpdate{ + TypeName: "dgraph.graphql.persisted_query", + Fields: []*pb.SchemaUpdate{ + { + Predicate: "dgraph.graphql.p_query", + ValueType: pb.Posting_STRING, + }, { + Predicate: "dgraph.graphql.p_sha256hash", + ValueType: pb.Posting_STRING, + }, + }, }) if all || x.WorkerConfig.AclEnabled { @@ -678,6 +689,14 @@ func initialSchemaInternal(all bool) []*pb.SchemaUpdate { }, &pb.SchemaUpdate{ Predicate: "dgraph.graphql.schema_created_at", ValueType: pb.Posting_DATETIME, + }, &pb.SchemaUpdate{ + Predicate: "dgraph.graphql.p_query", + ValueType: pb.Posting_STRING, + }, &pb.SchemaUpdate{ + Predicate: "dgraph.graphql.p_sha256hash", + ValueType: pb.Posting_STRING, + Directive: pb.SchemaUpdate_INDEX, + Tokenizer: []string{"exact"}, }) if all || x.WorkerConfig.AclEnabled { diff --git a/systest/backup/encryption/backup_test.go b/systest/backup/encryption/backup_test.go index 502d92f3f61..920898c4036 100644 --- a/systest/backup/encryption/backup_test.go +++ b/systest/backup/encryption/backup_test.go @@ -295,12 +295,13 @@ func runRestore(t *testing.T, lastDir string, commitTs uint64) map[string]string restoredPreds, err := testutil.GetPredicateNames(pdir) require.NoError(t, err) require.ElementsMatch(t, []string{"dgraph.graphql.schema", "dgraph.cors", "dgraph.graphql.xid", - "dgraph.type", "movie", "dgraph.graphql.schema_history", "dgraph.graphql.schema_created_at"}, + "dgraph.type", "movie", "dgraph.graphql.schema_history", "dgraph.graphql.schema_created_at", + "dgraph.graphql.p_query", "dgraph.graphql.p_sha256hash"}, restoredPreds) restoredTypes, err := testutil.GetTypeNames(pdir) require.NoError(t, err) - require.ElementsMatch(t, []string{"Node", "dgraph.graphql", "dgraph.graphql.history"}, restoredTypes) + require.ElementsMatch(t, []string{"Node", "dgraph.graphql", "dgraph.graphql.history", "dgraph.graphql.persisted_query"}, restoredTypes) require.NoError(t, err) t.Logf("--- Restored values: %+v\n", restored) diff --git a/systest/backup/filesystem/backup_test.go b/systest/backup/filesystem/backup_test.go index 49ec0e28c76..688bda325d4 100644 --- a/systest/backup/filesystem/backup_test.go +++ b/systest/backup/filesystem/backup_test.go @@ -121,8 +121,9 @@ func TestBackupFilesystem(t *testing.T) { // Check the predicates and types in the schema are as expected. // TODO: refactor tests so that minio and filesystem tests share most of their logic. preds := []string{"dgraph.graphql.schema", "dgraph.cors", "name", "dgraph.graphql.xid", - "dgraph.type", "movie", "dgraph.graphql.schema_history", "dgraph.graphql.schema_created_at"} - types := []string{"Node", "dgraph.graphql", "dgraph.graphql.history"} + "dgraph.type", "movie", "dgraph.graphql.schema_history", "dgraph.graphql.schema_created_at", + "dgraph.graphql.p_query", "dgraph.graphql.p_sha256hash"} + types := []string{"Node", "dgraph.graphql", "dgraph.graphql.history", "dgraph.graphql.persisted_query"} testutil.CheckSchema(t, preds, types) verifyUids := func() { diff --git a/systest/backup/minio/backup_test.go b/systest/backup/minio/backup_test.go index 570260b0ed4..95a7df97bbb 100644 --- a/systest/backup/minio/backup_test.go +++ b/systest/backup/minio/backup_test.go @@ -115,8 +115,9 @@ func TestBackupMinio(t *testing.T) { // Check the predicates and types in the schema are as expected. // TODO: refactor tests so that minio and filesystem tests share most of their logic. preds := []string{"dgraph.graphql.schema", "dgraph.cors", "dgraph.graphql.xid", "dgraph.type", "movie", - "dgraph.graphql.schema_history", "dgraph.graphql.schema_created_at"} - types := []string{"Node", "dgraph.graphql", "dgraph.graphql.history"} + "dgraph.graphql.schema_history", "dgraph.graphql.schema_created_at", "dgraph.graphql.p_query", + "dgraph.graphql.p_sha256hash"} + types := []string{"Node", "dgraph.graphql", "dgraph.graphql.history", "dgraph.graphql.persisted_query"} testutil.CheckSchema(t, preds, types) checks := []struct { diff --git a/systest/export/export_test.go b/systest/export/export_test.go index 3c08b153c1d..0a837d54cd5 100644 --- a/systest/export/export_test.go +++ b/systest/export/export_test.go @@ -81,6 +81,8 @@ var expectedSchema = `:string .` + " " + ` :[string] @index(exact) .` + " " + ` :string @index(exact) @upsert .` + " " + ` :string .` + " " + ` +:string .` + " " + ` +:string @index(exact) .` + " " + ` :string .` + " " + ` :datetime .` + " " + ` type { @@ -94,6 +96,10 @@ type { dgraph.graphql.schema_history dgraph.graphql.schema_created_at } +type { + dgraph.graphql.p_query + dgraph.graphql.p_sha256hash +} ` func setupDgraph(t *testing.T) { diff --git a/systest/mutations_test.go b/systest/mutations_test.go index 7e9797c579c..278bc2bc989 100644 --- a/systest/mutations_test.go +++ b/systest/mutations_test.go @@ -636,7 +636,7 @@ func SchemaAfterDeleteNode(t *testing.T, c *dgo.Dgraph) { resp, err := c.NewTxn().Query(ctx, `schema{}`) require.NoError(t, err) - testutil.CompareJSON(t, asJson(`[`+x.CorsPredicate+","+ + testutil.CompareJSON(t, asJson(`[`+x.CorsPredicate+","+x.PersistedQueryPredicate+","+ x.AclPredicates+","+x.GraphqlPredicates+","+ `{"predicate":"friend","type":"uid","list":true},`+ `{"predicate":"married","type":"bool"},`+ @@ -659,7 +659,7 @@ func SchemaAfterDeleteNode(t *testing.T, c *dgo.Dgraph) { resp, err = c.NewTxn().Query(ctx, `schema{}`) require.NoError(t, err) testutil.CompareJSON(t, asJson(`[`+ - x.AclPredicates+","+x.CorsPredicate+","+ + x.AclPredicates+","+x.CorsPredicate+","+x.PersistedQueryPredicate+","+ x.GraphqlPredicates+","+ `{"predicate":"friend","type":"uid","list":true},`+ `{"predicate":"name","type":"default"},`+ diff --git a/systest/queries_test.go b/systest/queries_test.go index 1c452262278..e1436c16447 100644 --- a/systest/queries_test.go +++ b/systest/queries_test.go @@ -336,7 +336,7 @@ func SchemaQueryTest(t *testing.T, c *dgo.Dgraph) { require.NoError(t, err) js := ` { - "schema": [` + x.CorsPredicate + "," + x.AclPredicates + `,` + x.GraphqlPredicates + `, + "schema": [` + x.CorsPredicate + "," + x.PersistedQueryPredicate + "," + x.AclPredicates + `,` + x.GraphqlPredicates + `, { "predicate": "dgraph.type", "type": "string", @@ -392,6 +392,12 @@ func SchemaQueryTestPredicate1(t *testing.T, c *dgo.Dgraph) { { "predicate": "dgraph.cors" }, + { + "predicate": "dgraph.graphql.p_query" + }, + { + "predicate": "dgraph.graphql.p_sha256hash" + }, { "predicate": "dgraph.graphql.schema_history" }, @@ -400,7 +406,7 @@ func SchemaQueryTestPredicate1(t *testing.T, c *dgo.Dgraph) { }, { "predicate": "dgraph.xid" - }, + }, { "predicate": "dgraph.password" }, @@ -560,7 +566,7 @@ func SchemaQueryTestHTTP(t *testing.T, c *dgo.Dgraph) { js := ` { - "schema": [` + x.CorsPredicate + `,` + x.AclPredicates + `,` + x.GraphqlPredicates + `, + "schema": [` + x.CorsPredicate + `,` + x.PersistedQueryPredicate + `,` + x.AclPredicates + `,` + x.GraphqlPredicates + `, { "index": true, "predicate": "dgraph.type", diff --git a/worker/export.go b/worker/export.go index 74e3144fa0b..274ba5fe66f 100644 --- a/worker/export.go +++ b/worker/export.go @@ -663,6 +663,10 @@ func exportInternal(ctx context.Context, in *pb.ExportRequest, db *badger.DB, // Ignore this predicate. case pk.Attr == "dgraph.graphql.schema_history": // Ignore this predicate. + case pk.Attr == "dgraph.graphql.p_query": + // Ignore this predicate. + case pk.Attr == "dgraph.graphql.p_sha256hash": + // Ignore this predicate. case pk.IsData() && pk.Attr == "dgraph.graphql.schema": // Export the graphql schema. pl, err := posting.ReadPostingList(key, itr) diff --git a/x/keys.go b/x/keys.go index a980de04d4a..ce0b066e344 100644 --- a/x/keys.go +++ b/x/keys.go @@ -546,6 +546,8 @@ var graphqlReservedPredicate = map[string]struct{}{ "dgraph.cors": {}, "dgraph.graphql.schema_history": {}, "dgraph.graphql.schema_created_at": {}, + "dgraph.graphql.p_query": {}, + "dgraph.graphql.p_sha256hash": {}, } // internalPredicateMap stores a set of Dgraph's internal predicate. An internal @@ -556,11 +558,12 @@ var internalPredicateMap = map[string]struct{}{ } var preDefinedTypeMap = map[string]struct{}{ - "dgraph.graphql": {}, - "dgraph.type.User": {}, - "dgraph.type.Group": {}, - "dgraph.type.Rule": {}, - "dgraph.graphql.history": {}, + "dgraph.graphql": {}, + "dgraph.type.User": {}, + "dgraph.type.Group": {}, + "dgraph.type.Rule": {}, + "dgraph.graphql.history": {}, + "dgraph.graphql.persisted_query": {}, } // IsGraphqlReservedPredicate returns true if it is the predicate is reserved by graphql. diff --git a/x/x.go b/x/x.go index 24029a2b762..0ab35764674 100644 --- a/x/x.go +++ b/x/x.go @@ -127,9 +127,14 @@ const ( {"predicate":"dgraph.rule.permission","type":"int"} ` // CorsPredicate is the json representation of the predicate reserved by dgraph for the use - //of cors + // of cors CorsPredicate = `{"predicate":"dgraph.cors","type":"string","list":true,"type":"string","index":true,"tokenizer":["exact"],"upsert":true}` + // PersistedQueryPredicate is the json representation of the predicate reserved by dgraph for the use of persisted queries + PersistedQueryPredicate = ` + {"predicate":"dgraph.graphql.p_query","type":"string"}, + {"predicate":"dgraph.graphql.p_sha256hash","type":"string","index":true,"tokenizer":["exact"]}` + InitialTypes = ` "types": [{ "fields": [{"name": "dgraph.graphql.schema"},{"name": "dgraph.graphql.xid"}], @@ -146,6 +151,9 @@ const ( }, { "fields": [{"name": "dgraph.graphql.schema_history"},{"name": "dgraph.graphql.schema_created_at"}], "name": "dgraph.graphql.history" +}, { + "fields": [{"name": "dgraph.graphql.p_query"},{"name": "dgraph.graphql.p_sha256hash"}], + "name": "dgraph.graphql.persisted_query" }]` // GroupIdFileName is the name of the file storing the ID of the group to which