From ddaae8514a750e79172bca1b57d836bf63e578ba Mon Sep 17 00:00:00 2001 From: Ahsan Barkati Date: Thu, 22 Apr 2021 15:21:21 +0530 Subject: [PATCH 1/6] Fix facet export to json --- worker/export.go | 55 ++++++++++++++++++++++++++++-------------------- 1 file changed, 32 insertions(+), 23 deletions(-) diff --git a/worker/export.go b/worker/export.go index 1e1ca5dcaae..e02da6cdd5b 100644 --- a/worker/export.go +++ b/worker/export.go @@ -142,6 +142,31 @@ func (e *exporter) toJSON() (*bpb.KVList, error) { // We could output more compact JSON at the cost of code complexity. // Leaving it simple for now. + writeFacets := func(pfacets []*api.Facet) error { + for _, fct := range pfacets { + fmt.Fprintf(bp, `,"%s|%s":`, e.attr, fct.Key) + + str, err := facetToString(fct) + if err != nil { + glog.Errorf("Ignoring error: %+v", err) + return nil + } + + tid, err := facets.TypeIDFor(fct) + if err != nil { + glog.Errorf("Error getting type id from facet %#v: %v", fct, err) + continue + } + + if !tid.IsNumber() { + str = escapedString(str) + } + + fmt.Fprint(bp, str) + } + return nil + } + continuing := false mapStart := fmt.Sprintf(" {\"uid\":"+uidFmtStrJson+`,"namespace":"0x%x"`, e.uid, e.namespace) err := e.pl.IterateAll(e.readTs, 0, func(p *pb.Posting) error { @@ -154,8 +179,11 @@ func (e *exporter) toJSON() (*bpb.KVList, error) { fmt.Fprint(bp, mapStart) if p.PostingType == pb.Posting_REF { fmt.Fprintf(bp, `,"%s":[`, e.attr) - fmt.Fprintf(bp, "{\"uid\":"+uidFmtStrJson+"}", p.Uid) - fmt.Fprint(bp, "]") + fmt.Fprintf(bp, "{\"uid\":"+uidFmtStrJson, p.Uid) + if err := writeFacets(p.Facets); err != nil { + return errors.Wrap(err, "While writing facets for posting_REF") + } + fmt.Fprint(bp, "}]") } else { if p.PostingType == pb.Posting_VALUE_LANG { fmt.Fprintf(bp, `,"%s@%s":`, e.attr, string(p.LangTag)) @@ -178,28 +206,9 @@ func (e *exporter) toJSON() (*bpb.KVList, error) { } fmt.Fprint(bp, str) - } - - for _, fct := range p.Facets { - fmt.Fprintf(bp, `,"%s|%s":`, e.attr, fct.Key) - - str, err := facetToString(fct) - if err != nil { - glog.Errorf("Ignoring error: %+v", err) - return nil - } - - tid, err := facets.TypeIDFor(fct) - if err != nil { - glog.Errorf("Error getting type id from facet %#v: %v", fct, err) - continue + if err := writeFacets(p.Facets); err != nil { + return errors.Wrap(err, "While writing facets for value postings") } - - if !tid.IsNumber() { - str = escapedString(str) - } - - fmt.Fprint(bp, str) } fmt.Fprint(bp, "}") From 5b3e2ca6354223a1313f62fe4b08085e0efe8070 Mon Sep 17 00:00:00 2001 From: Ahsan Barkati Date: Fri, 23 Apr 2021 11:18:37 +0530 Subject: [PATCH 2/6] Fix test --- worker/export_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/worker/export_test.go b/worker/export_test.go index 96b734ef609..440c259d25a 100644 --- a/worker/export_test.go +++ b/worker/export_test.go @@ -379,9 +379,9 @@ func TestExportJson(t *testing.T) { {"uid":"0x1","namespace":"0x0","friend":[{"uid":"0x5"}]}, {"uid":"0x2","namespace":"0x0","friend":[{"uid":"0x5"}]}, {"uid":"0x3","namespace":"0x0","friend":[{"uid":"0x5"}]}, - {"uid":"0x4","namespace":"0x0","friend":[{"uid":"0x5"}],"friend|age":33, + {"uid":"0x4","namespace":"0x0","friend":[{"uid":"0x5","friend|age":33, "friend|close":"true","friend|game":"football", - "friend|poem":"roses are red\nviolets are blue","friend|since":"2005-05-02T15:04:05Z"}, + "friend|poem":"roses are red\nviolets are blue","friend|since":"2005-05-02T15:04:05Z"}]}, {"uid":"0x9","namespace":"0x2","name":"ns2"} ] ` From 30aa315b50c4af92b0cd4ab54515151b54544653 Mon Sep 17 00:00:00 2001 From: Ahsan Barkati Date: Mon, 26 Apr 2021 15:30:46 +0530 Subject: [PATCH 3/6] Add basic integration test --- systest/export/docker-compose.yml | 12 +- systest/export/export_test.go | 231 +++++++++++++++++++++++++++--- 2 files changed, 217 insertions(+), 26 deletions(-) diff --git a/systest/export/docker-compose.yml b/systest/export/docker-compose.yml index 6c4f1fed361..2d5299c18d0 100644 --- a/systest/export/docker-compose.yml +++ b/systest/export/docker-compose.yml @@ -17,8 +17,8 @@ services: source: $GOPATH/bin target: /gobin read_only: true - - type: volume - source: data + - type: bind + source: ./data target: /data read_only: false command: /gobin/dgraph alpha --my=alpha1:7080 --zero=zero1:5080 --logtostderr @@ -39,8 +39,8 @@ services: source: $GOPATH/bin target: /gobin read_only: true - - type: volume - source: data + - type: bind + source: ./data target: /data read_only: false command: /gobin/dgraph alpha --my=alpha2:7080 --zero=zero1:5080 --logtostderr @@ -61,8 +61,8 @@ services: source: $GOPATH/bin target: /gobin read_only: true - - type: volume - source: data + - type: bind + source: ./data target: /data read_only: false command: /gobin/dgraph alpha --my=alpha3:7080 --zero=zero1:5080 --logtostderr diff --git a/systest/export/export_test.go b/systest/export/export_test.go index a485b696d18..2b6e87f998c 100644 --- a/systest/export/export_test.go +++ b/systest/export/export_test.go @@ -22,6 +22,8 @@ import ( "encoding/json" "io/ioutil" "net/http" + "os" + "path/filepath" "testing" "github.com/dgraph-io/dgo/v210" @@ -36,8 +38,9 @@ import ( var ( mc *minio.Client bucketName = "dgraph-backup" - destination = "minio://minio:9001/dgraph-backup?secure=false" + minioDest = "minio://minio:9001/dgraph-backup?secure=false" localBackupDst = "minio://localhost:9001/dgraph-backup?secure=false" + copyExportDir = "./data/export-copy" ) // TestExportSchemaToMinio. This test does an export, then verifies that the @@ -48,11 +51,12 @@ func TestExportSchemaToMinio(t *testing.T) { require.NoError(t, err) mc.MakeBucket(bucketName, "") - setupDgraph(t) - result := requestExport(t) + setupDgraph(t, moviesData, movieSchema) + result := requestExport(t, minioDest, "rdf") require.Equal(t, "Success", getFromJSON(result, "data", "export", "response", "code").(string)) - require.Equal(t, "Export completed.", getFromJSON(result, "data", "export", "response", "message").(string)) + require.Equal(t, "Export completed.", + getFromJSON(result, "data", "export", "response", "message").(string)) var files []string for _, f := range getFromJSON(result, "data", "export", "exportedFiles").([]interface{}) { @@ -93,8 +97,203 @@ var expectedSchema = `[0x0] :string .` + " " + ` dgraph.graphql.p_query } ` +var moviesData = `<_:x1> "BIRDS MAN OR (THE UNEXPECTED VIRTUE OF IGNORANCE)" . + <_:x2> "Spotlight" . + <_:x3> "Moonlight" . + <_:x4> "THE SHAPE OF WATERLOO" . + <_:x5> "BLACK PUNTER" .` -func setupDgraph(t *testing.T) { +var movieSchema = ` + movie: string . + type Node { + movie + }` + +func TestExportAndLoadJson(t *testing.T) { + setupDgraph(t, moviesData, movieSchema) + + // Run export + result := requestExport(t, "/data/export-data", "json") + require.Equal(t, "Success", getFromJSON(result, "data", "export", "response", "code").(string)) + require.Equal(t, "Export completed.", + getFromJSON(result, "data", "export", "response", "message").(string)) + + var files []string + for _, f := range getFromJSON(result, "data", "export", "exportedFiles").([]interface{}) { + files = append(files, f.(string)) + } + require.Equal(t, 3, len(files)) + copyToLocalFs(t) + + q := `{ q(func:has(movie)) { count(uid) } }` + + res := runQuery(t, q) + require.JSONEq(t, `{"data":{"q":[{"count": 5}]}}`, res) + + // Drop all data + dg, err := testutil.DgraphClient(testutil.SockAddr) + require.NoError(t, err) + err = dg.Alter(context.Background(), &api.Operation{DropAll: true}) + require.NoError(t, err) + + res = runQuery(t, q) + require.JSONEq(t, `{"data": {"q": [{"count":0}]}}`, res) + + // Live load the exported data + base := filepath.Dir(files[0]) + dir := filepath.Join(copyExportDir, base) + loadData(t, dir, "json") + + res = runQuery(t, q) + require.JSONEq(t, `{"data":{"q":[{"count": 5}]}}`, res) + + dirCleanup(t) +} + +var facetsData = ` + _:blank-0 "Carol" . + _:blank-0 _:blank-1 (close="yes") . + _:blank-1 "Daryl" . + + _:a "test" (f="test") . + _:a "London" (cont="England") . + _:a "Paris" (cont="France") . + _:a "alice" . + + _:b _:a (f="something") . + _:b "bob" . + ` + +var facetsSchema = ` + : string @index(exact) . + : [uid] . + : uid . + : [string] . +` + +func TestExportAndLoadJsonFacets(t *testing.T) { + setupDgraph(t, facetsData, facetsSchema) + + // Run export + result := requestExport(t, "/data/export-data", "json") + require.Equal(t, "Success", getFromJSON(result, "data", "export", "response", "code").(string)) + require.Equal(t, "Export completed.", + getFromJSON(result, "data", "export", "response", "message").(string)) + + var files []string + for _, f := range getFromJSON(result, "data", "export", "exportedFiles").([]interface{}) { + files = append(files, f.(string)) + } + require.Equal(t, 3, len(files)) + copyToLocalFs(t) + + q := `{ + q(func:has(name)) { + pred @facets + predlist @facets + refone @facets + friend @facets { + name + } + } + }` + + res := runQuery(t, q) + expected := `{ + "data": { + "q": [ + { + "friend": [ + { + "name": "Daryl", + "friend|close": "yes" + } + ] + }, + { + "pred|f": "test", + "pred": "test", + "predlist|cont": { + "0": "England", + "1": "France" + }, + "predlist": [ + "London", + "Paris" + ] + } + ] + } +}` + + require.JSONEq(t, expected, res) + + // Drop all data + dg, err := testutil.DgraphClient(testutil.SockAddr) + require.NoError(t, err) + err = dg.Alter(context.Background(), &api.Operation{DropAll: true}) + require.NoError(t, err) + + res = runQuery(t, q) + require.JSONEq(t, `{"data": {"q": []}}`, res) + + // Live load the exported data and verify that exported data is loaded correctly. + base := filepath.Dir(files[0]) + dir := filepath.Join(copyExportDir, base) + loadData(t, dir, "json") + + res = runQuery(t, q) + require.JSONEq(t, expected, res) + + dirCleanup(t) +} + +func runQuery(t *testing.T, q string) string { + dg, err := testutil.DgraphClient(testutil.SockAddr) + require.NoError(t, err) + + resp, err := testutil.RetryQuery(dg, q) + require.NoError(t, err) + response := map[string]interface{}{} + response["data"] = json.RawMessage(string(resp.Json)) + + jsonResponse, err := json.Marshal(response) + require.NoError(t, err) + return string(jsonResponse) +} + +func copyToLocalFs(t *testing.T) { + require.NoError(t, os.RemoveAll(copyExportDir)) + srcPath := testutil.DockerPrefix + "_alpha1_1:/data/export-data" + t.Log(srcPath) + require.NoError(t, testutil.DockerCp(srcPath, copyExportDir)) +} + +func loadData(t *testing.T, dir, format string) { + schemaFile := dir + "/g01.schema.gz" + dataFile := dir + "/g01." + format + ".gz" + + pipeline := [][]string{ + {testutil.DgraphBinaryPath(), "live", + "-s", schemaFile, "-f", dataFile, "--alpha", + testutil.SockAddr, "--zero", testutil.SockAddrZero, + }, + } + _, err := testutil.Pipeline(pipeline) + require.NoErrorf(t, err, "Got error while loading data: %v", err) + +} + +func dirCleanup(t *testing.T) { + require.NoError(t, os.RemoveAll("./t")) + + cmd := []string{"bash", "-c", "rm -rf /data/*"} + require.NoError(t, testutil.DockerExec("alpha1", cmd...)) +} + +func setupDgraph(t *testing.T, nquads, schema string) { + + require.NoError(t, os.MkdirAll("./data", os.ModePerm)) conn, err := grpc.Dial(testutil.SockAddr, grpc.WithInsecure()) require.NoError(t, err) dg := dgo.NewDgraphClient(api.NewDgraphClient(conn)) @@ -104,28 +303,19 @@ func setupDgraph(t *testing.T) { // Add schema and types. // this is because Alters are always blocked until the indexing is finished. - require.NoError(t, testutil.RetryAlter(dg, &api.Operation{Schema: `movie: string . - type Node { - movie - }`})) + require.NoError(t, testutil.RetryAlter(dg, &api.Operation{Schema: schema})) // Add initial data. _, err = dg.NewTxn().Mutate(ctx, &api.Mutation{ CommitNow: true, - SetNquads: []byte(` - <_:x1> "BIRDS MAN OR (THE UNEXPECTED VIRTUE OF IGNORANCE)" . - <_:x2> "Spotlight" . - <_:x3> "Moonlight" . - <_:x4> "THE SHAPE OF WATERLOO" . - <_:x5> "BLACK PUNTER" . - `), + SetNquads: []byte(nquads), }) require.NoError(t, err) } -func requestExport(t *testing.T) map[string]interface{} { - exportRequest := `mutation export($dst: String!) { - export(input: {destination: $dst}) { +func requestExport(t *testing.T, dest string, format string) map[string]interface{} { + exportRequest := `mutation export($dst: String!, $f: String!) { + export(input: {destination: $dst, format: $f}) { response { code message @@ -138,7 +328,8 @@ func requestExport(t *testing.T) map[string]interface{} { params := testutil.GraphQLParams{ Query: exportRequest, Variables: map[string]interface{}{ - "dst": destination, + "dst": dest, + "f": format, }, } b, err := json.Marshal(params) From 14f7469683838f88dda6422aff9d9c46f8ebdf65 Mon Sep 17 00:00:00 2001 From: Ahsan Barkati Date: Tue, 27 Apr 2021 13:24:09 +0530 Subject: [PATCH 4/6] Add integration test for export --- systest/export/docker-compose.yml | 15 +--- systest/export/export_test.go | 114 +++++++++++++----------------- 2 files changed, 53 insertions(+), 76 deletions(-) diff --git a/systest/export/docker-compose.yml b/systest/export/docker-compose.yml index 2d5299c18d0..98c6fd1cf0c 100644 --- a/systest/export/docker-compose.yml +++ b/systest/export/docker-compose.yml @@ -17,12 +17,9 @@ services: source: $GOPATH/bin target: /gobin read_only: true - - type: bind - source: ./data - target: /data read_only: false command: /gobin/dgraph alpha --my=alpha1:7080 --zero=zero1:5080 --logtostderr - -v=2 + -v=2 --security "whitelist=10.0.0.0/8,172.16.0.0/12,192.168.0.0/16;" alpha2: image: dgraph/dgraph:latest @@ -39,12 +36,9 @@ services: source: $GOPATH/bin target: /gobin read_only: true - - type: bind - source: ./data - target: /data read_only: false command: /gobin/dgraph alpha --my=alpha2:7080 --zero=zero1:5080 --logtostderr - -v=2 + -v=2 --security "whitelist=10.0.0.0/8,172.16.0.0/12,192.168.0.0/16;" alpha3: image: dgraph/dgraph:latest @@ -61,12 +55,9 @@ services: source: $GOPATH/bin target: /gobin read_only: true - - type: bind - source: ./data - target: /data read_only: false command: /gobin/dgraph alpha --my=alpha3:7080 --zero=zero1:5080 --logtostderr - -v=2 + -v=2 --security "whitelist=10.0.0.0/8,172.16.0.0/12,192.168.0.0/16;" minio: image: minio/minio:RELEASE.2020-11-13T20-10-18Z diff --git a/systest/export/export_test.go b/systest/export/export_test.go index 2b6e87f998c..be2349bd3d5 100644 --- a/systest/export/export_test.go +++ b/systest/export/export_test.go @@ -81,27 +81,27 @@ func TestExportSchemaToMinio(t *testing.T) { } var expectedSchema = `[0x0] :string .` + " " + ` -[0x0] :[string] @index(exact) .` + " " + ` -[0x0] :string .` + " " + ` -[0x0] :string @index(exact) @upsert .` + " " + ` -[0x0] :string .` + " " + ` -[0x0] :string @index(sha256) .` + " " + ` -[0x0] type { - movie -} -[0x0] type { - dgraph.graphql.schema - dgraph.graphql.xid -} -[0x0] type { - dgraph.graphql.p_query -} -` + [0x0] :[string] @index(exact) .` + " " + ` + [0x0] :string .` + " " + ` + [0x0] :string @index(exact) @upsert .` + " " + ` + [0x0] :string .` + " " + ` + [0x0] :string @index(sha256) .` + " " + ` + [0x0] type { + movie + } + [0x0] type { + dgraph.graphql.schema + dgraph.graphql.xid + } + [0x0] type { + dgraph.graphql.p_query + } + ` var moviesData = `<_:x1> "BIRDS MAN OR (THE UNEXPECTED VIRTUE OF IGNORANCE)" . - <_:x2> "Spotlight" . - <_:x3> "Moonlight" . - <_:x4> "THE SHAPE OF WATERLOO" . - <_:x5> "BLACK PUNTER" .` + <_:x2> "Spotlight" . + <_:x3> "Moonlight" . + <_:x4> "THE SHAPE OF WATERLOO" . + <_:x5> "BLACK PUNTER" .` var movieSchema = ` movie: string . @@ -187,46 +187,33 @@ func TestExportAndLoadJsonFacets(t *testing.T) { require.Equal(t, 3, len(files)) copyToLocalFs(t) - q := `{ - q(func:has(name)) { - pred @facets - predlist @facets - refone @facets - friend @facets { - name - } - } - }` - - res := runQuery(t, q) - expected := `{ - "data": { - "q": [ - { - "friend": [ - { - "name": "Daryl", - "friend|close": "yes" - } - ] - }, - { - "pred|f": "test", - "pred": "test", - "predlist|cont": { - "0": "England", - "1": "France" - }, - "predlist": [ - "London", - "Paris" - ] - } - ] + checkRes := func() { + // Check value posting. + q := `{ q(func:has(name)) { pred @facets } }` + res := runQuery(t, q) + require.JSONEq(t, `{"data": {"q": [{"pred": "test", "pred|f": "test"}]}}`, res) + + // Check value postings of list type. + q = `{ q(func:has(name)) { predlist @facets } }` + res = runQuery(t, q) + require.JSONEq(t, `{"data": {"q": [{ + "predlist|cont": {"0": "England","1": "France"}, + "predlist": ["London","Paris" ]}]}}`, res) + + // Check reference posting. + q = `{ q(func:has(name)) { refone @facets {name} } }` + res = runQuery(t, q) + require.JSONEq(t, + `{"data":{"q":[{"refone":{"name":"alice","refone|f":"something"}}]}}`, res) + + // Check reference postings of list type. + q = `{ q(func:has(name)) { friend @facets {name} } }` + res = runQuery(t, q) + require.JSONEq(t, + `{"data":{"q":[{"friend":[{"name":"Daryl","friend|close":"yes"}]}]}}`, res) } -}` - require.JSONEq(t, expected, res) + checkRes() // Drop all data dg, err := testutil.DgraphClient(testutil.SockAddr) @@ -234,7 +221,7 @@ func TestExportAndLoadJsonFacets(t *testing.T) { err = dg.Alter(context.Background(), &api.Operation{DropAll: true}) require.NoError(t, err) - res = runQuery(t, q) + res := runQuery(t, `{ q(func:has(name)) { name } }`) require.JSONEq(t, `{"data": {"q": []}}`, res) // Live load the exported data and verify that exported data is loaded correctly. @@ -242,9 +229,8 @@ func TestExportAndLoadJsonFacets(t *testing.T) { dir := filepath.Join(copyExportDir, base) loadData(t, dir, "json") - res = runQuery(t, q) - require.JSONEq(t, expected, res) - + // verify that the state after loading the exported data as same. + checkRes() dirCleanup(t) } @@ -265,7 +251,6 @@ func runQuery(t *testing.T, q string) string { func copyToLocalFs(t *testing.T) { require.NoError(t, os.RemoveAll(copyExportDir)) srcPath := testutil.DockerPrefix + "_alpha1_1:/data/export-data" - t.Log(srcPath) require.NoError(t, testutil.DockerCp(srcPath, copyExportDir)) } @@ -286,8 +271,9 @@ func loadData(t *testing.T, dir, format string) { func dirCleanup(t *testing.T) { require.NoError(t, os.RemoveAll("./t")) + require.NoError(t, os.RemoveAll("./data")) - cmd := []string{"bash", "-c", "rm -rf /data/*"} + cmd := []string{"bash", "-c", "rm -rf /data/export-data/*"} require.NoError(t, testutil.DockerExec("alpha1", cmd...)) } From 5a4923612ccc417ac6d6a6f742a179a8ffb1c6d3 Mon Sep 17 00:00:00 2001 From: Ahsan Barkati Date: Tue, 27 Apr 2021 13:27:15 +0530 Subject: [PATCH 5/6] Revert compose changes --- systest/export/docker-compose.yml | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/systest/export/docker-compose.yml b/systest/export/docker-compose.yml index 98c6fd1cf0c..6c4f1fed361 100644 --- a/systest/export/docker-compose.yml +++ b/systest/export/docker-compose.yml @@ -17,9 +17,12 @@ services: source: $GOPATH/bin target: /gobin read_only: true + - type: volume + source: data + target: /data read_only: false command: /gobin/dgraph alpha --my=alpha1:7080 --zero=zero1:5080 --logtostderr - -v=2 + -v=2 --security "whitelist=10.0.0.0/8,172.16.0.0/12,192.168.0.0/16;" alpha2: image: dgraph/dgraph:latest @@ -36,9 +39,12 @@ services: source: $GOPATH/bin target: /gobin read_only: true + - type: volume + source: data + target: /data read_only: false command: /gobin/dgraph alpha --my=alpha2:7080 --zero=zero1:5080 --logtostderr - -v=2 + -v=2 --security "whitelist=10.0.0.0/8,172.16.0.0/12,192.168.0.0/16;" alpha3: image: dgraph/dgraph:latest @@ -55,9 +61,12 @@ services: source: $GOPATH/bin target: /gobin read_only: true + - type: volume + source: data + target: /data read_only: false command: /gobin/dgraph alpha --my=alpha3:7080 --zero=zero1:5080 --logtostderr - -v=2 + -v=2 --security "whitelist=10.0.0.0/8,172.16.0.0/12,192.168.0.0/16;" minio: image: minio/minio:RELEASE.2020-11-13T20-10-18Z From 6adaefa65802f7a47ae8fa8c94c42a34d9093cfb Mon Sep 17 00:00:00 2001 From: Ahsan Barkati Date: Tue, 27 Apr 2021 13:37:31 +0530 Subject: [PATCH 6/6] Minor fix --- systest/export/export_test.go | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/systest/export/export_test.go b/systest/export/export_test.go index be2349bd3d5..15f9d7bc6fd 100644 --- a/systest/export/export_test.go +++ b/systest/export/export_test.go @@ -81,22 +81,22 @@ func TestExportSchemaToMinio(t *testing.T) { } var expectedSchema = `[0x0] :string .` + " " + ` - [0x0] :[string] @index(exact) .` + " " + ` - [0x0] :string .` + " " + ` - [0x0] :string @index(exact) @upsert .` + " " + ` - [0x0] :string .` + " " + ` - [0x0] :string @index(sha256) .` + " " + ` - [0x0] type { - movie - } - [0x0] type { - dgraph.graphql.schema - dgraph.graphql.xid - } - [0x0] type { - dgraph.graphql.p_query - } - ` +[0x0] :[string] @index(exact) .` + " " + ` +[0x0] :string .` + " " + ` +[0x0] :string @index(exact) @upsert .` + " " + ` +[0x0] :string .` + " " + ` +[0x0] :string @index(sha256) .` + " " + ` +[0x0] type { + movie +} +[0x0] type { + dgraph.graphql.schema + dgraph.graphql.xid +} +[0x0] type { + dgraph.graphql.p_query +} +` var moviesData = `<_:x1> "BIRDS MAN OR (THE UNEXPECTED VIRTUE OF IGNORANCE)" . <_:x2> "Spotlight" . <_:x3> "Moonlight" .