Skip to content

Commit 041ba15

Browse files
authored
Feat(graphql): Add vector support to graphql (#9074)
Adds support for vector predicate in GraphQL. Introduced new queries like similar_to() in graphql
1 parent 3a8de31 commit 041ba15

File tree

95 files changed

+2227
-167
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

95 files changed

+2227
-167
lines changed

dgraphtest/local_cluster.go

+19-16
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222
"fmt"
2323
"io"
2424
"log"
25+
"math/rand"
2526
"net/http"
2627
"os"
2728
"os/exec"
@@ -346,7 +347,8 @@ func (c *LocalCluster) Start() error {
346347
if err1 := c.Stop(); err1 != nil {
347348
log.Printf("[WARNING] error while stopping :%v", err)
348349
}
349-
c.Cleanup(false)
350+
c.Cleanup(true)
351+
c.conf.prefix = fmt.Sprintf("dgraphtest-%d", rand.NewSource(time.Now().UnixNano()).Int63()%1000000)
350352
if err := c.init(); err != nil {
351353
c.Cleanup(true)
352354
return err
@@ -449,11 +451,7 @@ func (c *LocalCluster) HealthCheck(zeroOnly bool) error {
449451
if !zo.isRunning {
450452
break
451453
}
452-
url, err := zo.healthURL(c)
453-
if err != nil {
454-
return errors.Wrap(err, "error getting health URL")
455-
}
456-
if err := c.containerHealthCheck(url); err != nil {
454+
if err := c.containerHealthCheck(zo.healthURL); err != nil {
457455
return err
458456
}
459457
log.Printf("[INFO] container [%v] passed health check", zo.containerName)
@@ -470,11 +468,7 @@ func (c *LocalCluster) HealthCheck(zeroOnly bool) error {
470468
if !aa.isRunning {
471469
break
472470
}
473-
url, err := aa.healthURL(c)
474-
if err != nil {
475-
return errors.Wrap(err, "error getting health URL")
476-
}
477-
if err := c.containerHealthCheck(url); err != nil {
471+
if err := c.containerHealthCheck(aa.healthURL); err != nil {
478472
return err
479473
}
480474
log.Printf("[INFO] container [%v] passed health check", aa.containerName)
@@ -486,18 +480,27 @@ func (c *LocalCluster) HealthCheck(zeroOnly bool) error {
486480
return nil
487481
}
488482

489-
func (c *LocalCluster) containerHealthCheck(url string) error {
483+
func (c *LocalCluster) containerHealthCheck(url func(c *LocalCluster) (string, error)) error {
484+
endpoint, err := url(c)
485+
if err != nil {
486+
return errors.Wrap(err, "error getting health URL")
487+
}
490488
for i := 0; i < 60; i++ {
491489
time.Sleep(waitDurBeforeRetry)
492490

493-
req, err := http.NewRequest(http.MethodGet, url, nil)
491+
endpoint, err = url(c)
492+
if err != nil {
493+
return errors.Wrap(err, "error getting health URL")
494+
}
495+
496+
req, err := http.NewRequest(http.MethodGet, endpoint, nil)
494497
if err != nil {
495-
log.Printf("[WARNING] error building req for endpoint [%v], err: [%v]", url, err)
498+
log.Printf("[WARNING] error building req for endpoint [%v], err: [%v]", endpoint, err)
496499
continue
497500
}
498501
body, err := doReq(req)
499502
if err != nil {
500-
log.Printf("[WARNING] error hitting health endpoint [%v], err: [%v]", url, err)
503+
log.Printf("[WARNING] error hitting health endpoint [%v], err: [%v]", endpoint, err)
501504
continue
502505
}
503506
resp := string(body)
@@ -523,7 +526,7 @@ func (c *LocalCluster) containerHealthCheck(url string) error {
523526
return nil
524527
}
525528

526-
return fmt.Errorf("health failed, cluster took too long to come up [%v]", url)
529+
return fmt.Errorf("health failed, cluster took too long to come up [%v]", endpoint)
527530
}
528531

529532
func (c *LocalCluster) waitUntilLogin() error {

graphql/bench/schema.graphql

+1-1
Original file line numberDiff line numberDiff line change
@@ -69,4 +69,4 @@ type Owner {
6969
hasRestaurants: [Restaurant] @hasInverse(field: owner)
7070
}
7171

72-
# Dgraph.Authorization {"VerificationKey":"secretkey","Header":"X-Test-Auth","Namespace":"https://xyz.io/jwt/claims","Algo":"HS256","Audience":["aud1","63do0q16n6ebjgkumu05kkeian","aud5"]}
72+
# Dgraph.Authorization {"VerificationKey":"secretkey","Header":"X-Test-Auth","Namespace":"https://xyz.io/jwt/claims","Algo":"HS256","Audience":["aud1","63do0q16n6ebjgkumu05kkeian","aud5"]}

graphql/bench/schema_auth.graphql

+1-1
Original file line numberDiff line numberDiff line change
@@ -248,4 +248,4 @@ type Owner @auth(
248248
hasRestaurants: [Restaurant] @hasInverse(field: owner)
249249
}
250250

251-
# Dgraph.Authorization {"VerificationKey":"secretkey","Header":"X-Test-Auth","Namespace":"https://xyz.io/jwt/claims","Algo":"HS256","Audience":["aud1","63do0q16n6ebjgkumu05kkeian","aud5"]}
251+
# Dgraph.Authorization {"VerificationKey":"secretkey","Header":"X-Test-Auth","Namespace":"https://xyz.io/jwt/claims","Algo":"HS256","Audience":["aud1","63do0q16n6ebjgkumu05kkeian","aud5"]}

graphql/dgraph/graphquery.go

+31-2
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,16 @@ import (
2828
// validate query, and so doesn't return an error if query is 'malformed' - it might
2929
// just write something that wouldn't parse as a Dgraph query.
3030
func AsString(queries []*dql.GraphQuery) string {
31-
if queries == nil {
31+
if len(queries) == 0 {
3232
return ""
3333
}
3434

3535
var b strings.Builder
36-
x.Check2(b.WriteString("query {\n"))
36+
queryName := queries[len(queries)-1].Attr
37+
x.Check2(b.WriteString("query "))
38+
addQueryVars(&b, queryName, queries[0].Args)
39+
x.Check2(b.WriteString("{\n"))
40+
3741
numRewrittenQueries := 0
3842
for _, q := range queries {
3943
if q == nil {
@@ -54,6 +58,24 @@ func AsString(queries []*dql.GraphQuery) string {
5458
return b.String()
5559
}
5660

61+
func addQueryVars(b *strings.Builder, queryName string, args map[string]string) {
62+
dollarFound := false
63+
for name, val := range args {
64+
if strings.HasPrefix(name, "$") {
65+
if !dollarFound {
66+
x.Check2(b.WriteString(queryName + "("))
67+
x.Check2(b.WriteString(name + ": " + val))
68+
dollarFound = true
69+
} else {
70+
x.Check2(b.WriteString(", " + name + ": " + val))
71+
}
72+
}
73+
}
74+
if dollarFound {
75+
x.Check2(b.WriteString(") "))
76+
}
77+
}
78+
5779
func writeQuery(b *strings.Builder, query *dql.GraphQuery, prefix string) {
5880
if query.Var != "" || query.Alias != "" || query.Attr != "" {
5981
x.Check2(b.WriteString(prefix))
@@ -145,6 +167,9 @@ func writeRoot(b *strings.Builder, q *dql.GraphQuery) {
145167
}
146168

147169
switch {
170+
// TODO: Instead of the hard-coded strings "uid", "type", etc., use the
171+
// pre-defined constants in dql/parser.go such as dql.uidFunc, dql.typFunc,
172+
// etc. This of course will require that we make these constants public.
148173
case q.Func.Name == "uid":
149174
x.Check2(b.WriteString("(func: "))
150175
writeUIDFunc(b, q.Func.UID, q.Func.Args)
@@ -154,6 +179,10 @@ func writeRoot(b *strings.Builder, q *dql.GraphQuery) {
154179
x.Check2(b.WriteString("(func: eq("))
155180
writeFilterArguments(b, q.Func.Args)
156181
x.Check2(b.WriteRune(')'))
182+
case q.Func.Name == "similar_to":
183+
x.Check2(b.WriteString("(func: similar_to("))
184+
writeFilterArguments(b, q.Func.Args)
185+
x.Check2(b.WriteRune(')'))
157186
}
158187
writeOrderAndPage(b, q, true)
159188
x.Check2(b.WriteRune(')'))

graphql/e2e/auth/schema.graphql

+4-4
Original file line numberDiff line numberDiff line change
@@ -568,7 +568,7 @@ type Contact @auth(
568568
query: { rule: "{$ContactRole: { eq: \"ADMINISTRATOR\"}}" }
569569
) {
570570
id: ID!
571-
nickName: String @search(by: [exact, term, fulltext, regexp])
571+
nickName: String @search(by: ["exact", "term", "fulltext", "regexp"])
572572
adminTasks: [AdminTask] @hasInverse(field: forContact)
573573
tasks: [Task] @hasInverse(field: forContact)
574574
}
@@ -577,14 +577,14 @@ type AdminTask @auth(
577577
query: { rule: "{$TaskRole: { eq: \"ADMINISTRATOR\"}}" }
578578
) {
579579
id: ID!
580-
name: String @search(by: [exact, term, fulltext, regexp])
580+
name: String @search(by: ["exact", "term", "fulltext", "regexp"])
581581
occurrences: [TaskOccurrence] @hasInverse(field: adminTask)
582582
forContact: Contact @hasInverse(field: adminTasks)
583583
}
584584

585585
type Task {
586586
id: ID!
587-
name: String @search(by: [exact, term, fulltext, regexp])
587+
name: String @search(by: ["exact", "term", "fulltext", "regexp"])
588588
occurrences: [TaskOccurrence] @hasInverse(field: task)
589589
forContact: Contact @hasInverse(field: tasks)
590590
}
@@ -608,7 +608,7 @@ type TaskOccurrence @auth(
608608
task: Task @hasInverse(field: occurrences)
609609
adminTask: AdminTask @hasInverse(field: occurrences)
610610
isPublic: Boolean @search
611-
role: String @search(by: [exact, term, fulltext, regexp])
611+
role: String @search(by: ["exact", "term", "fulltext", "regexp"])
612612
}
613613

614614
type Author {

graphql/e2e/common/query.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -1285,7 +1285,7 @@ func stringExactFilters(t *testing.T) {
12851285

12861286
func scalarListFilters(t *testing.T) {
12871287

1288-
// tags is a list of strings with @search(by: exact). So all the filters
1288+
// tags is a list of strings with @search(by: "exact"). So all the filters
12891289
// lt, le, ... mean "is there something in the list that's lt 'Dgraph'", etc.
12901290

12911291
cases := map[string]struct {

graphql/e2e/custom_logic/custom_logic_test.go

+3-3
Original file line numberDiff line numberDiff line change
@@ -1067,7 +1067,7 @@ func TestCustomFieldsShouldPassBody(t *testing.T) {
10671067

10681068
schema := `
10691069
type User {
1070-
id: String! @id @search(by: [hash, regexp])
1070+
id: String! @id @search(by: ["hash", "regexp"])
10711071
address:String
10721072
name: String
10731073
@custom(
@@ -2573,7 +2573,7 @@ func TestCustomDQL(t *testing.T) {
25732573
}
25742574
type Tweets implements Node {
25752575
id: ID!
2576-
text: String! @search(by: [fulltext, exact])
2576+
text: String! @search(by: ["fulltext", "exact"])
25772577
user: User
25782578
timestamp: DateTime! @search
25792579
}
@@ -2864,7 +2864,7 @@ func TestCustomFieldsWithRestError(t *testing.T) {
28642864
}
28652865
28662866
type User {
2867-
id: String! @id @search(by: [hash, regexp])
2867+
id: String! @id @search(by: ["hash", "regexp"])
28682868
name: String
28692869
@custom(
28702870
http: {

graphql/e2e/directives/schema.graphql

+1-1
Original file line numberDiff line numberDiff line change
@@ -423,4 +423,4 @@ type CricketTeam implements Team {
423423
type LibraryManager {
424424
name: String! @id
425425
manages: [LibraryMember]
426-
}
426+
}

graphql/e2e/normal/schema.graphql

+1-1
Original file line numberDiff line numberDiff line change
@@ -422,4 +422,4 @@ type CricketTeam implements Team {
422422
type LibraryManager {
423423
name: String! @id
424424
manages: [LibraryMember]
425-
}
425+
}

graphql/e2e/schema/apollo_service_response.graphql

+3-1
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ enum DgraphIndex {
8383
day
8484
hour
8585
geo
86+
hnsw
8687
}
8788

8889
input AuthRule {
@@ -196,7 +197,8 @@ input GenerateMutationParams {
196197
}
197198

198199
directive @hasInverse(field: String!) on FIELD_DEFINITION
199-
directive @search(by: [DgraphIndex!]) on FIELD_DEFINITION
200+
directive @search(by: [String!]) on FIELD_DEFINITION
201+
directive @embedding on FIELD_DEFINITION
200202
directive @dgraph(type: String, pred: String) on OBJECT | INTERFACE | FIELD_DEFINITION
201203
directive @id(interface: Boolean) on FIELD_DEFINITION
202204
directive @withSubscription on OBJECT | INTERFACE | FIELD_DEFINITION

graphql/e2e/schema/generatedSchema.graphql

+3-1
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ enum DgraphIndex {
6464
day
6565
hour
6666
geo
67+
hnsw
6768
}
6869

6970
input AuthRule {
@@ -177,7 +178,8 @@ input GenerateMutationParams {
177178
}
178179

179180
directive @hasInverse(field: String!) on FIELD_DEFINITION
180-
directive @search(by: [DgraphIndex!]) on FIELD_DEFINITION
181+
directive @search(by: [String!]) on FIELD_DEFINITION
182+
directive @embedding on FIELD_DEFINITION
181183
directive @dgraph(type: String, pred: String) on OBJECT | INTERFACE | FIELD_DEFINITION
182184
directive @id(interface: Boolean) on FIELD_DEFINITION
183185
directive @withSubscription on OBJECT | INTERFACE | FIELD_DEFINITION

graphql/e2e/schema/schema_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -564,7 +564,7 @@ func TestLargeSchemaUpdate(t *testing.T) {
564564

565565
schema := "type LargeSchema {"
566566
for i := 1; i <= numFields; i++ {
567-
schema = schema + "\n" + fmt.Sprintf("field%d: String! @search(by: [regexp])", i)
567+
schema = schema + "\n" + fmt.Sprintf("field%d: String! @search(by: [\"regexp\"])", i)
568568
}
569569
schema = schema + "\n}"
570570

graphql/e2e/subscription/subscription_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ const (
4646
}
4747
4848
type Customer {
49-
username: String! @id @search(by: [hash, regexp])
49+
username: String! @id @search(by: ["hash", "regexp"])
5050
reviews: [Review] @hasInverse(field: by)
5151
}
5252

graphql/resolve/mutation_rewriter.go

+6
Original file line numberDiff line numberDiff line change
@@ -1675,6 +1675,12 @@ func rewriteObject(
16751675
fieldName = fieldName[1 : len(fieldName)-1]
16761676
}
16771677

1678+
if fieldDef.HasEmbeddingDirective() {
1679+
// embedding is a JSON array of numbers. Rewrite it as a string, for now
1680+
var valBytes []byte
1681+
valBytes, _ = json.Marshal(val)
1682+
val = string(valBytes)
1683+
}
16781684
// TODO: Write a function for aggregating data of fragment from child nodes.
16791685
switch val := val.(type) {
16801686
case map[string]interface{}:

0 commit comments

Comments
 (0)