From 041ba157990964db065efd3fecd3a1ba1beb4349 Mon Sep 17 00:00:00 2001 From: Harshil Goel <54325286+harshil-goel@users.noreply.github.com> Date: Thu, 18 Apr 2024 16:36:47 +0530 Subject: [PATCH] Feat(graphql): Add vector support to graphql (#9074) Adds support for vector predicate in GraphQL. Introduced new queries like similar_to() in graphql --- dgraphtest/local_cluster.go | 35 +- graphql/bench/schema.graphql | 2 +- graphql/bench/schema_auth.graphql | 2 +- graphql/dgraph/graphquery.go | 33 +- graphql/e2e/auth/schema.graphql | 8 +- graphql/e2e/common/query.go | 2 +- graphql/e2e/custom_logic/custom_logic_test.go | 6 +- graphql/e2e/directives/schema.graphql | 2 +- graphql/e2e/normal/schema.graphql | 2 +- .../schema/apollo_service_response.graphql | 4 +- graphql/e2e/schema/generatedSchema.graphql | 4 +- graphql/e2e/schema/schema_test.go | 2 +- graphql/e2e/subscription/subscription_test.go | 2 +- graphql/resolve/mutation_rewriter.go | 6 + graphql/resolve/query_rewriter.go | 295 ++++++++- graphql/resolve/query_test.yaml | 165 +++++ graphql/resolve/resolver.go | 2 + graphql/resolve/resolver_error_test.go | 8 + graphql/resolve/schema.graphql | 26 +- graphql/schema/dgraph_schemagen_test.yml | 32 +- graphql/schema/gqlschema.go | 213 ++++++- graphql/schema/gqlschema_test.yml | 60 +- graphql/schema/rules.go | 160 ++++- graphql/schema/schemagen.go | 13 +- .../input/generate-directive.graphql | 2 +- .../output/auth-directive.graphql | 4 +- .../output/custom-directive.graphql | 4 +- .../output/extended-types.graphql | 4 +- .../output/generate-directive.graphql | 4 +- .../output/single-extended-type.graphql | 4 +- .../input/auth-on-interfaces.graphql | 2 +- ...ing-directive-with-similar-queries.graphql | 21 + .../schemagen/input/language-tags.graphql | 2 +- .../input/searchables-references.graphql | 4 +- .../schemagen/input/searchables.graphql | 6 +- .../testdata/schemagen/input/union.graphql | 2 +- .../output/apollo-federation.graphql | 4 +- .../output/auth-on-interfaces.graphql | 4 +- .../schemagen/output/authorization.graphql | 4 +- .../output/comments-and-descriptions.graphql | 4 +- ...custom-dql-query-with-subscription.graphql | 4 +- .../schemagen/output/custom-mutation.graphql | 4 +- .../output/custom-nested-types.graphql | 4 +- .../output/custom-query-mixed-types.graphql | 4 +- .../custom-query-not-dgraph-type.graphql | 4 +- .../custom-query-with-dgraph-type.graphql | 4 +- .../schemagen/output/deprecated.graphql | 4 +- ...e-on-concrete-type-with-interfaces.graphql | 4 +- ...-reverse-directive-with-interfaces.graphql | 4 +- ...ing-directive-with-similar-queries.graphql | 575 ++++++++++++++++++ .../output/field-with-id-directive.graphql | 4 +- .../field-with-multiple-@id-fields.graphql | 4 +- ...erse-predicate-in-dgraph-directive.graphql | 4 +- .../filter-cleanSchema-all-empty.graphql | 4 +- .../filter-cleanSchema-circular.graphql | 4 +- ...filter-cleanSchema-custom-mutation.graphql | 4 +- .../filter-cleanSchema-directLink.graphql | 4 +- .../output/generate-directive.graphql | 4 +- .../schemagen/output/geo-type.graphql | 4 +- ...se-with-interface-having-directive.graphql | 4 +- .../output/hasInverse-with-interface.graphql | 4 +- ...Inverse-with-type-having-directive.graphql | 4 +- .../schemagen/output/hasInverse.graphql | 4 +- .../hasInverse_withSubscription.graphql | 4 +- .../schemagen/output/hasfilter.graphql | 4 +- .../ignore-unsupported-directive.graphql | 4 +- .../output/interface-with-dgraph-pred.graphql | 4 +- .../interface-with-id-directive.graphql | 4 +- .../output/interface-with-no-ids.graphql | 4 +- ...interfaces-with-types-and-password.graphql | 4 +- .../output/interfaces-with-types.graphql | 4 +- .../schemagen/output/lambda-directive.graphql | 4 +- .../schemagen/output/language-tags.graphql | 6 +- .../no-id-field-with-searchables.graphql | 4 +- .../schemagen/output/no-id-field.graphql | 4 +- .../schemagen/output/password-type.graphql | 4 +- .../testdata/schemagen/output/random.graphql | 4 +- .../output/searchables-references.graphql | 8 +- .../schemagen/output/searchables.graphql | 10 +- .../output/single-type-with-enum.graphql | 4 +- .../schemagen/output/single-type.graphql | 4 +- ...ype-implements-multiple-interfaces.graphql | 4 +- .../schemagen/output/type-reference.graphql | 4 +- .../type-with-arguments-on-field.graphql | 4 +- ...e-with-custom-field-on-dgraph-type.graphql | 4 +- ...-with-custom-fields-on-remote-type.graphql | 4 +- .../output/type-without-orderables.graphql | 4 +- .../testdata/schemagen/output/union.graphql | 4 +- graphql/schema/wrappers.go | 85 ++- graphql/schema/wrappers_test.go | 4 +- query/vector/integration_test.go | 41 ++ query/vector/vector_graphql_test.go | 206 +++++++ query/{ => vector}/vector_test.go | 120 ++++ systest/backup/common/utils.go | 2 +- systest/multi-tenancy/basic_test.go | 2 +- 95 files changed, 2227 insertions(+), 167 deletions(-) create mode 100644 graphql/schema/testdata/schemagen/input/embedding-directive-with-similar-queries.graphql mode change 100755 => 100644 graphql/schema/testdata/schemagen/output/comments-and-descriptions.graphql mode change 100755 => 100644 graphql/schema/testdata/schemagen/output/custom-dql-query-with-subscription.graphql mode change 100755 => 100644 graphql/schema/testdata/schemagen/output/custom-nested-types.graphql mode change 100755 => 100644 graphql/schema/testdata/schemagen/output/custom-query-not-dgraph-type.graphql mode change 100755 => 100644 graphql/schema/testdata/schemagen/output/custom-query-with-dgraph-type.graphql mode change 100755 => 100644 graphql/schema/testdata/schemagen/output/deprecated.graphql mode change 100755 => 100644 graphql/schema/testdata/schemagen/output/dgraph-reverse-directive-on-concrete-type-with-interfaces.graphql mode change 100755 => 100644 graphql/schema/testdata/schemagen/output/dgraph-reverse-directive-with-interfaces.graphql create mode 100644 graphql/schema/testdata/schemagen/output/embedding-directive-with-similar-queries.graphql mode change 100755 => 100644 graphql/schema/testdata/schemagen/output/field-with-id-directive.graphql mode change 100755 => 100644 graphql/schema/testdata/schemagen/output/field-with-multiple-@id-fields.graphql mode change 100755 => 100644 graphql/schema/testdata/schemagen/output/field-with-reverse-predicate-in-dgraph-directive.graphql mode change 100755 => 100644 graphql/schema/testdata/schemagen/output/hasInverse-with-interface-having-directive.graphql mode change 100755 => 100644 graphql/schema/testdata/schemagen/output/hasInverse-with-interface.graphql mode change 100755 => 100644 graphql/schema/testdata/schemagen/output/hasInverse-with-type-having-directive.graphql mode change 100755 => 100644 graphql/schema/testdata/schemagen/output/hasInverse.graphql mode change 100755 => 100644 graphql/schema/testdata/schemagen/output/hasInverse_withSubscription.graphql mode change 100755 => 100644 graphql/schema/testdata/schemagen/output/ignore-unsupported-directive.graphql mode change 100755 => 100644 graphql/schema/testdata/schemagen/output/interface-with-id-directive.graphql mode change 100755 => 100644 graphql/schema/testdata/schemagen/output/interface-with-no-ids.graphql mode change 100755 => 100644 graphql/schema/testdata/schemagen/output/interfaces-with-types-and-password.graphql mode change 100755 => 100644 graphql/schema/testdata/schemagen/output/interfaces-with-types.graphql mode change 100755 => 100644 graphql/schema/testdata/schemagen/output/language-tags.graphql mode change 100755 => 100644 graphql/schema/testdata/schemagen/output/no-id-field-with-searchables.graphql mode change 100755 => 100644 graphql/schema/testdata/schemagen/output/no-id-field.graphql mode change 100755 => 100644 graphql/schema/testdata/schemagen/output/password-type.graphql mode change 100755 => 100644 graphql/schema/testdata/schemagen/output/searchables-references.graphql mode change 100755 => 100644 graphql/schema/testdata/schemagen/output/searchables.graphql mode change 100755 => 100644 graphql/schema/testdata/schemagen/output/single-type-with-enum.graphql mode change 100755 => 100644 graphql/schema/testdata/schemagen/output/single-type.graphql mode change 100755 => 100644 graphql/schema/testdata/schemagen/output/type-implements-multiple-interfaces.graphql mode change 100755 => 100644 graphql/schema/testdata/schemagen/output/type-reference.graphql create mode 100644 query/vector/integration_test.go create mode 100644 query/vector/vector_graphql_test.go rename query/{ => vector}/vector_test.go (82%) diff --git a/dgraphtest/local_cluster.go b/dgraphtest/local_cluster.go index 0e2c47b0720..b348b742724 100644 --- a/dgraphtest/local_cluster.go +++ b/dgraphtest/local_cluster.go @@ -22,6 +22,7 @@ import ( "fmt" "io" "log" + "math/rand" "net/http" "os" "os/exec" @@ -346,7 +347,8 @@ func (c *LocalCluster) Start() error { if err1 := c.Stop(); err1 != nil { log.Printf("[WARNING] error while stopping :%v", err) } - c.Cleanup(false) + c.Cleanup(true) + c.conf.prefix = fmt.Sprintf("dgraphtest-%d", rand.NewSource(time.Now().UnixNano()).Int63()%1000000) if err := c.init(); err != nil { c.Cleanup(true) return err @@ -449,11 +451,7 @@ func (c *LocalCluster) HealthCheck(zeroOnly bool) error { if !zo.isRunning { break } - url, err := zo.healthURL(c) - if err != nil { - return errors.Wrap(err, "error getting health URL") - } - if err := c.containerHealthCheck(url); err != nil { + if err := c.containerHealthCheck(zo.healthURL); err != nil { return err } log.Printf("[INFO] container [%v] passed health check", zo.containerName) @@ -470,11 +468,7 @@ func (c *LocalCluster) HealthCheck(zeroOnly bool) error { if !aa.isRunning { break } - url, err := aa.healthURL(c) - if err != nil { - return errors.Wrap(err, "error getting health URL") - } - if err := c.containerHealthCheck(url); err != nil { + if err := c.containerHealthCheck(aa.healthURL); err != nil { return err } log.Printf("[INFO] container [%v] passed health check", aa.containerName) @@ -486,18 +480,27 @@ func (c *LocalCluster) HealthCheck(zeroOnly bool) error { return nil } -func (c *LocalCluster) containerHealthCheck(url string) error { +func (c *LocalCluster) containerHealthCheck(url func(c *LocalCluster) (string, error)) error { + endpoint, err := url(c) + if err != nil { + return errors.Wrap(err, "error getting health URL") + } for i := 0; i < 60; i++ { time.Sleep(waitDurBeforeRetry) - req, err := http.NewRequest(http.MethodGet, url, nil) + endpoint, err = url(c) + if err != nil { + return errors.Wrap(err, "error getting health URL") + } + + req, err := http.NewRequest(http.MethodGet, endpoint, nil) if err != nil { - log.Printf("[WARNING] error building req for endpoint [%v], err: [%v]", url, err) + log.Printf("[WARNING] error building req for endpoint [%v], err: [%v]", endpoint, err) continue } body, err := doReq(req) if err != nil { - log.Printf("[WARNING] error hitting health endpoint [%v], err: [%v]", url, err) + log.Printf("[WARNING] error hitting health endpoint [%v], err: [%v]", endpoint, err) continue } resp := string(body) @@ -523,7 +526,7 @@ func (c *LocalCluster) containerHealthCheck(url string) error { return nil } - return fmt.Errorf("health failed, cluster took too long to come up [%v]", url) + return fmt.Errorf("health failed, cluster took too long to come up [%v]", endpoint) } func (c *LocalCluster) waitUntilLogin() error { diff --git a/graphql/bench/schema.graphql b/graphql/bench/schema.graphql index 91ddba4f481..7299905b12b 100644 --- a/graphql/bench/schema.graphql +++ b/graphql/bench/schema.graphql @@ -69,4 +69,4 @@ type Owner { hasRestaurants: [Restaurant] @hasInverse(field: owner) } -# Dgraph.Authorization {"VerificationKey":"secretkey","Header":"X-Test-Auth","Namespace":"https://xyz.io/jwt/claims","Algo":"HS256","Audience":["aud1","63do0q16n6ebjgkumu05kkeian","aud5"]} \ No newline at end of file +# Dgraph.Authorization {"VerificationKey":"secretkey","Header":"X-Test-Auth","Namespace":"https://xyz.io/jwt/claims","Algo":"HS256","Audience":["aud1","63do0q16n6ebjgkumu05kkeian","aud5"]} diff --git a/graphql/bench/schema_auth.graphql b/graphql/bench/schema_auth.graphql index 245bbc5f7dd..50b62cf009b 100644 --- a/graphql/bench/schema_auth.graphql +++ b/graphql/bench/schema_auth.graphql @@ -248,4 +248,4 @@ type Owner @auth( hasRestaurants: [Restaurant] @hasInverse(field: owner) } -# Dgraph.Authorization {"VerificationKey":"secretkey","Header":"X-Test-Auth","Namespace":"https://xyz.io/jwt/claims","Algo":"HS256","Audience":["aud1","63do0q16n6ebjgkumu05kkeian","aud5"]} \ No newline at end of file +# Dgraph.Authorization {"VerificationKey":"secretkey","Header":"X-Test-Auth","Namespace":"https://xyz.io/jwt/claims","Algo":"HS256","Audience":["aud1","63do0q16n6ebjgkumu05kkeian","aud5"]} diff --git a/graphql/dgraph/graphquery.go b/graphql/dgraph/graphquery.go index 99bb6352147..23519f45a78 100644 --- a/graphql/dgraph/graphquery.go +++ b/graphql/dgraph/graphquery.go @@ -28,12 +28,16 @@ import ( // validate query, and so doesn't return an error if query is 'malformed' - it might // just write something that wouldn't parse as a Dgraph query. func AsString(queries []*dql.GraphQuery) string { - if queries == nil { + if len(queries) == 0 { return "" } var b strings.Builder - x.Check2(b.WriteString("query {\n")) + queryName := queries[len(queries)-1].Attr + x.Check2(b.WriteString("query ")) + addQueryVars(&b, queryName, queries[0].Args) + x.Check2(b.WriteString("{\n")) + numRewrittenQueries := 0 for _, q := range queries { if q == nil { @@ -54,6 +58,24 @@ func AsString(queries []*dql.GraphQuery) string { return b.String() } +func addQueryVars(b *strings.Builder, queryName string, args map[string]string) { + dollarFound := false + for name, val := range args { + if strings.HasPrefix(name, "$") { + if !dollarFound { + x.Check2(b.WriteString(queryName + "(")) + x.Check2(b.WriteString(name + ": " + val)) + dollarFound = true + } else { + x.Check2(b.WriteString(", " + name + ": " + val)) + } + } + } + if dollarFound { + x.Check2(b.WriteString(") ")) + } +} + func writeQuery(b *strings.Builder, query *dql.GraphQuery, prefix string) { if query.Var != "" || query.Alias != "" || query.Attr != "" { x.Check2(b.WriteString(prefix)) @@ -145,6 +167,9 @@ func writeRoot(b *strings.Builder, q *dql.GraphQuery) { } switch { + // TODO: Instead of the hard-coded strings "uid", "type", etc., use the + // pre-defined constants in dql/parser.go such as dql.uidFunc, dql.typFunc, + // etc. This of course will require that we make these constants public. case q.Func.Name == "uid": x.Check2(b.WriteString("(func: ")) writeUIDFunc(b, q.Func.UID, q.Func.Args) @@ -154,6 +179,10 @@ func writeRoot(b *strings.Builder, q *dql.GraphQuery) { x.Check2(b.WriteString("(func: eq(")) writeFilterArguments(b, q.Func.Args) x.Check2(b.WriteRune(')')) + case q.Func.Name == "similar_to": + x.Check2(b.WriteString("(func: similar_to(")) + writeFilterArguments(b, q.Func.Args) + x.Check2(b.WriteRune(')')) } writeOrderAndPage(b, q, true) x.Check2(b.WriteRune(')')) diff --git a/graphql/e2e/auth/schema.graphql b/graphql/e2e/auth/schema.graphql index a9542def0a7..4148b373968 100644 --- a/graphql/e2e/auth/schema.graphql +++ b/graphql/e2e/auth/schema.graphql @@ -568,7 +568,7 @@ type Contact @auth( query: { rule: "{$ContactRole: { eq: \"ADMINISTRATOR\"}}" } ) { id: ID! - nickName: String @search(by: [exact, term, fulltext, regexp]) + nickName: String @search(by: ["exact", "term", "fulltext", "regexp"]) adminTasks: [AdminTask] @hasInverse(field: forContact) tasks: [Task] @hasInverse(field: forContact) } @@ -577,14 +577,14 @@ type AdminTask @auth( query: { rule: "{$TaskRole: { eq: \"ADMINISTRATOR\"}}" } ) { id: ID! - name: String @search(by: [exact, term, fulltext, regexp]) + name: String @search(by: ["exact", "term", "fulltext", "regexp"]) occurrences: [TaskOccurrence] @hasInverse(field: adminTask) forContact: Contact @hasInverse(field: adminTasks) } type Task { id: ID! - name: String @search(by: [exact, term, fulltext, regexp]) + name: String @search(by: ["exact", "term", "fulltext", "regexp"]) occurrences: [TaskOccurrence] @hasInverse(field: task) forContact: Contact @hasInverse(field: tasks) } @@ -608,7 +608,7 @@ type TaskOccurrence @auth( task: Task @hasInverse(field: occurrences) adminTask: AdminTask @hasInverse(field: occurrences) isPublic: Boolean @search - role: String @search(by: [exact, term, fulltext, regexp]) + role: String @search(by: ["exact", "term", "fulltext", "regexp"]) } type Author { diff --git a/graphql/e2e/common/query.go b/graphql/e2e/common/query.go index 5bb4c1136bc..4c26375132a 100644 --- a/graphql/e2e/common/query.go +++ b/graphql/e2e/common/query.go @@ -1285,7 +1285,7 @@ func stringExactFilters(t *testing.T) { func scalarListFilters(t *testing.T) { - // tags is a list of strings with @search(by: exact). So all the filters + // tags is a list of strings with @search(by: "exact"). So all the filters // lt, le, ... mean "is there something in the list that's lt 'Dgraph'", etc. cases := map[string]struct { diff --git a/graphql/e2e/custom_logic/custom_logic_test.go b/graphql/e2e/custom_logic/custom_logic_test.go index e0e4861c2b8..8601228c01a 100644 --- a/graphql/e2e/custom_logic/custom_logic_test.go +++ b/graphql/e2e/custom_logic/custom_logic_test.go @@ -1067,7 +1067,7 @@ func TestCustomFieldsShouldPassBody(t *testing.T) { schema := ` type User { - id: String! @id @search(by: [hash, regexp]) + id: String! @id @search(by: ["hash", "regexp"]) address:String name: String @custom( @@ -2573,7 +2573,7 @@ func TestCustomDQL(t *testing.T) { } type Tweets implements Node { id: ID! - text: String! @search(by: [fulltext, exact]) + text: String! @search(by: ["fulltext", "exact"]) user: User timestamp: DateTime! @search } @@ -2864,7 +2864,7 @@ func TestCustomFieldsWithRestError(t *testing.T) { } type User { - id: String! @id @search(by: [hash, regexp]) + id: String! @id @search(by: ["hash", "regexp"]) name: String @custom( http: { diff --git a/graphql/e2e/directives/schema.graphql b/graphql/e2e/directives/schema.graphql index f7e6718608b..84b9e5eee32 100644 --- a/graphql/e2e/directives/schema.graphql +++ b/graphql/e2e/directives/schema.graphql @@ -423,4 +423,4 @@ type CricketTeam implements Team { type LibraryManager { name: String! @id manages: [LibraryMember] -} \ No newline at end of file +} diff --git a/graphql/e2e/normal/schema.graphql b/graphql/e2e/normal/schema.graphql index 396dea4318b..1f8a2d2956e 100644 --- a/graphql/e2e/normal/schema.graphql +++ b/graphql/e2e/normal/schema.graphql @@ -422,4 +422,4 @@ type CricketTeam implements Team { type LibraryManager { name: String! @id manages: [LibraryMember] -} \ No newline at end of file +} diff --git a/graphql/e2e/schema/apollo_service_response.graphql b/graphql/e2e/schema/apollo_service_response.graphql index c0383cdeec5..14f6efa1e0b 100644 --- a/graphql/e2e/schema/apollo_service_response.graphql +++ b/graphql/e2e/schema/apollo_service_response.graphql @@ -83,6 +83,7 @@ enum DgraphIndex { day hour geo + hnsw } input AuthRule { @@ -196,7 +197,8 @@ input GenerateMutationParams { } directive @hasInverse(field: String!) on FIELD_DEFINITION -directive @search(by: [DgraphIndex!]) on FIELD_DEFINITION +directive @search(by: [String!]) on FIELD_DEFINITION +directive @embedding on FIELD_DEFINITION directive @dgraph(type: String, pred: String) on OBJECT | INTERFACE | FIELD_DEFINITION directive @id(interface: Boolean) on FIELD_DEFINITION directive @withSubscription on OBJECT | INTERFACE | FIELD_DEFINITION diff --git a/graphql/e2e/schema/generatedSchema.graphql b/graphql/e2e/schema/generatedSchema.graphql index 6b9c37d5f31..70cc4f3f19c 100644 --- a/graphql/e2e/schema/generatedSchema.graphql +++ b/graphql/e2e/schema/generatedSchema.graphql @@ -64,6 +64,7 @@ enum DgraphIndex { day hour geo + hnsw } input AuthRule { @@ -177,7 +178,8 @@ input GenerateMutationParams { } directive @hasInverse(field: String!) on FIELD_DEFINITION -directive @search(by: [DgraphIndex!]) on FIELD_DEFINITION +directive @search(by: [String!]) on FIELD_DEFINITION +directive @embedding on FIELD_DEFINITION directive @dgraph(type: String, pred: String) on OBJECT | INTERFACE | FIELD_DEFINITION directive @id(interface: Boolean) on FIELD_DEFINITION directive @withSubscription on OBJECT | INTERFACE | FIELD_DEFINITION diff --git a/graphql/e2e/schema/schema_test.go b/graphql/e2e/schema/schema_test.go index 0b37f440264..671f93def11 100644 --- a/graphql/e2e/schema/schema_test.go +++ b/graphql/e2e/schema/schema_test.go @@ -564,7 +564,7 @@ func TestLargeSchemaUpdate(t *testing.T) { schema := "type LargeSchema {" for i := 1; i <= numFields; i++ { - schema = schema + "\n" + fmt.Sprintf("field%d: String! @search(by: [regexp])", i) + schema = schema + "\n" + fmt.Sprintf("field%d: String! @search(by: [\"regexp\"])", i) } schema = schema + "\n}" diff --git a/graphql/e2e/subscription/subscription_test.go b/graphql/e2e/subscription/subscription_test.go index 3dda3c231c9..f546ed81ec5 100644 --- a/graphql/e2e/subscription/subscription_test.go +++ b/graphql/e2e/subscription/subscription_test.go @@ -46,7 +46,7 @@ const ( } type Customer { - username: String! @id @search(by: [hash, regexp]) + username: String! @id @search(by: ["hash", "regexp"]) reviews: [Review] @hasInverse(field: by) } diff --git a/graphql/resolve/mutation_rewriter.go b/graphql/resolve/mutation_rewriter.go index 669cfafa6ae..a5914347b76 100644 --- a/graphql/resolve/mutation_rewriter.go +++ b/graphql/resolve/mutation_rewriter.go @@ -1675,6 +1675,12 @@ func rewriteObject( fieldName = fieldName[1 : len(fieldName)-1] } + if fieldDef.HasEmbeddingDirective() { + // embedding is a JSON array of numbers. Rewrite it as a string, for now + var valBytes []byte + valBytes, _ = json.Marshal(val) + val = string(valBytes) + } // TODO: Write a function for aggregating data of fragment from child nodes. switch val := val.(type) { case map[string]interface{}: diff --git a/graphql/resolve/query_rewriter.go b/graphql/resolve/query_rewriter.go index e58f03ab73b..ced445bdcb6 100644 --- a/graphql/resolve/query_rewriter.go +++ b/graphql/resolve/query_rewriter.go @@ -19,6 +19,7 @@ package resolve import ( "bytes" "context" + "encoding/json" "fmt" "sort" "strconv" @@ -147,7 +148,14 @@ func (qr *queryRewriter) Rewrite( dgQuery := rewriteAsGet(gqlQuery, uid, xid, authRw) return dgQuery, nil - + case schema.SimilarByIdQuery: + xid, uid, err := gqlQuery.IDArgValue() + if err != nil { + return nil, err + } + return rewriteAsSimilarByIdQuery(gqlQuery, uid, xid, authRw), nil + case schema.SimilarByEmbeddingQuery: + return rewriteAsSimilarByEmbeddingQuery(gqlQuery, authRw), nil case schema.FilterQuery: return rewriteAsQuery(gqlQuery, authRw), nil case schema.PasswordQuery: @@ -612,6 +620,291 @@ func rewriteAsGet( return dgQuery } +// rewriteAsSimilarByIdQuery +// +// rewrites SimilarById graphQL query to nested DQL query blocks +// Example rewrittern query: +// +// query { +// var(func: eq(Product.id, "0528012398")) @filter(type(Product)) { +// vec as Product.embedding +// } +// var() { +// v1 as max(val(vec)) +// } +// var(func: similar_to(Product.embedding, 8, val(v1))) { +// v2 as Product.embedding +// distance as math((v2 - v1) dot (v2 - v1)) +// } +// querySimilarProductById(func: uid(distance) +// @filter(Product.id != "0528012398"), orderasc: val(distance)) { +// Product.id : Product.id +// Product.description : Product.description +// Product.title : Product.title +// Product.imageUrl : Product.imageUrl +// Product.vector_distance : val(distance) +// dgraph.uid : uid +// } +// } +func rewriteAsSimilarByIdQuery( + query schema.Query, + uid uint64, + xidArgToVal map[string]string, + auth *authRewriter) []*dql.GraphQuery { + + // Get graphQL arguments + typ := query.Type() + similarBy := query.ArgValue(schema.SimilarByArgName).(string) + pred := typ.DgraphPredicate(similarBy) + topK := query.ArgValue(schema.SimilarTopKArgName) + similarByField := typ.Field(similarBy) + metric := similarByField.EmbeddingSearchMetric() + distanceFormula := "math((v2 - v1) dot (v2 - v1))" // default - euclidian + + if metric == schema.SimilarSearchMetricDotProduct { + distanceFormula = "math(v1 dot v2)" + } else if metric == schema.SimilarSearchMetricCosine { + distanceFormula = "math((v1 dot v2) / ((v1 dot v1) * (v2 dot v2)))" + } + + // First generate the query to fetch the uid + // for the given id. For Example, + // var(func: eq(Product.id, "0528012398")) @filter(type(Product)) { + // vec as Product.embedding + // } + dgQuery := rewriteAsGet(query, uid, xidArgToVal, auth) + lastQuery := dgQuery[len(dgQuery)-1] + // Turn the root query into "var" + lastQuery.Attr = "var" + // Save the result to be later used for the last query block, sortQuery + result := lastQuery.Children + + // define the variable "vec" for the search vector + lastQuery.Children = []*dql.GraphQuery{{ + Attr: pred, + Var: "vec", + }} + + // Turn the variable into a "const" by + // remembering the max of it. + // The lookup is going to return exactly one uid + // anyway. For example, + // var() { + // v1 as max(val(vec)) + // } + aggQuery := &dql.GraphQuery{ + Attr: "var" + "()", + Children: []*dql.GraphQuery{ + { + Var: "v1", + Attr: "max(val(vec))", + }, + }, + } + + // Similar_to query, computes the distance for + // ordering the result later. + // Example: + // var(func: similar_to(Product.embedding, 8, val(v1))) { + // v2 as Product.embedding + // distance as math((v2 - v1) dot (v2 - v1)) + // } + similarQuery := &dql.GraphQuery{ + Attr: "var", + Children: []*dql.GraphQuery{ + { + Var: "v2", + Attr: pred, + }, + { + Var: "distance", + Attr: distanceFormula, + }, + }, + Func: &dql.Function{ + Name: "similar_to", + Args: []dql.Arg{ + { + Value: pred, + }, + { + Value: fmt.Sprintf("%v", topK), + }, + { + Value: "val(v1)", + }, + }, + }, + } + + // Rename the distance as .vector_distance + distance := &dql.GraphQuery{ + Alias: typ.Name() + "." + schema.SimilarQueryDistanceFieldName, + Attr: "val(distance)", + } + + var found bool = false + for _, child := range result { + if child.Alias == typ.Name()+"."+schema.SimilarQueryDistanceFieldName { + child.Attr = "val(distance)" + found = true + break + } + } + if !found { + result = append(result, distance) + } + + // order the result by euclidian distance, For example, + // querySimilarProductById(func: uid(distance), orderasc: val(distance)) { + // Product.id : Product.id + // Product.description : Product.description + // Product.title : Product.title + // Product.imageUrl : Product.imageUrl + // Product.vector_distance : val(distance) + // dgraph.uid : uid + // } + // } + sortQuery := &dql.GraphQuery{ + Attr: query.DgraphAlias(), + Children: result, + Func: &dql.Function{ + Name: "uid", + Args: []dql.Arg{{Value: "distance"}}, + }, + Order: []*pb.Order{{Attr: "val(distance)", Desc: false}}, + } + addArgumentsToField(sortQuery, query) + + dgQuery = append(dgQuery, aggQuery, similarQuery, sortQuery) + return dgQuery +} + +// rewriteAsSimilarByEmbeddingQuery +// +// rewrites SimilarByEmbedding graphQL query to nested DQL query blocks +// Example rewrittern query: +// +// query gQLTodQL($search_vector: float32vector = "") { +// var(func: similar_to(Product.embedding, 8, $search_vector)) { +// v2 as Product.embedding +// distance as math((v2 - $search_vector) dot (v2 - $search_vector)) +// } +// querySimilarProductById(func: uid(distance), +// @filter(Product.id != "0528012398"), orderasc: val(distance)) { +// Product.id : Product.id +// Product.description : Product.description +// Product.title : Product.title +// Product.imageUrl : Product.imageUrl +// Product.vector_distance : val(distance) +// dgraph.uid : uid +// } +// } +func rewriteAsSimilarByEmbeddingQuery( + query schema.Query, auth *authRewriter) []*dql.GraphQuery { + + dgQuery := rewriteAsQuery(query, auth) + + // Remember dgQuery[0].Children as result type for the last block + // in the rewritten query + result := dgQuery[0].Children + typ := query.Type() + + // Get all the arguments from graphQL query + similarBy := query.ArgValue(schema.SimilarByArgName).(string) + pred := typ.DgraphPredicate(similarBy) + topK := query.ArgValue(schema.SimilarTopKArgName) + vec := query.ArgValue(schema.SimilarVectorArgName).([]interface{}) + vecStr, _ := json.Marshal(vec) + + similarByField := typ.Field(similarBy) + metric := similarByField.EmbeddingSearchMetric() + distanceFormula := "math((v2 - $search_vector) dot (v2 - $search_vector))" // default = euclidian + + if metric == schema.SimilarSearchMetricDotProduct { + distanceFormula = "math($search_vector dot v2)" + } else if metric == schema.SimilarSearchMetricCosine { + distanceFormula = "math(($search_vector dot v2) / (($search_vector dot $search_vector) * (v2 dot v2)))" + } + + // Save vectorString as a query variable, $search_vector + queryArgs := dgQuery[0].Args + if queryArgs == nil { + queryArgs = make(map[string]string) + } + queryArgs["$search_vector"] = " float32vector = \"" + string(vecStr) + "\"" + thisFilter := &dql.FilterTree{ + Func: dgQuery[0].Func, + } + + // create the similar_to function and move existing root function + // to the filter tree + addToFilterTree(dgQuery[0], thisFilter) + + // Create similar_to as the root function, passing $search_vector as + // the search vector + dgQuery[0].Attr = "var" + dgQuery[0].Func = &dql.Function{ + Name: "similar_to", + Args: []dql.Arg{ + { + Value: pred, + }, + { + Value: fmt.Sprintf("%v", topK), + }, + { + Value: "$search_vector", + }, + }, + } + + // Compute the euclidian distance between the neighbor + // and the search vector + dgQuery[0].Children = []*dql.GraphQuery{ + { + Var: "v2", + Attr: pred, + }, + { + Var: "distance", + Attr: distanceFormula, + }, + } + + // Rename distance as .vector_distance + distance := &dql.GraphQuery{ + Alias: typ.Name() + "." + schema.SimilarQueryDistanceFieldName, + Attr: "val(distance)", + } + + var found bool = false + for _, child := range result { + if child.Alias == typ.Name()+"."+schema.SimilarQueryDistanceFieldName { + child.Attr = "val(distance)" + found = true + break + } + } + if !found { + result = append(result, distance) + } + + // order by distance + sortQuery := &dql.GraphQuery{ + Attr: query.DgraphAlias(), + Children: result, + Func: &dql.Function{ + Name: "uid", + Args: []dql.Arg{{Value: "distance"}}, + }, + Order: []*pb.Order{{Attr: "val(distance)", Desc: false}}, + } + + dgQuery = append(dgQuery, sortQuery) + return dgQuery +} + // Adds common RBAC and UID, Type rules to DQL query. // This function is used by rewriteAsQuery and aggregateQuery functions func addCommonRules( diff --git a/graphql/resolve/query_test.yaml b/graphql/resolve/query_test.yaml index c281bc5932a..c74b9d77471 100644 --- a/graphql/resolve/query_test.yaml +++ b/graphql/resolve/query_test.yaml @@ -3353,3 +3353,168 @@ dgraph.uid : uid } } +- name: "query similar_to" + gqlquery: | + query { + querySimilarProductByEmbedding(by: productVector, topK: 1, vector: [0.1, 0.2, 0.3, 0.4, 0.5]) { + id + title + productVector + } + } + + dgquery: |- + query querySimilarProductByEmbedding($search_vector: float32vector = "[0.1,0.2,0.3,0.4,0.5]") { + var(func: similar_to(Product.productVector, 1, $search_vector)) @filter(type(Product)) { + v2 as Product.productVector + distance as math((v2 - $search_vector) dot (v2 - $search_vector)) + } + querySimilarProductByEmbedding(func: uid(distance), orderasc: val(distance)) { + Product.id : Product.id + Product.title : Product.title + Product.productVector : Product.productVector + dgraph.uid : uid + Product.vector_distance : val(distance) + } + } +- name: "query vector using uid" + gqlquery: | + query { + querySimilarProductById(by: productVector, topK: 3, id: "0x1") { + id + title + productVector + } + } + + dgquery: |- + query { + var(func: eq(Product.id, "0x1")) @filter(type(Product)) { + vec as Product.productVector + } + var() { + v1 as max(val(vec)) + } + var(func: similar_to(Product.productVector, 3, val(v1))) { + v2 as Product.productVector + distance as math((v2 - v1) dot (v2 - v1)) + } + querySimilarProductById(func: uid(distance), orderasc: val(distance)) { + Product.id : Product.id + Product.title : Product.title + Product.productVector : Product.productVector + dgraph.uid : uid + Product.vector_distance : val(distance) + } + } + +- name: "query vector by id with cosine distance" + gqlquery: | + query { + querySimilarProjectCosineById(by: description_v, topK: 3, id: "0x1") { + id + title + description_v + } + } + + dgquery: |- + query { + var(func: eq(ProjectCosine.id, "0x1")) @filter(type(ProjectCosine)) { + vec as ProjectCosine.description_v + } + var() { + v1 as max(val(vec)) + } + var(func: similar_to(ProjectCosine.description_v, 3, val(v1))) { + v2 as ProjectCosine.description_v + distance as math((v1 dot v2) / ((v1 dot v1) * (v2 dot v2))) + } + querySimilarProjectCosineById(func: uid(distance), orderasc: val(distance)) { + ProjectCosine.id : ProjectCosine.id + ProjectCosine.title : ProjectCosine.title + ProjectCosine.description_v : ProjectCosine.description_v + dgraph.uid : uid + ProjectCosine.vector_distance : val(distance) + } + } + +- name: "query similar_to with cosine distance" + gqlquery: | + query { + querySimilarProjectCosineByEmbedding(by: description_v, topK: 1, vector: [0.1, 0.2, 0.3, 0.4, 0.5]) { + id + title + description_v + } + } + + dgquery: |- + query querySimilarProjectCosineByEmbedding($search_vector: float32vector = "[0.1,0.2,0.3,0.4,0.5]") { + var(func: similar_to(ProjectCosine.description_v, 1, $search_vector)) @filter(type(ProjectCosine)) { + v2 as ProjectCosine.description_v + distance as math(($search_vector dot v2) / (($search_vector dot $search_vector) * (v2 dot v2))) + } + querySimilarProjectCosineByEmbedding(func: uid(distance), orderasc: val(distance)) { + ProjectCosine.id : ProjectCosine.id + ProjectCosine.title : ProjectCosine.title + ProjectCosine.description_v : ProjectCosine.description_v + dgraph.uid : uid + ProjectCosine.vector_distance : val(distance) + } + } +- name: "query vector by id with dot product distance" + gqlquery: | + query { + querySimilarProjectDotProductById(by: description_v, topK: 3, id: "0x1") { + id + title + description_v + } + } + + dgquery: |- + query { + var(func: eq(ProjectDotProduct.id, "0x1")) @filter(type(ProjectDotProduct)) { + vec as ProjectDotProduct.description_v + } + var() { + v1 as max(val(vec)) + } + var(func: similar_to(ProjectDotProduct.description_v, 3, val(v1))) { + v2 as ProjectDotProduct.description_v + distance as math(v1 dot v2) + } + querySimilarProjectDotProductById(func: uid(distance), orderasc: val(distance)) { + ProjectDotProduct.id : ProjectDotProduct.id + ProjectDotProduct.title : ProjectDotProduct.title + ProjectDotProduct.description_v : ProjectDotProduct.description_v + dgraph.uid : uid + ProjectDotProduct.vector_distance : val(distance) + } + } + +- name: "query similar_to with dot product distance" + gqlquery: | + query { + querySimilarProjectDotProductByEmbedding(by: description_v, topK: 1, vector: [0.1, 0.2, 0.3, 0.4, 0.5]) { + id + title + description_v + } + } + + dgquery: |- + query querySimilarProjectDotProductByEmbedding($search_vector: float32vector = "[0.1,0.2,0.3,0.4,0.5]") { + var(func: similar_to(ProjectDotProduct.description_v, 1, $search_vector)) @filter(type(ProjectDotProduct)) { + v2 as ProjectDotProduct.description_v + distance as math($search_vector dot v2) + } + querySimilarProjectDotProductByEmbedding(func: uid(distance), orderasc: val(distance)) { + ProjectDotProduct.id : ProjectDotProduct.id + ProjectDotProduct.title : ProjectDotProduct.title + ProjectDotProduct.description_v : ProjectDotProduct.description_v + dgraph.uid : uid + ProjectDotProduct.vector_distance : val(distance) + } + } \ No newline at end of file diff --git a/graphql/resolve/resolver.go b/graphql/resolve/resolver.go index 7b0936ec755..79dd12b16ab 100644 --- a/graphql/resolve/resolver.go +++ b/graphql/resolve/resolver.go @@ -235,6 +235,8 @@ func (rf *resolverFactory) WithConventionResolvers( s schema.Schema, fns *ResolverFns) ResolverFactory { queries := append(s.Queries(schema.GetQuery), s.Queries(schema.FilterQuery)...) + queries = append(queries, s.Queries(schema.SimilarByIdQuery)...) + queries = append(queries, s.Queries(schema.SimilarByEmbeddingQuery)...) queries = append(queries, s.Queries(schema.PasswordQuery)...) queries = append(queries, s.Queries(schema.AggregateQuery)...) for _, q := range queries { diff --git a/graphql/resolve/resolver_error_test.go b/graphql/resolve/resolver_error_test.go index cdd4d2a30ff..be298e67765 100644 --- a/graphql/resolve/resolver_error_test.go +++ b/graphql/resolve/resolver_error_test.go @@ -83,6 +83,14 @@ type Author { postsNullableListRequired: [Post]! } +type Product { + id: String! @id + description: String + title: String + imageUrl: String + productVector: [Float!] @embedding +} + type Post { id: ID! title: String! diff --git a/graphql/resolve/schema.graphql b/graphql/resolve/schema.graphql index 6d8b2a1588d..6b469284860 100644 --- a/graphql/resolve/schema.graphql +++ b/graphql/resolve/schema.graphql @@ -11,7 +11,7 @@ type Hotel { type Country { id: ID! - name: String! @search(by: [trigram, exact]) + name: String! @search(by: ["trigram", "exact"]) states: [State] @hasInverse(field: country) } @@ -499,4 +499,26 @@ type CricketTeam implements Team { type LibraryManager { name: String! @id manages: [LibraryMember] -} \ No newline at end of file +} + +type Product { + id: String! @id + description: String + title: String + imageUrl: String + productVector: [Float!] @embedding +} + +type ProjectCosine { + id: String! @id + description: String + title: String + description_v: [Float!] @embedding @search(by: ["hnsw(metric: cosine, exponent: 4)"]) +} + +type ProjectDotProduct { + id: String! @id + description: String + title: String + description_v: [Float!] @embedding @search(by: ["hnsw(metric: dotproduct, exponent: 4)"]) +} diff --git a/graphql/schema/dgraph_schemagen_test.yml b/graphql/schema/dgraph_schemagen_test.yml index 32bb64b3b58..ba066df38da 100644 --- a/graphql/schema/dgraph_schemagen_test.yml +++ b/graphql/schema/dgraph_schemagen_test.yml @@ -141,20 +141,24 @@ schemas: s5: String @search(by: [fulltext]) s6: String @search(by: [trigram]) s7: String @search(by: [regexp]) - s8: String @search(by: [exact, fulltext, term, trigram]) + s8: String @search(by: ["exact", "fulltext", "term", "trigram"]) dt1: DateTime @search dt2: DateTime @search(by: [year]) dt3: DateTime @search(by: [month]) dt4: DateTime @search(by: [day]) dt5: DateTime @search(by: [hour]) + vf1: [Float!] @embedding @search(by: ["hnsw"]) + vf2: [Float!] @embedding @search(by: ["hnsw(exponent: 4, metric: euclidian)"]) + vf3: [Float!] @embedding @search(by: ["hnsw(metric: cosine)"]) + vf4: [Float!] @embedding @search(by: ["hnsw(metric: dotproduct, exponent: 4)"]) e: E @search e1: E @search(by: [hash]) e2: E @search(by: [exact]) e3: E @search(by: [trigram]) e4: E @search(by: [regexp]) - e5: E @search(by: [hash, regexp]) - e6: E @search(by: [hash, trigram]) - e7: E @search(by: [exact, regexp]) + e5: E @search(by: ["hash", "regexp"]) + e6: E @search(by: ["hash", "trigram"]) + e7: E @search(by: ["exact", "regexp"]) } enum E { A } output: | @@ -180,6 +184,10 @@ schemas: X.dt3 X.dt4 X.dt5 + X.vf1 + X.vf2 + X.vf3 + X.vf4 X.e X.e1 X.e2 @@ -210,6 +218,10 @@ schemas: X.dt3: dateTime @index(month) . X.dt4: dateTime @index(day) . X.dt5: dateTime @index(hour) . + X.vf1: float32vector @index(hnsw) . + X.vf2: float32vector @index(hnsw(exponent: "4", metric: "euclidian")) . + X.vf3: float32vector @index(hnsw(metric: "cosine")) . + X.vf4: float32vector @index(hnsw(exponent: "4", metric: "dotproduct")) . X.e: string @index(hash) . X.e1: string @index(hash) . X.e2: string @index(exact) . @@ -434,7 +446,7 @@ schemas: f2: String @dgraph(pred: "T.f@no") f3: String @dgraph(pred: "f3@en") name: String! @id - nameHi: String @dgraph(pred: "Person.name@hi") @search(by: [term, exact]) + nameHi: String @dgraph(pred: "Person.name@hi") @search(by: ["term", "exact"]) nameEn: String @dgraph(pred: "Person.name@en") @search(by: [regexp]) nameHiEn: String @dgraph(pred: "Person.name@hi:en") nameHi_En_Untag: String @dgraph(pred: "Person.name@hi:en:.") @@ -503,7 +515,7 @@ schemas: - name: "Field with @id directive and a hash arg in search directive generates correct schema." input: | interface A { - id: String! @id @search(by: [hash, term]) + id: String! @id @search(by: ["hash", "term"]) } type B implements A { correct: Boolean @search @@ -641,13 +653,13 @@ schemas: combined" input: | type A { - p: String @dgraph(pred: "name") @search(by: [exact, term]) + p: String @dgraph(pred: "name") @search(by: ["exact", "term"]) } type B { q: String @dgraph(pred: "name") @search(by: [trigram]) } type C { - q: String @dgraph(pred: "name") @search(by: [exact, term]) + q: String @dgraph(pred: "name") @search(by: ["exact", "term"]) } output: | type A { @@ -664,8 +676,8 @@ schemas: - name: "fields with @dgraph(pred: ...) contain different language." input: | type A { - content: String! @dgraph(pred: "post") @search(by: [exact, term]) - author: String @dgraph(pred: "<公司>") @search(by: [exact, term]) + content: String! @dgraph(pred: "post") @search(by: ["exact", "term"]) + author: String @dgraph(pred: "<公司>") @search(by: ["exact", "term"]) } output: | type A { diff --git a/graphql/schema/gqlschema.go b/graphql/schema/gqlschema.go index 7927f547e64..3fff2a7bae2 100644 --- a/graphql/schema/gqlschema.go +++ b/graphql/schema/gqlschema.go @@ -34,9 +34,10 @@ const ( searchDirective = "search" searchArgs = "by" - dgraphDirective = "dgraph" - dgraphTypeArg = "type" - dgraphPredArg = "pred" + dgraphDirective = "dgraph" + dgraphTypeArg = "type" + dgraphPredArg = "pred" + embeddingDirective = "embedding" idDirective = "id" idDirectiveInterfaceArg = "interface" @@ -161,6 +162,7 @@ enum DgraphIndex { day hour geo + hnsw } input AuthRule { @@ -275,7 +277,8 @@ input GenerateMutationParams { ` directiveDefs = ` directive @hasInverse(field: String!) on FIELD_DEFINITION -directive @search(by: [DgraphIndex!]) on FIELD_DEFINITION +directive @search(by: [String!]) on FIELD_DEFINITION +directive @embedding on FIELD_DEFINITION directive @dgraph(type: String, pred: String) on OBJECT | INTERFACE | FIELD_DEFINITION directive @id(interface: Boolean) on FIELD_DEFINITION directive @withSubscription on OBJECT | INTERFACE | FIELD_DEFINITION @@ -306,7 +309,8 @@ directive @generate( // So, such directives have to be missed too. apolloSupportedDirectiveDefs = ` directive @hasInverse(field: String!) on FIELD_DEFINITION -directive @search(by: [DgraphIndex!]) on FIELD_DEFINITION +directive @search(by: [String!]) on FIELD_DEFINITION +directive @embedding on FIELD_DEFINITION directive @dgraph(type: String, pred: String) on OBJECT | INTERFACE | FIELD_DEFINITION directive @id(interface: Boolean) on FIELD_DEFINITION directive @withSubscription on OBJECT | INTERFACE | FIELD_DEFINITION @@ -458,6 +462,7 @@ var supportedSearches = map[string]searchTypeIndex{ "point": {"Point", "geo"}, "polygon": {"Polygon", "geo"}, "multiPolygon": {"MultiPolygon", "geo"}, + "hnsw": {"Float", "hnsw"}, } // GraphQL scalar/object type -> default search arg @@ -532,6 +537,7 @@ var builtInFilters = map[string]string{ "point": "PointGeoFilter", "polygon": "PolygonGeoFilter", "multiPolygon": "PolygonGeoFilter", + "hnsw": "HNSWSearchFilter", } // GraphQL in-built type -> Dgraph scalar @@ -561,6 +567,7 @@ func ValidatorNoOp( var directiveValidators = map[string]directiveValidator{ inverseDirective: hasInverseValidation, searchDirective: searchValidation, + embeddingDirective: embeddingValidation, dgraphDirective: dgraphDirectiveValidation, idDirective: idValidation, subscriptionDirective: ValidatorNoOp, @@ -1469,6 +1476,7 @@ func getFilterTypes(schema *ast.Schema, fld *ast.FieldDefinition, filterName str filterNames := make([]string, len(searchArgs)) for i, search := range searchArgs { + search = parseSearchType(search) filterNames[i] = builtInFilters[search] // For enum type, if the index is "hash" or "exact", we construct filter named @@ -1671,6 +1679,10 @@ func hasXID(defn *ast.Definition) bool { return fieldAny(nonExternalAndKeyFields(defn), hasIDDirective) } +func hasEmbedding(defn *ast.Definition) bool { + return fieldAny(nonExternalAndKeyFields(defn), hasEmbeddingDirective) +} + // fieldAny returns true if any field in fields satisfies pred func fieldAny(fields ast.FieldList, pred func(*ast.FieldDefinition) bool) bool { for _, fld := range fields { @@ -1945,7 +1957,8 @@ func addAggregationResultType(schema *ast.Schema, defn *ast.Definition, provides } } -func addGetQuery(schema *ast.Schema, defn *ast.Definition, providesTypeMap map[string]bool, generateSubscription bool) { +func addGetQuery(schema *ast.Schema, defn *ast.Definition, + providesTypeMap map[string]bool, generateSubscription bool) { hasIDField := hasID(defn) hasXIDField := hasXID(defn) xidCount := xidsCount(defn.Fields) @@ -2006,6 +2019,184 @@ func addGetQuery(schema *ast.Schema, defn *ast.Definition, providesTypeMap map[s } } +// addSimilarByEmbeddingQuery adds a query to perform similarity based search on +// a specified embedding as an array of floats +// schema - the graphQL schema. The schema will be modified by this operation +// defn - The type definition for the object for which this query will be added +func addSimilarByEmbeddingQuery(schema *ast.Schema, defn *ast.Definition) { + + qry := &ast.FieldDefinition{ + Name: SimilarQueryPrefix + defn.Name + SimilarByEmbeddingQuerySuffix, + Type: &ast.Type{ + Elem: &ast.Type{ + NamedType: defn.Name, + }, + }, + } + + // The new field is "vector_distance". Add it to input Type + if defn.Fields.ForName(SimilarQueryDistanceFieldName) == nil { + defn.Fields = append(defn.Fields, + &ast.FieldDefinition{ + Name: SimilarQueryDistanceFieldName, + Type: &ast.Type{NamedType: "Float"}}) + } + // Define the enum to + //select from among all predicates with "@embedding" directives + enumName := defn.Name + EmbeddingEnumSuffix + enum := &ast.Definition{ + Kind: ast.Enum, + Name: enumName, + } + + for _, fld := range defn.Fields { + if hasEmbeddingDirective(fld) { + enum.EnumValues = append(enum.EnumValues, + &ast.EnumValueDefinition{Name: fld.Name}) + } + } + schema.Types[enumName] = enum + + //Accept the name of embedding field + qry.Arguments = append(qry.Arguments, &ast.ArgumentDefinition{ + Name: SimilarByArgName, + Type: &ast.Type{NamedType: enumName, NonNull: true}, + }) + + // Accept the topK, number of nearest neighbors to + // return + qry.Arguments = append(qry.Arguments, &ast.ArgumentDefinition{ + Name: SimilarTopKArgName, + Type: &ast.Type{ + NamedType: "Int", + NonNull: true, + }, + }) + + // Accept an array of floats as the search vector + qry.Arguments = append(qry.Arguments, &ast.ArgumentDefinition{ + Name: SimilarVectorArgName, + Type: &ast.Type{ + Elem: &ast.Type{ + NamedType: "Float", + NonNull: true, + }, + NonNull: true, + }, + }) + addFilterArgument(schema, qry) + + schema.Query.Fields = append(schema.Query.Fields, qry) +} + +// addSimilarByIdQuery adds a query that looks up a node based on an id/xid. +// The query then performs a similarity search based on the value of the +// selected embedding field to find similar objects +// schema - The graphQL schema. New enums and result type are added to the schema +// defn - The object type for which the query is added +func addSimilarByIdQuery(schema *ast.Schema, defn *ast.Definition, + providesTypeMap map[string]bool) { + hasIDField := hasID(defn) + hasXIDField := hasXID(defn) + xidCount := xidsCount(defn.Fields) + if !hasIDField && !hasXIDField { + return + } + + // create the new query, querySimilarById + qry := &ast.FieldDefinition{ + Name: SimilarQueryPrefix + defn.Name + SimilarByIdQuerySuffix, + Type: &ast.Type{ + Elem: &ast.Type{ + NamedType: defn.Name, + }, + }, + } + + // The new field is "vector_distance". Add it to input Type + if defn.Fields.ForName(SimilarQueryDistanceFieldName) == nil { + defn.Fields = append(defn.Fields, + &ast.FieldDefinition{ + Name: SimilarQueryDistanceFieldName, + Type: &ast.Type{NamedType: "Float"}}) + } + // If the defn, only specified one of ID/XID field, then they are mandatory. + // If it specified both, then they are optional. + if hasIDField { + fields := getIDField(defn, providesTypeMap) + qry.Arguments = append(qry.Arguments, &ast.ArgumentDefinition{ + Name: fields[0].Name, + Type: &ast.Type{ + NamedType: idTypeFor(defn), + NonNull: !hasXIDField, + }, + }) + } + + if hasXIDField { + var idWithoutUniqueArgExists bool + for _, fld := range defn.Fields { + if hasIDDirective(fld) { + if !hasInterfaceArg(fld) { + idWithoutUniqueArgExists = true + } + qry.Arguments = append(qry.Arguments, &ast.ArgumentDefinition{ + Name: fld.Name, + Type: &ast.Type{ + NamedType: fld.Type.Name(), + NonNull: !hasIDField && xidCount <= 1, + }, + }) + } + } + if defn.Kind == "INTERFACE" && idWithoutUniqueArgExists { + qry.Directives = append( + qry.Directives, &ast.Directive{Name: deprecatedDirective, + Arguments: ast.ArgumentList{&ast.Argument{Name: "reason", + Value: &ast.Value{Raw: "@id argument for get query on interface is being" + + " deprecated. Only those @id fields which have interface argument" + + " set to true will be available in getQuery argument on interface" + + " post v21.11.0, please update your schema accordingly.", + Kind: ast.StringValue}}}}) + } + } + + // Define the enum to + //select from among all predicates with "@embedding" directives + enumName := defn.Name + EmbeddingEnumSuffix + enum := &ast.Definition{ + Kind: ast.Enum, + Name: enumName, + } + + for _, fld := range defn.Fields { + if hasEmbeddingDirective(fld) { + enum.EnumValues = append(enum.EnumValues, + &ast.EnumValueDefinition{Name: fld.Name}) + } + } + schema.Types[enumName] = enum + + // Accept the name of the embedding field. + qry.Arguments = append(qry.Arguments, &ast.ArgumentDefinition{ + Name: SimilarByArgName, + Type: &ast.Type{NamedType: enumName, NonNull: true}, + }) + + // Accept the topK, number of nearest neighbors to + // return + qry.Arguments = append(qry.Arguments, &ast.ArgumentDefinition{ + Name: SimilarTopKArgName, + Type: &ast.Type{ + NamedType: "Int", + NonNull: true, + }, + }) + + addFilterArgument(schema, qry) + schema.Query.Fields = append(schema.Query.Fields, qry) +} + func addFilterQuery( schema *ast.Schema, defn *ast.Definition, @@ -2032,7 +2223,8 @@ func addFilterQuery( } -func addAggregationQuery(schema *ast.Schema, defn *ast.Definition, generateSubscription bool) { +func addAggregationQuery(schema *ast.Schema, + defn *ast.Definition, generateSubscription bool) { qry := &ast.FieldDefinition{ Name: "aggregate" + defn.Name, Type: &ast.Type{ @@ -2049,7 +2241,8 @@ func addAggregationQuery(schema *ast.Schema, defn *ast.Definition, generateSubsc } -func addPasswordQuery(schema *ast.Schema, defn *ast.Definition, providesTypeMap map[string]bool) { +func addPasswordQuery(schema *ast.Schema, + defn *ast.Definition, providesTypeMap map[string]bool) { hasIDField := hasID(defn) hasXIDField := hasXID(defn) if !hasIDField && !hasXIDField { @@ -2095,6 +2288,10 @@ func addQueries( ) { if params.generateGetQuery { addGetQuery(schema, defn, providesTypeMap, params.generateSubscription) + if hasEmbedding(defn) { + addSimilarByIdQuery(schema, defn, providesTypeMap) + addSimilarByEmbeddingQuery(schema, defn) + } } if params.generatePasswordQuery { diff --git a/graphql/schema/gqlschema_test.yml b/graphql/schema/gqlschema_test.yml index 64f918f05e7..eb206de22fa 100644 --- a/graphql/schema/gqlschema_test.yml +++ b/graphql/schema/gqlschema_test.yml @@ -77,7 +77,7 @@ invalid_schemas: name: "Enum indexes clash trigram and regexp" input: | type T { - f: E @search(by: [trigram, regexp]) + f: E @search(by: ["trigram", "regexp"]) } enum E { A @@ -91,7 +91,7 @@ invalid_schemas: name: "Enum indexes clash hash and exact" input: | type T { - f: E @search(by: [hash, exact]) + f: E @search(by: ["hash", "exact"]) } enum E { A @@ -100,6 +100,17 @@ invalid_schemas: {"message": "Type T; Field f: the arguments 'hash' and 'exact' can't be used together as arguments to @search.", "locations": [{"line": 2, "column": 9}]} ] + - + name: "HNSW index options malformed" + input: | + type T { + f: [Float!] @embedding @search(by: ["hnsw(metric:dotproduct)"]) + } + errlist: [ + {"message": "Type T; Field f: has the @search directive but the argument 'hnsw(metric:dotproduct)' with search options is malformed. Search options are comma-separated key-value pairs in YAML format => ", + "locations": [{"line": 2, "column": 27}]} + ] + - name: "Reference type that is not in input schema" input: | @@ -466,7 +477,7 @@ invalid_schemas: name: "Search with wrong arg for the index" input: | type X { - y: String @search(by: [hash, hour]) + y: String @search(by: ["hash", "hour"]) } errlist: [ {"message": "Type X; Field y: has the @search directive but the argument hour doesn't @@ -479,11 +490,11 @@ invalid_schemas: name: "Search without []" input: | type X { - y: String @search(by: hash) + y: String @search(by: "hash") } errlist: [ {"message": "Type X; Field y: the @search directive requires a list argument, - like @search(by: [hash])", + like @search(by: [\"hash\"])", "locations":[{"line":2, "column":14}]} ] @@ -491,7 +502,7 @@ invalid_schemas: name: "Search doesn't allow hash and exact together" input: | type X { - y: String @search(by: [hash, exact]) + y: String @search(by: ["hash", "exact"]) } errlist: [ {"message": "Type X; Field y: the arguments 'hash' and 'exact' can't be @@ -503,7 +514,7 @@ invalid_schemas: name: "Search with multiple datetime index" input: | type X { - y: DateTime @search(by: [hour, month]) + y: DateTime @search(by: ["hour", "month"]) } errlist: [ {"message": "Type X; Field y: has the search directive on DateTime. DateTime @@ -515,7 +526,7 @@ invalid_schemas: name: "Search doesn't allow trigram and regexp together" input: | type X { - y: String @search(by: [trigram, regexp]) + y: String @search(by: ["trigram", "regexp"]) } errlist: [ {"message": "Type X; Field y: the argument to @search 'trigram' is the same as @@ -527,7 +538,7 @@ invalid_schemas: name: "Search doesn't accept bogus args" input: | type X { - y: String @search(by: [bogus]) + y: String @search(by: ["bogus"]) } errlist: [ {"message": "Type X; Field y: the argument to @search bogus isn't valid.Fields of type @@ -2732,7 +2743,7 @@ invalid_schemas: review: String! } errlist: [ - {"message": "Type Product; @remote directive cannot be defined with @key directive", "locations": [ { "line": 174, "column": 12} ] }, + {"message": "Type Product; @remote directive cannot be defined with @key directive", "locations": [ { "line": 176, "column": 12} ] }, ] - name: "directives defined on @external fields that are not @key." @@ -2751,6 +2762,29 @@ invalid_schemas: {"message": "Type Product: Field name: @search directive can not be defined on @external fields that are not @key.", "locations": [ { "line": 3, "column": 18} ] }, ] + - name: "@embedding directive on a field with String type" + input: | + type Product { + id: String! @id + description: String + title: String + productVector: String @embedding + } + errlist: [ + {"message": "Type Product; Field productVector: The field with @embedding directive is of type String, but @embedding directive only applies to fields of type [Float!].", "locations": [ { "line": 5, "column": 3} ] }, + ] + + - name: "@embedding directive on a field with [Int] type" + input: | + type User { + email: String! @id + name: String + userVector: [Int] @embedding + } + errlist: [ + {"message": "Type User; Field userVector: The field with @embedding directive is of type [Int], but @embedding directive only applies to fields of type [Float!].", "locations": [ { "line": 4, "column": 3} ] }, + ] + - name: "@requires directive defined on type definitions" input: | type Product @key(fields: "id"){ @@ -3099,8 +3133,8 @@ valid_schemas: strFulltext: String @search(by: [fulltext]) strTrigram: String @search(by: [trigram]) strRegexp: String @search(by: [regexp]) - strRegexpFulltext: String @search(by: [regexp, fulltext]) - strMultipleIndex: String @search(by: [trigram, hash, term, fulltext]) + strRegexpFulltext: String @search(by: ["regexp", "fulltext"]) + strMultipleIndex: String @search(by: ["trigram", "hash", "term", "fulltext"]) dt: DateTime @search dt2: DateTime @search(by: []) dtYear: DateTime @search(by: [year]) @@ -3446,7 +3480,7 @@ valid_schemas: f2: String @dgraph(pred: "T.f@no") name: String! @id f3: String @dgraph(pred: "f3@en") - nameHi: String @dgraph(pred: "Person.name@hi") @search(by: [term, exact]) + nameHi: String @dgraph(pred: "Person.name@hi") @search(by: ["term", "exact"]) nameEn: String @dgraph(pred: "Person.name@en") @search(by: [regexp]) nameHiEn: String @dgraph(pred: "Person.name@hi:en") nameHi_En_Untag: String @dgraph(pred: "Person.name@hi:en:.") diff --git a/graphql/schema/rules.go b/graphql/schema/rules.go index 1f010f91b36..c5799bddd0b 100644 --- a/graphql/schema/rules.go +++ b/graphql/schema/rules.go @@ -29,6 +29,7 @@ import ( "github.com/dgraph-io/gqlparser/v2/gqlerror" "github.com/dgraph-io/gqlparser/v2/parser" "github.com/dgraph-io/gqlparser/v2/validator" + "gopkg.in/yaml.v2" ) const ( @@ -829,6 +830,33 @@ func listValidityCheck(typ *ast.Definition, field *ast.FieldDefinition) gqlerror return nil } +func embeddingValidation(sch *ast.Schema, typ *ast.Definition, + field *ast.FieldDefinition, dir *ast.Directive, + secrets map[string]x.Sensitive) gqlerror.List { + var errs []*gqlerror.Error + if field.Type.Elem == nil { + errs = append(errs, + gqlerror.ErrorPosf( + field.Position, + "Type %s; Field %s: The field with @embedding directive is of type %s,"+ + " but @embedding directive only applies"+ + " to fields of type [Float!].", typ.Name, field.Name, field.Type.Name())) + return errs + } + + if !strings.EqualFold(field.Type.Elem.NamedType, "Float") || + !field.Type.Elem.NonNull { + errs = append(errs, + gqlerror.ErrorPosf( + field.Position, + "Type %s; Field %s: The field with @embedding directive is of type [%s], "+ + "but @embedding directive only applies"+ + " to fields of type [Float!].", typ.Name, field.Name, field.Type.Name())) + } + + return errs +} + func hasInverseValidation(sch *ast.Schema, typ *ast.Definition, field *ast.FieldDefinition, dir *ast.Directive, secrets map[string]x.Sensitive) gqlerror.List { @@ -971,7 +999,8 @@ func validateSearchArg(searchArg string, dir *ast.Directive) *gqlerror.Error { isEnum := sch.Types[field.Type.Name()].Kind == ast.Enum - search, ok := supportedSearches[searchArg] + searchType := parseSearchType(searchArg) + search, ok := supportedSearches[searchType] switch { case !ok: // This check can be removed once gqlparser bug @@ -998,6 +1027,24 @@ func validateSearchArg(searchArg string, "doesn't apply to field type %s which is an Enum. Enum only supports "+ "hash, exact, regexp and trigram", typ.Name, field.Name, searchArg, field.Type.Name()) + + case search.dgIndex == "hnsw": + if !hasEmbeddingDirective(field) { + return gqlerror.ErrorPosf( + dir.Position, + "Type %s; Field %s: has the @search directive but the argument %s "+ + "requires the field also has @%s directive.", + typ.Name, field.Name, searchArg, embeddingDirective) + } + _, valid := getSearchOptions(searchArg) + if !valid { + return gqlerror.ErrorPosf( + dir.Position, + "Type %s; Field %s: has the @search directive but the argument '%s' "+ + "with search options is malformed. Search options are comma-separated "+ + "key-value pairs in YAML format => ", + typ.Name, field.Name, searchArg) + } } return nil @@ -1041,7 +1088,7 @@ func searchValidation( if arg.Value.Kind != ast.ListValue { errs = append(errs, gqlerror.ErrorPosf( dir.Position, - "Type %s; Field %s: the @search directive requires a list argument, like @search(by: [hash])", + "Type %s; Field %s: the @search directive requires a list argument, like @search(by: [\"hash\"])", typ.Name, field.Name)) return errs } @@ -1056,7 +1103,16 @@ func searchValidation( // Checks that the filter indexes aren't repeated and they // don't clash with each other. - searchIndex := builtInFilters[searchArg] + searchType := parseSearchType(searchArg) + searchIndex := builtInFilters[searchType] + if len(searchIndex) == 0 { + errs = append(errs, gqlerror.ErrorPosf( + dir.Position, + "Type %s; Field %s: the argument to @search '%s' is not among "+ + "supported search types.", + typ.Name, field.Name, searchArg)) + return errs + } if val, ok := searchIndexes[searchIndex]; ok { if field.Type.Name() == "String" || sch.Types[field.Type.Name()].Kind == ast.Enum { errs = append(errs, gqlerror.ErrorPosf( @@ -1087,12 +1143,108 @@ func searchValidation( } } - searchIndexes[searchIndex] = searchArg + searchIndexes[searchIndex] = searchType } return errs } +// parseSearchType(searchArg) parses the searchType from searchArg +// searchArg is specified with the following syntax +// +// := [ ] +// +// hnsw(metric: euclidian, exponent: 6) +// hnsw +// hnsw(exponent: 3) +func parseSearchType(searchArg string) string { + searchType := searchArg + if strings.IndexByte(searchArg, '(') >= 0 { + searchType = searchArg[:strings.IndexByte(searchArg, '(')] + searchType = strings.TrimSpace(searchType) + } + return searchType +} + +// parseSearchOptions(searchArg) parses searchOptions from searchArg +// searchArg is specified with the following syntax +// +// := [ ] +// +// searchOptions := * +// := +// Examples: +// +// hnsw(metric: euclidian, exponent: 6) +// hnsw +// hnsw(exponent: 3) +func parseSearchOptions(searchArg string) (map[string]string, bool) { + searchArg = strings.TrimSpace(searchArg) + openParen := strings.Index(searchArg, "(") + + if openParen < 0 && searchArg[len(searchArg)-1] != ')' { + // no search options and supported searchType found + return map[string]string{}, true // valid = true, no search options + } + + if openParen+1 == len(searchArg)-1 { + // found () with no index options between + // '(' & ')' + // TODO: If DQL schema parser allows the pair of parentheses + // without any options then we need to allow this in GraphQL + // schema too + return map[string]string{}, false + } + + if openParen < 0 || searchArg[len(searchArg)-1] != ')' { + // does not have open/close parenthesis + return map[string]string{}, false // valid = false + } + + indexOptions := "{" + searchArg[openParen+1:len(searchArg)-1] + "}" + var kvMap map[string]string + err := yaml.Unmarshal([]byte(indexOptions), &kvMap) + if err != nil { + return map[string]string{}, false + } + + return kvMap, true // parsed valid options +} + +// getSearchOptions(searchArg) Stringifies search options using DQL syntax +func getSearchOptions(searchArg string) (string, bool) { + res := "" + kvMap, ok := parseSearchOptions(searchArg) + if len(kvMap) == 0 { + return res, ok + } + + keys := make([]string, 0, len(kvMap)) + for k := range kvMap { + keys = append(keys, k) + } + + sort.Strings(keys) + + res += "(" + i := 0 + for _, key := range keys { + if len(kvMap[key]) == 0 { + // If the value is null, then return invalid + return "", false + } + res += strings.TrimSpace(key) + ": \"" + + strings.TrimSpace(kvMap[key]) + "\"" + if i < len(keys)-1 { + res += ", " + } + i++ + } + res += ")" + + return res, true // parsed valid options +} + func dgraphDirectiveValidation(sch *ast.Schema, typ *ast.Definition, field *ast.FieldDefinition, dir *ast.Directive, secrets map[string]x.Sensitive) gqlerror.List { var errs []*gqlerror.Error diff --git a/graphql/schema/schemagen.go b/graphql/schema/schemagen.go index 76b9a30fd96..068e808db27 100644 --- a/graphql/schema/schemagen.go +++ b/graphql/schema/schemagen.go @@ -472,10 +472,14 @@ func getAllowedHeaders(sch *ast.Schema, definitions []string, authHeader string) } func getAllSearchIndexes(val *ast.Value) []string { + // all searchArgs were validated before getting here. res := make([]string, len(val.Children)) for i, child := range val.Children { - res[i] = supportedSearches[child.Value.Raw].dgIndex + searchType := parseSearchType(child.Value.Raw) + res[i] = supportedSearches[searchType].dgIndex + searchOptions, _ := getSearchOptions(child.Value.Raw) + res[i] += searchOptions } return res @@ -669,6 +673,13 @@ func genDgSchema(gqlSch *ast.Schema, definitions []string, } } + embedding := f.Directives.ForName(embeddingDirective) + if embedding != nil { + // embeddingValidation ensured GQL type is [Float] + // set typStr to float32vector + typStr = "float32vector" + } + if parentInt == nil { // if field name contains @ then it is a language tagged field. isLang := false diff --git a/graphql/schema/testdata/apolloservice/input/generate-directive.graphql b/graphql/schema/testdata/apolloservice/input/generate-directive.graphql index 0621754ede9..d45f95ace3e 100644 --- a/graphql/schema/testdata/apolloservice/input/generate-directive.graphql +++ b/graphql/schema/testdata/apolloservice/input/generate-directive.graphql @@ -34,4 +34,4 @@ type Person @withSubscription @generate( ) { id: ID! name: String! -} \ No newline at end of file +} diff --git a/graphql/schema/testdata/apolloservice/output/auth-directive.graphql b/graphql/schema/testdata/apolloservice/output/auth-directive.graphql index d11d8dd6f8b..4c7c8697b62 100644 --- a/graphql/schema/testdata/apolloservice/output/auth-directive.graphql +++ b/graphql/schema/testdata/apolloservice/output/auth-directive.graphql @@ -77,6 +77,7 @@ enum DgraphIndex { day hour geo + hnsw } input AuthRule { @@ -190,7 +191,8 @@ input GenerateMutationParams { } directive @hasInverse(field: String!) on FIELD_DEFINITION -directive @search(by: [DgraphIndex!]) on FIELD_DEFINITION +directive @search(by: [String!]) on FIELD_DEFINITION +directive @embedding on FIELD_DEFINITION directive @dgraph(type: String, pred: String) on OBJECT | INTERFACE | FIELD_DEFINITION directive @id(interface: Boolean) on FIELD_DEFINITION directive @withSubscription on OBJECT | INTERFACE | FIELD_DEFINITION diff --git a/graphql/schema/testdata/apolloservice/output/custom-directive.graphql b/graphql/schema/testdata/apolloservice/output/custom-directive.graphql index 0ffe521b151..2895f84b1a9 100644 --- a/graphql/schema/testdata/apolloservice/output/custom-directive.graphql +++ b/graphql/schema/testdata/apolloservice/output/custom-directive.graphql @@ -69,6 +69,7 @@ enum DgraphIndex { day hour geo + hnsw } input AuthRule { @@ -182,7 +183,8 @@ input GenerateMutationParams { } directive @hasInverse(field: String!) on FIELD_DEFINITION -directive @search(by: [DgraphIndex!]) on FIELD_DEFINITION +directive @search(by: [String!]) on FIELD_DEFINITION +directive @embedding on FIELD_DEFINITION directive @dgraph(type: String, pred: String) on OBJECT | INTERFACE | FIELD_DEFINITION directive @id(interface: Boolean) on FIELD_DEFINITION directive @withSubscription on OBJECT | INTERFACE | FIELD_DEFINITION diff --git a/graphql/schema/testdata/apolloservice/output/extended-types.graphql b/graphql/schema/testdata/apolloservice/output/extended-types.graphql index 4c2a6ab110b..5cfe4020f15 100644 --- a/graphql/schema/testdata/apolloservice/output/extended-types.graphql +++ b/graphql/schema/testdata/apolloservice/output/extended-types.graphql @@ -83,6 +83,7 @@ enum DgraphIndex { day hour geo + hnsw } input AuthRule { @@ -196,7 +197,8 @@ input GenerateMutationParams { } directive @hasInverse(field: String!) on FIELD_DEFINITION -directive @search(by: [DgraphIndex!]) on FIELD_DEFINITION +directive @search(by: [String!]) on FIELD_DEFINITION +directive @embedding on FIELD_DEFINITION directive @dgraph(type: String, pred: String) on OBJECT | INTERFACE | FIELD_DEFINITION directive @id(interface: Boolean) on FIELD_DEFINITION directive @withSubscription on OBJECT | INTERFACE | FIELD_DEFINITION diff --git a/graphql/schema/testdata/apolloservice/output/generate-directive.graphql b/graphql/schema/testdata/apolloservice/output/generate-directive.graphql index fd38dab07b9..e3acf38139c 100644 --- a/graphql/schema/testdata/apolloservice/output/generate-directive.graphql +++ b/graphql/schema/testdata/apolloservice/output/generate-directive.graphql @@ -79,6 +79,7 @@ enum DgraphIndex { day hour geo + hnsw } input AuthRule { @@ -192,7 +193,8 @@ input GenerateMutationParams { } directive @hasInverse(field: String!) on FIELD_DEFINITION -directive @search(by: [DgraphIndex!]) on FIELD_DEFINITION +directive @search(by: [String!]) on FIELD_DEFINITION +directive @embedding on FIELD_DEFINITION directive @dgraph(type: String, pred: String) on OBJECT | INTERFACE | FIELD_DEFINITION directive @id(interface: Boolean) on FIELD_DEFINITION directive @withSubscription on OBJECT | INTERFACE | FIELD_DEFINITION diff --git a/graphql/schema/testdata/apolloservice/output/single-extended-type.graphql b/graphql/schema/testdata/apolloservice/output/single-extended-type.graphql index 94acbbcc7a3..3a375fac52e 100644 --- a/graphql/schema/testdata/apolloservice/output/single-extended-type.graphql +++ b/graphql/schema/testdata/apolloservice/output/single-extended-type.graphql @@ -64,6 +64,7 @@ enum DgraphIndex { day hour geo + hnsw } input AuthRule { @@ -177,7 +178,8 @@ input GenerateMutationParams { } directive @hasInverse(field: String!) on FIELD_DEFINITION -directive @search(by: [DgraphIndex!]) on FIELD_DEFINITION +directive @search(by: [String!]) on FIELD_DEFINITION +directive @embedding on FIELD_DEFINITION directive @dgraph(type: String, pred: String) on OBJECT | INTERFACE | FIELD_DEFINITION directive @id(interface: Boolean) on FIELD_DEFINITION directive @withSubscription on OBJECT | INTERFACE | FIELD_DEFINITION diff --git a/graphql/schema/testdata/schemagen/input/auth-on-interfaces.graphql b/graphql/schema/testdata/schemagen/input/auth-on-interfaces.graphql index 2a50a0f32bf..6a8812cc6fe 100644 --- a/graphql/schema/testdata/schemagen/input/auth-on-interfaces.graphql +++ b/graphql/schema/testdata/schemagen/input/auth-on-interfaces.graphql @@ -28,4 +28,4 @@ type Question implements Post @auth( }""" } ){ answered: Boolean @search -} \ No newline at end of file +} diff --git a/graphql/schema/testdata/schemagen/input/embedding-directive-with-similar-queries.graphql b/graphql/schema/testdata/schemagen/input/embedding-directive-with-similar-queries.graphql new file mode 100644 index 00000000000..c3c7f33cd9c --- /dev/null +++ b/graphql/schema/testdata/schemagen/input/embedding-directive-with-similar-queries.graphql @@ -0,0 +1,21 @@ +# simple user product GraphQL API - v0.4 + +type Product { + id: String! @id + description: String + title: String + imageUrl: String + product_vector: [Float!] @embedding @search(by: ["hnsw(metric: euclidian, exponent: 4)"]) +} + +type Purchase @lambdaOnMutate(add: true){ + user: User @hasInverse(field: "purchase_history") + product: Product + date: DateTime @search(by: [day]) +} + +type User { + email: String! @id + purchase_history: [Purchase] + user_vector: [Float!] @embedding @search(by: ["hnsw"]) +} diff --git a/graphql/schema/testdata/schemagen/input/language-tags.graphql b/graphql/schema/testdata/schemagen/input/language-tags.graphql index 97257697b46..0d6633669a9 100644 --- a/graphql/schema/testdata/schemagen/input/language-tags.graphql +++ b/graphql/schema/testdata/schemagen/input/language-tags.graphql @@ -12,7 +12,7 @@ type Person implements Node { f3: String @dgraph(pred: "f3@en") name: String! @id # We can have exact index on language tagged field while having hash index on language untagged field - nameHi: String @dgraph(pred: "Person.name@hi") @search(by: [term, exact]) + nameHi: String @dgraph(pred: "Person.name@hi") @search(by: ["term", "exact"]) nameEn: String @dgraph(pred: "Person.name@en") @search(by: [regexp]) # Below Fields nameHiEn,nameHi_En_Untag won't be added to update/add mutation/ref type # and also to filters, order as they corresponds to multiple language tags diff --git a/graphql/schema/testdata/schemagen/input/searchables-references.graphql b/graphql/schema/testdata/schemagen/input/searchables-references.graphql index fbac2b2cfee..d69dad886ce 100644 --- a/graphql/schema/testdata/schemagen/input/searchables-references.graphql +++ b/graphql/schema/testdata/schemagen/input/searchables-references.graphql @@ -7,7 +7,7 @@ type Author { type Post { postID: ID! - title: String! @search(by: [term, fulltext]) - text: String @search(by: [fulltext, term]) + title: String! @search(by: ["term", "fulltext"]) + text: String @search(by: ["fulltext", "term"]) datePublished: DateTime # Have something not search } diff --git a/graphql/schema/testdata/schemagen/input/searchables.graphql b/graphql/schema/testdata/schemagen/input/searchables.graphql index bc704585127..3690268b843 100644 --- a/graphql/schema/testdata/schemagen/input/searchables.graphql +++ b/graphql/schema/testdata/schemagen/input/searchables.graphql @@ -1,7 +1,7 @@ type Post { postID: ID! title: String! @search(by: [term]) - titleByEverything: String! @search(by: [term, fulltext, trigram, hash]) + titleByEverything: String! @search(by: ["term", "fulltext", "trigram", "hash"]) text: String @search(by: [fulltext]) tags: [String] @search(by: [trigram]) @@ -26,8 +26,8 @@ type Post { postTypeRegexp: PostType @search(by: [regexp]) postTypeExact: [PostType] @search(by: [exact]) postTypeHash: PostType @search(by: [hash]) - postTypeRegexpExact: PostType @search(by: [exact, regexp]) - postTypeHashRegexp: PostType @search(by: [hash, regexp]) + postTypeRegexpExact: PostType @search(by: ["exact", "regexp"]) + postTypeHashRegexp: PostType @search(by: ["hash", "regexp"]) postTypeNone: PostType @search(by: []) } diff --git a/graphql/schema/testdata/schemagen/input/union.graphql b/graphql/schema/testdata/schemagen/input/union.graphql index ee36d9f3cc7..d18f80cd9e1 100644 --- a/graphql/schema/testdata/schemagen/input/union.graphql +++ b/graphql/schema/testdata/schemagen/input/union.graphql @@ -38,4 +38,4 @@ type Planet { url: "http://mock:8888/tool/$id" method: "GET" }) -} \ No newline at end of file +} diff --git a/graphql/schema/testdata/schemagen/output/apollo-federation.graphql b/graphql/schema/testdata/schemagen/output/apollo-federation.graphql index 6e2ce105791..58e3caa0114 100644 --- a/graphql/schema/testdata/schemagen/output/apollo-federation.graphql +++ b/graphql/schema/testdata/schemagen/output/apollo-federation.graphql @@ -99,6 +99,7 @@ enum DgraphIndex { day hour geo + hnsw } input AuthRule { @@ -212,7 +213,8 @@ input GenerateMutationParams { } directive @hasInverse(field: String!) on FIELD_DEFINITION -directive @search(by: [DgraphIndex!]) on FIELD_DEFINITION +directive @search(by: [String!]) on FIELD_DEFINITION +directive @embedding on FIELD_DEFINITION directive @dgraph(type: String, pred: String) on OBJECT | INTERFACE | FIELD_DEFINITION directive @id(interface: Boolean) on FIELD_DEFINITION directive @withSubscription on OBJECT | INTERFACE | FIELD_DEFINITION diff --git a/graphql/schema/testdata/schemagen/output/auth-on-interfaces.graphql b/graphql/schema/testdata/schemagen/output/auth-on-interfaces.graphql index ea87104b74f..25fe5454258 100644 --- a/graphql/schema/testdata/schemagen/output/auth-on-interfaces.graphql +++ b/graphql/schema/testdata/schemagen/output/auth-on-interfaces.graphql @@ -81,6 +81,7 @@ enum DgraphIndex { day hour geo + hnsw } input AuthRule { @@ -194,7 +195,8 @@ input GenerateMutationParams { } directive @hasInverse(field: String!) on FIELD_DEFINITION -directive @search(by: [DgraphIndex!]) on FIELD_DEFINITION +directive @search(by: [String!]) on FIELD_DEFINITION +directive @embedding on FIELD_DEFINITION directive @dgraph(type: String, pred: String) on OBJECT | INTERFACE | FIELD_DEFINITION directive @id(interface: Boolean) on FIELD_DEFINITION directive @withSubscription on OBJECT | INTERFACE | FIELD_DEFINITION diff --git a/graphql/schema/testdata/schemagen/output/authorization.graphql b/graphql/schema/testdata/schemagen/output/authorization.graphql index b04bf479f80..4154417f6bf 100644 --- a/graphql/schema/testdata/schemagen/output/authorization.graphql +++ b/graphql/schema/testdata/schemagen/output/authorization.graphql @@ -77,6 +77,7 @@ enum DgraphIndex { day hour geo + hnsw } input AuthRule { @@ -190,7 +191,8 @@ input GenerateMutationParams { } directive @hasInverse(field: String!) on FIELD_DEFINITION -directive @search(by: [DgraphIndex!]) on FIELD_DEFINITION +directive @search(by: [String!]) on FIELD_DEFINITION +directive @embedding on FIELD_DEFINITION directive @dgraph(type: String, pred: String) on OBJECT | INTERFACE | FIELD_DEFINITION directive @id(interface: Boolean) on FIELD_DEFINITION directive @withSubscription on OBJECT | INTERFACE | FIELD_DEFINITION diff --git a/graphql/schema/testdata/schemagen/output/comments-and-descriptions.graphql b/graphql/schema/testdata/schemagen/output/comments-and-descriptions.graphql old mode 100755 new mode 100644 index cf11ecc3856..5cfbc133bff --- a/graphql/schema/testdata/schemagen/output/comments-and-descriptions.graphql +++ b/graphql/schema/testdata/schemagen/output/comments-and-descriptions.graphql @@ -90,6 +90,7 @@ enum DgraphIndex { day hour geo + hnsw } input AuthRule { @@ -203,7 +204,8 @@ input GenerateMutationParams { } directive @hasInverse(field: String!) on FIELD_DEFINITION -directive @search(by: [DgraphIndex!]) on FIELD_DEFINITION +directive @search(by: [String!]) on FIELD_DEFINITION +directive @embedding on FIELD_DEFINITION directive @dgraph(type: String, pred: String) on OBJECT | INTERFACE | FIELD_DEFINITION directive @id(interface: Boolean) on FIELD_DEFINITION directive @withSubscription on OBJECT | INTERFACE | FIELD_DEFINITION diff --git a/graphql/schema/testdata/schemagen/output/custom-dql-query-with-subscription.graphql b/graphql/schema/testdata/schemagen/output/custom-dql-query-with-subscription.graphql old mode 100755 new mode 100644 index 99d4d850e10..42d1b5e8735 --- a/graphql/schema/testdata/schemagen/output/custom-dql-query-with-subscription.graphql +++ b/graphql/schema/testdata/schemagen/output/custom-dql-query-with-subscription.graphql @@ -78,6 +78,7 @@ enum DgraphIndex { day hour geo + hnsw } input AuthRule { @@ -191,7 +192,8 @@ input GenerateMutationParams { } directive @hasInverse(field: String!) on FIELD_DEFINITION -directive @search(by: [DgraphIndex!]) on FIELD_DEFINITION +directive @search(by: [String!]) on FIELD_DEFINITION +directive @embedding on FIELD_DEFINITION directive @dgraph(type: String, pred: String) on OBJECT | INTERFACE | FIELD_DEFINITION directive @id(interface: Boolean) on FIELD_DEFINITION directive @withSubscription on OBJECT | INTERFACE | FIELD_DEFINITION diff --git a/graphql/schema/testdata/schemagen/output/custom-mutation.graphql b/graphql/schema/testdata/schemagen/output/custom-mutation.graphql index 1b48735de16..b06c8052f6d 100644 --- a/graphql/schema/testdata/schemagen/output/custom-mutation.graphql +++ b/graphql/schema/testdata/schemagen/output/custom-mutation.graphql @@ -68,6 +68,7 @@ enum DgraphIndex { day hour geo + hnsw } input AuthRule { @@ -181,7 +182,8 @@ input GenerateMutationParams { } directive @hasInverse(field: String!) on FIELD_DEFINITION -directive @search(by: [DgraphIndex!]) on FIELD_DEFINITION +directive @search(by: [String!]) on FIELD_DEFINITION +directive @embedding on FIELD_DEFINITION directive @dgraph(type: String, pred: String) on OBJECT | INTERFACE | FIELD_DEFINITION directive @id(interface: Boolean) on FIELD_DEFINITION directive @withSubscription on OBJECT | INTERFACE | FIELD_DEFINITION diff --git a/graphql/schema/testdata/schemagen/output/custom-nested-types.graphql b/graphql/schema/testdata/schemagen/output/custom-nested-types.graphql old mode 100755 new mode 100644 index 7184d90e5a8..ba46ead1487 --- a/graphql/schema/testdata/schemagen/output/custom-nested-types.graphql +++ b/graphql/schema/testdata/schemagen/output/custom-nested-types.graphql @@ -85,6 +85,7 @@ enum DgraphIndex { day hour geo + hnsw } input AuthRule { @@ -198,7 +199,8 @@ input GenerateMutationParams { } directive @hasInverse(field: String!) on FIELD_DEFINITION -directive @search(by: [DgraphIndex!]) on FIELD_DEFINITION +directive @search(by: [String!]) on FIELD_DEFINITION +directive @embedding on FIELD_DEFINITION directive @dgraph(type: String, pred: String) on OBJECT | INTERFACE | FIELD_DEFINITION directive @id(interface: Boolean) on FIELD_DEFINITION directive @withSubscription on OBJECT | INTERFACE | FIELD_DEFINITION diff --git a/graphql/schema/testdata/schemagen/output/custom-query-mixed-types.graphql b/graphql/schema/testdata/schemagen/output/custom-query-mixed-types.graphql index d441721cf27..689f77789a8 100644 --- a/graphql/schema/testdata/schemagen/output/custom-query-mixed-types.graphql +++ b/graphql/schema/testdata/schemagen/output/custom-query-mixed-types.graphql @@ -69,6 +69,7 @@ enum DgraphIndex { day hour geo + hnsw } input AuthRule { @@ -182,7 +183,8 @@ input GenerateMutationParams { } directive @hasInverse(field: String!) on FIELD_DEFINITION -directive @search(by: [DgraphIndex!]) on FIELD_DEFINITION +directive @search(by: [String!]) on FIELD_DEFINITION +directive @embedding on FIELD_DEFINITION directive @dgraph(type: String, pred: String) on OBJECT | INTERFACE | FIELD_DEFINITION directive @id(interface: Boolean) on FIELD_DEFINITION directive @withSubscription on OBJECT | INTERFACE | FIELD_DEFINITION diff --git a/graphql/schema/testdata/schemagen/output/custom-query-not-dgraph-type.graphql b/graphql/schema/testdata/schemagen/output/custom-query-not-dgraph-type.graphql old mode 100755 new mode 100644 index bbc666a87e1..69eff1c9bc2 --- a/graphql/schema/testdata/schemagen/output/custom-query-not-dgraph-type.graphql +++ b/graphql/schema/testdata/schemagen/output/custom-query-not-dgraph-type.graphql @@ -68,6 +68,7 @@ enum DgraphIndex { day hour geo + hnsw } input AuthRule { @@ -181,7 +182,8 @@ input GenerateMutationParams { } directive @hasInverse(field: String!) on FIELD_DEFINITION -directive @search(by: [DgraphIndex!]) on FIELD_DEFINITION +directive @search(by: [String!]) on FIELD_DEFINITION +directive @embedding on FIELD_DEFINITION directive @dgraph(type: String, pred: String) on OBJECT | INTERFACE | FIELD_DEFINITION directive @id(interface: Boolean) on FIELD_DEFINITION directive @withSubscription on OBJECT | INTERFACE | FIELD_DEFINITION diff --git a/graphql/schema/testdata/schemagen/output/custom-query-with-dgraph-type.graphql b/graphql/schema/testdata/schemagen/output/custom-query-with-dgraph-type.graphql old mode 100755 new mode 100644 index a38c50561d8..34959d52171 --- a/graphql/schema/testdata/schemagen/output/custom-query-with-dgraph-type.graphql +++ b/graphql/schema/testdata/schemagen/output/custom-query-with-dgraph-type.graphql @@ -64,6 +64,7 @@ enum DgraphIndex { day hour geo + hnsw } input AuthRule { @@ -177,7 +178,8 @@ input GenerateMutationParams { } directive @hasInverse(field: String!) on FIELD_DEFINITION -directive @search(by: [DgraphIndex!]) on FIELD_DEFINITION +directive @search(by: [String!]) on FIELD_DEFINITION +directive @embedding on FIELD_DEFINITION directive @dgraph(type: String, pred: String) on OBJECT | INTERFACE | FIELD_DEFINITION directive @id(interface: Boolean) on FIELD_DEFINITION directive @withSubscription on OBJECT | INTERFACE | FIELD_DEFINITION diff --git a/graphql/schema/testdata/schemagen/output/deprecated.graphql b/graphql/schema/testdata/schemagen/output/deprecated.graphql old mode 100755 new mode 100644 index 52cb4ea15d4..a44e32bd30d --- a/graphql/schema/testdata/schemagen/output/deprecated.graphql +++ b/graphql/schema/testdata/schemagen/output/deprecated.graphql @@ -64,6 +64,7 @@ enum DgraphIndex { day hour geo + hnsw } input AuthRule { @@ -177,7 +178,8 @@ input GenerateMutationParams { } directive @hasInverse(field: String!) on FIELD_DEFINITION -directive @search(by: [DgraphIndex!]) on FIELD_DEFINITION +directive @search(by: [String!]) on FIELD_DEFINITION +directive @embedding on FIELD_DEFINITION directive @dgraph(type: String, pred: String) on OBJECT | INTERFACE | FIELD_DEFINITION directive @id(interface: Boolean) on FIELD_DEFINITION directive @withSubscription on OBJECT | INTERFACE | FIELD_DEFINITION diff --git a/graphql/schema/testdata/schemagen/output/dgraph-reverse-directive-on-concrete-type-with-interfaces.graphql b/graphql/schema/testdata/schemagen/output/dgraph-reverse-directive-on-concrete-type-with-interfaces.graphql old mode 100755 new mode 100644 index b1af0955121..43cb363af0e --- a/graphql/schema/testdata/schemagen/output/dgraph-reverse-directive-on-concrete-type-with-interfaces.graphql +++ b/graphql/schema/testdata/schemagen/output/dgraph-reverse-directive-on-concrete-type-with-interfaces.graphql @@ -81,6 +81,7 @@ enum DgraphIndex { day hour geo + hnsw } input AuthRule { @@ -194,7 +195,8 @@ input GenerateMutationParams { } directive @hasInverse(field: String!) on FIELD_DEFINITION -directive @search(by: [DgraphIndex!]) on FIELD_DEFINITION +directive @search(by: [String!]) on FIELD_DEFINITION +directive @embedding on FIELD_DEFINITION directive @dgraph(type: String, pred: String) on OBJECT | INTERFACE | FIELD_DEFINITION directive @id(interface: Boolean) on FIELD_DEFINITION directive @withSubscription on OBJECT | INTERFACE | FIELD_DEFINITION diff --git a/graphql/schema/testdata/schemagen/output/dgraph-reverse-directive-with-interfaces.graphql b/graphql/schema/testdata/schemagen/output/dgraph-reverse-directive-with-interfaces.graphql old mode 100755 new mode 100644 index 44da4eec782..40219cc45be --- a/graphql/schema/testdata/schemagen/output/dgraph-reverse-directive-with-interfaces.graphql +++ b/graphql/schema/testdata/schemagen/output/dgraph-reverse-directive-with-interfaces.graphql @@ -81,6 +81,7 @@ enum DgraphIndex { day hour geo + hnsw } input AuthRule { @@ -194,7 +195,8 @@ input GenerateMutationParams { } directive @hasInverse(field: String!) on FIELD_DEFINITION -directive @search(by: [DgraphIndex!]) on FIELD_DEFINITION +directive @search(by: [String!]) on FIELD_DEFINITION +directive @embedding on FIELD_DEFINITION directive @dgraph(type: String, pred: String) on OBJECT | INTERFACE | FIELD_DEFINITION directive @id(interface: Boolean) on FIELD_DEFINITION directive @withSubscription on OBJECT | INTERFACE | FIELD_DEFINITION diff --git a/graphql/schema/testdata/schemagen/output/embedding-directive-with-similar-queries.graphql b/graphql/schema/testdata/schemagen/output/embedding-directive-with-similar-queries.graphql new file mode 100644 index 00000000000..77d52a9e96a --- /dev/null +++ b/graphql/schema/testdata/schemagen/output/embedding-directive-with-similar-queries.graphql @@ -0,0 +1,575 @@ +####################### +# Input Schema +####################### + +type Product { + id: String! @id + description: String + title: String + imageUrl: String + product_vector: [Float!] @embedding @search(by: ["hnsw(metric: euclidian, exponent: 4)"]) + vector_distance: Float +} + +type Purchase @lambdaOnMutate(add: true) { + user(filter: UserFilter): User @hasInverse(field: "purchase_history") + product(filter: ProductFilter): Product + date: DateTime @search(by: [day]) +} + +type User { + email: String! @id + purchase_history(filter: PurchaseFilter, order: PurchaseOrder, first: Int, offset: Int): [Purchase] @hasInverse(field: user) + user_vector: [Float!] @embedding @search(by: ["hnsw"]) + vector_distance: Float + purchase_historyAggregate(filter: PurchaseFilter): PurchaseAggregateResult +} + +####################### +# Extended Definitions +####################### + +""" +The Int64 scalar type represents a signed 64‐bit numeric non‐fractional value. +Int64 can represent values in range [-(2^63),(2^63 - 1)]. +""" +scalar Int64 + +""" +The DateTime scalar type represents date and time as a string in RFC3339 format. +For example: "1985-04-12T23:20:50.52Z" represents 20 mins 50.52 secs after the 23rd hour of Apr 12th 1985 in UTC. +""" +scalar DateTime + +input IntRange{ + min: Int! + max: Int! +} + +input FloatRange{ + min: Float! + max: Float! +} + +input Int64Range{ + min: Int64! + max: Int64! +} + +input DateTimeRange{ + min: DateTime! + max: DateTime! +} + +input StringRange{ + min: String! + max: String! +} + +enum DgraphIndex { + int + int64 + float + bool + hash + exact + term + fulltext + trigram + regexp + year + month + day + hour + geo + hnsw +} + +input AuthRule { + and: [AuthRule] + or: [AuthRule] + not: AuthRule + rule: String +} + +enum HTTPMethod { + GET + POST + PUT + PATCH + DELETE +} + +enum Mode { + BATCH + SINGLE +} + +input CustomHTTP { + url: String! + method: HTTPMethod! + body: String + graphql: String + mode: Mode + forwardHeaders: [String!] + secretHeaders: [String!] + introspectionHeaders: [String!] + skipIntrospection: Boolean +} + +type Point { + longitude: Float! + latitude: Float! +} + +input PointRef { + longitude: Float! + latitude: Float! +} + +input NearFilter { + distance: Float! + coordinate: PointRef! +} + +input PointGeoFilter { + near: NearFilter + within: WithinFilter +} + +type PointList { + points: [Point!]! +} + +input PointListRef { + points: [PointRef!]! +} + +type Polygon { + coordinates: [PointList!]! +} + +input PolygonRef { + coordinates: [PointListRef!]! +} + +type MultiPolygon { + polygons: [Polygon!]! +} + +input MultiPolygonRef { + polygons: [PolygonRef!]! +} + +input WithinFilter { + polygon: PolygonRef! +} + +input ContainsFilter { + point: PointRef + polygon: PolygonRef +} + +input IntersectsFilter { + polygon: PolygonRef + multiPolygon: MultiPolygonRef +} + +input PolygonGeoFilter { + near: NearFilter + within: WithinFilter + contains: ContainsFilter + intersects: IntersectsFilter +} + +input GenerateQueryParams { + get: Boolean + query: Boolean + password: Boolean + aggregate: Boolean +} + +input GenerateMutationParams { + add: Boolean + update: Boolean + delete: Boolean +} + +directive @hasInverse(field: String!) on FIELD_DEFINITION +directive @search(by: [String!]) on FIELD_DEFINITION +directive @embedding on FIELD_DEFINITION +directive @dgraph(type: String, pred: String) on OBJECT | INTERFACE | FIELD_DEFINITION +directive @id(interface: Boolean) on FIELD_DEFINITION +directive @withSubscription on OBJECT | INTERFACE | FIELD_DEFINITION +directive @secret(field: String!, pred: String) on OBJECT | INTERFACE +directive @auth( + password: AuthRule + query: AuthRule, + add: AuthRule, + update: AuthRule, + delete: AuthRule) on OBJECT | INTERFACE +directive @custom(http: CustomHTTP, dql: String) on FIELD_DEFINITION +directive @remote on OBJECT | INTERFACE | UNION | INPUT_OBJECT | ENUM +directive @remoteResponse(name: String) on FIELD_DEFINITION +directive @cascade(fields: [String]) on FIELD +directive @lambda on FIELD_DEFINITION +directive @lambdaOnMutate(add: Boolean, update: Boolean, delete: Boolean) on OBJECT | INTERFACE +directive @cacheControl(maxAge: Int!) on QUERY +directive @generate( + query: GenerateQueryParams, + mutation: GenerateMutationParams, + subscription: Boolean) on OBJECT | INTERFACE + +input IntFilter { + eq: Int + in: [Int] + le: Int + lt: Int + ge: Int + gt: Int + between: IntRange +} + +input Int64Filter { + eq: Int64 + in: [Int64] + le: Int64 + lt: Int64 + ge: Int64 + gt: Int64 + between: Int64Range +} + +input FloatFilter { + eq: Float + in: [Float] + le: Float + lt: Float + ge: Float + gt: Float + between: FloatRange +} + +input DateTimeFilter { + eq: DateTime + in: [DateTime] + le: DateTime + lt: DateTime + ge: DateTime + gt: DateTime + between: DateTimeRange +} + +input StringTermFilter { + allofterms: String + anyofterms: String +} + +input StringRegExpFilter { + regexp: String +} + +input StringFullTextFilter { + alloftext: String + anyoftext: String +} + +input StringExactFilter { + eq: String + in: [String] + le: String + lt: String + ge: String + gt: String + between: StringRange +} + +input StringHashFilter { + eq: String + in: [String] +} + +####################### +# Generated Types +####################### + +type AddProductPayload { + product(filter: ProductFilter, order: ProductOrder, first: Int, offset: Int): [Product] + numUids: Int +} + +type AddPurchasePayload { + purchase(filter: PurchaseFilter, order: PurchaseOrder, first: Int, offset: Int): [Purchase] + numUids: Int +} + +type AddUserPayload { + user(filter: UserFilter, order: UserOrder, first: Int, offset: Int): [User] + numUids: Int +} + +type DeleteProductPayload { + product(filter: ProductFilter, order: ProductOrder, first: Int, offset: Int): [Product] + msg: String + numUids: Int +} + +type DeletePurchasePayload { + purchase(filter: PurchaseFilter, order: PurchaseOrder, first: Int, offset: Int): [Purchase] + msg: String + numUids: Int +} + +type DeleteUserPayload { + user(filter: UserFilter, order: UserOrder, first: Int, offset: Int): [User] + msg: String + numUids: Int +} + +type ProductAggregateResult { + count: Int + idMin: String + idMax: String + descriptionMin: String + descriptionMax: String + titleMin: String + titleMax: String + imageUrlMin: String + imageUrlMax: String +} + +type PurchaseAggregateResult { + count: Int + dateMin: DateTime + dateMax: DateTime +} + +type UpdateProductPayload { + product(filter: ProductFilter, order: ProductOrder, first: Int, offset: Int): [Product] + numUids: Int +} + +type UpdatePurchasePayload { + purchase(filter: PurchaseFilter, order: PurchaseOrder, first: Int, offset: Int): [Purchase] + numUids: Int +} + +type UpdateUserPayload { + user(filter: UserFilter, order: UserOrder, first: Int, offset: Int): [User] + numUids: Int +} + +type UserAggregateResult { + count: Int + emailMin: String + emailMax: String +} + +####################### +# Generated Enums +####################### + +enum ProductEmbedding { + product_vector +} + +enum ProductHasFilter { + id + description + title + imageUrl + product_vector + vector_distance +} + +enum ProductOrderable { + id + description + title + imageUrl +} + +enum PurchaseHasFilter { + user + product + date +} + +enum PurchaseOrderable { + date +} + +enum UserEmbedding { + user_vector +} + +enum UserHasFilter { + email + purchase_history + user_vector + vector_distance +} + +enum UserOrderable { + email +} + +####################### +# Generated Inputs +####################### + +input AddProductInput { + id: String! + description: String + title: String + imageUrl: String + product_vector: [Float!] +} + +input AddPurchaseInput { + user: UserRef + product: ProductRef + date: DateTime +} + +input AddUserInput { + email: String! + purchase_history: [PurchaseRef] + user_vector: [Float!] +} + +input ProductFilter { + id: StringHashFilter + has: [ProductHasFilter] + and: [ProductFilter] + or: [ProductFilter] + not: ProductFilter +} + +input ProductOrder { + asc: ProductOrderable + desc: ProductOrderable + then: ProductOrder +} + +input ProductPatch { + id: String + description: String + title: String + imageUrl: String + product_vector: [Float!] +} + +input ProductRef { + id: String + description: String + title: String + imageUrl: String + product_vector: [Float!] +} + +input PurchaseFilter { + date: DateTimeFilter + has: [PurchaseHasFilter] + and: [PurchaseFilter] + or: [PurchaseFilter] + not: PurchaseFilter +} + +input PurchaseOrder { + asc: PurchaseOrderable + desc: PurchaseOrderable + then: PurchaseOrder +} + +input PurchasePatch { + user: UserRef + product: ProductRef + date: DateTime +} + +input PurchaseRef { + user: UserRef + product: ProductRef + date: DateTime +} + +input UpdateProductInput { + filter: ProductFilter! + set: ProductPatch + remove: ProductPatch +} + +input UpdatePurchaseInput { + filter: PurchaseFilter! + set: PurchasePatch + remove: PurchasePatch +} + +input UpdateUserInput { + filter: UserFilter! + set: UserPatch + remove: UserPatch +} + +input UserFilter { + email: StringHashFilter + has: [UserHasFilter] + and: [UserFilter] + or: [UserFilter] + not: UserFilter +} + +input UserOrder { + asc: UserOrderable + desc: UserOrderable + then: UserOrder +} + +input UserPatch { + email: String + purchase_history: [PurchaseRef] + user_vector: [Float!] +} + +input UserRef { + email: String + purchase_history: [PurchaseRef] + user_vector: [Float!] +} + +####################### +# Generated Query +####################### + +type Query { + getProduct(id: String!): Product + querySimilarProductById(id: String!, by: ProductEmbedding!, topK: Int!, filter: ProductFilter): [Product] + querySimilarProductByEmbedding(by: ProductEmbedding!, topK: Int!, vector: [Float!]!, filter: ProductFilter): [Product] + queryProduct(filter: ProductFilter, order: ProductOrder, first: Int, offset: Int): [Product] + aggregateProduct(filter: ProductFilter): ProductAggregateResult + queryPurchase(filter: PurchaseFilter, order: PurchaseOrder, first: Int, offset: Int): [Purchase] + aggregatePurchase(filter: PurchaseFilter): PurchaseAggregateResult + getUser(email: String!): User + querySimilarUserById(email: String!, by: UserEmbedding!, topK: Int!, filter: UserFilter): [User] + querySimilarUserByEmbedding(by: UserEmbedding!, topK: Int!, vector: [Float!]!, filter: UserFilter): [User] + queryUser(filter: UserFilter, order: UserOrder, first: Int, offset: Int): [User] + aggregateUser(filter: UserFilter): UserAggregateResult +} + +####################### +# Generated Mutations +####################### + +type Mutation { + addProduct(input: [AddProductInput!]!, upsert: Boolean): AddProductPayload + updateProduct(input: UpdateProductInput!): UpdateProductPayload + deleteProduct(filter: ProductFilter!): DeleteProductPayload + addPurchase(input: [AddPurchaseInput!]!): AddPurchasePayload + updatePurchase(input: UpdatePurchaseInput!): UpdatePurchasePayload + deletePurchase(filter: PurchaseFilter!): DeletePurchasePayload + addUser(input: [AddUserInput!]!, upsert: Boolean): AddUserPayload + updateUser(input: UpdateUserInput!): UpdateUserPayload + deleteUser(filter: UserFilter!): DeleteUserPayload +} + diff --git a/graphql/schema/testdata/schemagen/output/field-with-id-directive.graphql b/graphql/schema/testdata/schemagen/output/field-with-id-directive.graphql old mode 100755 new mode 100644 index c352bb70d87..c570fe404b0 --- a/graphql/schema/testdata/schemagen/output/field-with-id-directive.graphql +++ b/graphql/schema/testdata/schemagen/output/field-with-id-directive.graphql @@ -78,6 +78,7 @@ enum DgraphIndex { day hour geo + hnsw } input AuthRule { @@ -191,7 +192,8 @@ input GenerateMutationParams { } directive @hasInverse(field: String!) on FIELD_DEFINITION -directive @search(by: [DgraphIndex!]) on FIELD_DEFINITION +directive @search(by: [String!]) on FIELD_DEFINITION +directive @embedding on FIELD_DEFINITION directive @dgraph(type: String, pred: String) on OBJECT | INTERFACE | FIELD_DEFINITION directive @id(interface: Boolean) on FIELD_DEFINITION directive @withSubscription on OBJECT | INTERFACE | FIELD_DEFINITION diff --git a/graphql/schema/testdata/schemagen/output/field-with-multiple-@id-fields.graphql b/graphql/schema/testdata/schemagen/output/field-with-multiple-@id-fields.graphql old mode 100755 new mode 100644 index 13aa2507611..4742f69f583 --- a/graphql/schema/testdata/schemagen/output/field-with-multiple-@id-fields.graphql +++ b/graphql/schema/testdata/schemagen/output/field-with-multiple-@id-fields.graphql @@ -78,6 +78,7 @@ enum DgraphIndex { day hour geo + hnsw } input AuthRule { @@ -191,7 +192,8 @@ input GenerateMutationParams { } directive @hasInverse(field: String!) on FIELD_DEFINITION -directive @search(by: [DgraphIndex!]) on FIELD_DEFINITION +directive @search(by: [String!]) on FIELD_DEFINITION +directive @embedding on FIELD_DEFINITION directive @dgraph(type: String, pred: String) on OBJECT | INTERFACE | FIELD_DEFINITION directive @id(interface: Boolean) on FIELD_DEFINITION directive @withSubscription on OBJECT | INTERFACE | FIELD_DEFINITION diff --git a/graphql/schema/testdata/schemagen/output/field-with-reverse-predicate-in-dgraph-directive.graphql b/graphql/schema/testdata/schemagen/output/field-with-reverse-predicate-in-dgraph-directive.graphql old mode 100755 new mode 100644 index f2145184ee6..b323d5c0af3 --- a/graphql/schema/testdata/schemagen/output/field-with-reverse-predicate-in-dgraph-directive.graphql +++ b/graphql/schema/testdata/schemagen/output/field-with-reverse-predicate-in-dgraph-directive.graphql @@ -73,6 +73,7 @@ enum DgraphIndex { day hour geo + hnsw } input AuthRule { @@ -186,7 +187,8 @@ input GenerateMutationParams { } directive @hasInverse(field: String!) on FIELD_DEFINITION -directive @search(by: [DgraphIndex!]) on FIELD_DEFINITION +directive @search(by: [String!]) on FIELD_DEFINITION +directive @embedding on FIELD_DEFINITION directive @dgraph(type: String, pred: String) on OBJECT | INTERFACE | FIELD_DEFINITION directive @id(interface: Boolean) on FIELD_DEFINITION directive @withSubscription on OBJECT | INTERFACE | FIELD_DEFINITION diff --git a/graphql/schema/testdata/schemagen/output/filter-cleanSchema-all-empty.graphql b/graphql/schema/testdata/schemagen/output/filter-cleanSchema-all-empty.graphql index 64d88e15dd9..98a287265a8 100644 --- a/graphql/schema/testdata/schemagen/output/filter-cleanSchema-all-empty.graphql +++ b/graphql/schema/testdata/schemagen/output/filter-cleanSchema-all-empty.graphql @@ -76,6 +76,7 @@ enum DgraphIndex { day hour geo + hnsw } input AuthRule { @@ -189,7 +190,8 @@ input GenerateMutationParams { } directive @hasInverse(field: String!) on FIELD_DEFINITION -directive @search(by: [DgraphIndex!]) on FIELD_DEFINITION +directive @search(by: [String!]) on FIELD_DEFINITION +directive @embedding on FIELD_DEFINITION directive @dgraph(type: String, pred: String) on OBJECT | INTERFACE | FIELD_DEFINITION directive @id(interface: Boolean) on FIELD_DEFINITION directive @withSubscription on OBJECT | INTERFACE | FIELD_DEFINITION diff --git a/graphql/schema/testdata/schemagen/output/filter-cleanSchema-circular.graphql b/graphql/schema/testdata/schemagen/output/filter-cleanSchema-circular.graphql index fd2ae029d78..e8a0a4e3262 100644 --- a/graphql/schema/testdata/schemagen/output/filter-cleanSchema-circular.graphql +++ b/graphql/schema/testdata/schemagen/output/filter-cleanSchema-circular.graphql @@ -80,6 +80,7 @@ enum DgraphIndex { day hour geo + hnsw } input AuthRule { @@ -193,7 +194,8 @@ input GenerateMutationParams { } directive @hasInverse(field: String!) on FIELD_DEFINITION -directive @search(by: [DgraphIndex!]) on FIELD_DEFINITION +directive @search(by: [String!]) on FIELD_DEFINITION +directive @embedding on FIELD_DEFINITION directive @dgraph(type: String, pred: String) on OBJECT | INTERFACE | FIELD_DEFINITION directive @id(interface: Boolean) on FIELD_DEFINITION directive @withSubscription on OBJECT | INTERFACE | FIELD_DEFINITION diff --git a/graphql/schema/testdata/schemagen/output/filter-cleanSchema-custom-mutation.graphql b/graphql/schema/testdata/schemagen/output/filter-cleanSchema-custom-mutation.graphql index 480d42cc906..b49eccb0dda 100644 --- a/graphql/schema/testdata/schemagen/output/filter-cleanSchema-custom-mutation.graphql +++ b/graphql/schema/testdata/schemagen/output/filter-cleanSchema-custom-mutation.graphql @@ -68,6 +68,7 @@ enum DgraphIndex { day hour geo + hnsw } input AuthRule { @@ -181,7 +182,8 @@ input GenerateMutationParams { } directive @hasInverse(field: String!) on FIELD_DEFINITION -directive @search(by: [DgraphIndex!]) on FIELD_DEFINITION +directive @search(by: [String!]) on FIELD_DEFINITION +directive @embedding on FIELD_DEFINITION directive @dgraph(type: String, pred: String) on OBJECT | INTERFACE | FIELD_DEFINITION directive @id(interface: Boolean) on FIELD_DEFINITION directive @withSubscription on OBJECT | INTERFACE | FIELD_DEFINITION diff --git a/graphql/schema/testdata/schemagen/output/filter-cleanSchema-directLink.graphql b/graphql/schema/testdata/schemagen/output/filter-cleanSchema-directLink.graphql index ce22f674d5a..12d3fc8f1a8 100644 --- a/graphql/schema/testdata/schemagen/output/filter-cleanSchema-directLink.graphql +++ b/graphql/schema/testdata/schemagen/output/filter-cleanSchema-directLink.graphql @@ -78,6 +78,7 @@ enum DgraphIndex { day hour geo + hnsw } input AuthRule { @@ -191,7 +192,8 @@ input GenerateMutationParams { } directive @hasInverse(field: String!) on FIELD_DEFINITION -directive @search(by: [DgraphIndex!]) on FIELD_DEFINITION +directive @search(by: [String!]) on FIELD_DEFINITION +directive @embedding on FIELD_DEFINITION directive @dgraph(type: String, pred: String) on OBJECT | INTERFACE | FIELD_DEFINITION directive @id(interface: Boolean) on FIELD_DEFINITION directive @withSubscription on OBJECT | INTERFACE | FIELD_DEFINITION diff --git a/graphql/schema/testdata/schemagen/output/generate-directive.graphql b/graphql/schema/testdata/schemagen/output/generate-directive.graphql index 046c376fd08..2218601efb5 100644 --- a/graphql/schema/testdata/schemagen/output/generate-directive.graphql +++ b/graphql/schema/testdata/schemagen/output/generate-directive.graphql @@ -79,6 +79,7 @@ enum DgraphIndex { day hour geo + hnsw } input AuthRule { @@ -192,7 +193,8 @@ input GenerateMutationParams { } directive @hasInverse(field: String!) on FIELD_DEFINITION -directive @search(by: [DgraphIndex!]) on FIELD_DEFINITION +directive @search(by: [String!]) on FIELD_DEFINITION +directive @embedding on FIELD_DEFINITION directive @dgraph(type: String, pred: String) on OBJECT | INTERFACE | FIELD_DEFINITION directive @id(interface: Boolean) on FIELD_DEFINITION directive @withSubscription on OBJECT | INTERFACE | FIELD_DEFINITION diff --git a/graphql/schema/testdata/schemagen/output/geo-type.graphql b/graphql/schema/testdata/schemagen/output/geo-type.graphql index 7b007dbea08..229ca9e0bf6 100644 --- a/graphql/schema/testdata/schemagen/output/geo-type.graphql +++ b/graphql/schema/testdata/schemagen/output/geo-type.graphql @@ -70,6 +70,7 @@ enum DgraphIndex { day hour geo + hnsw } input AuthRule { @@ -183,7 +184,8 @@ input GenerateMutationParams { } directive @hasInverse(field: String!) on FIELD_DEFINITION -directive @search(by: [DgraphIndex!]) on FIELD_DEFINITION +directive @search(by: [String!]) on FIELD_DEFINITION +directive @embedding on FIELD_DEFINITION directive @dgraph(type: String, pred: String) on OBJECT | INTERFACE | FIELD_DEFINITION directive @id(interface: Boolean) on FIELD_DEFINITION directive @withSubscription on OBJECT | INTERFACE | FIELD_DEFINITION diff --git a/graphql/schema/testdata/schemagen/output/hasInverse-with-interface-having-directive.graphql b/graphql/schema/testdata/schemagen/output/hasInverse-with-interface-having-directive.graphql old mode 100755 new mode 100644 index 2a6e74d672c..18d386066e0 --- a/graphql/schema/testdata/schemagen/output/hasInverse-with-interface-having-directive.graphql +++ b/graphql/schema/testdata/schemagen/output/hasInverse-with-interface-having-directive.graphql @@ -89,6 +89,7 @@ enum DgraphIndex { day hour geo + hnsw } input AuthRule { @@ -202,7 +203,8 @@ input GenerateMutationParams { } directive @hasInverse(field: String!) on FIELD_DEFINITION -directive @search(by: [DgraphIndex!]) on FIELD_DEFINITION +directive @search(by: [String!]) on FIELD_DEFINITION +directive @embedding on FIELD_DEFINITION directive @dgraph(type: String, pred: String) on OBJECT | INTERFACE | FIELD_DEFINITION directive @id(interface: Boolean) on FIELD_DEFINITION directive @withSubscription on OBJECT | INTERFACE | FIELD_DEFINITION diff --git a/graphql/schema/testdata/schemagen/output/hasInverse-with-interface.graphql b/graphql/schema/testdata/schemagen/output/hasInverse-with-interface.graphql old mode 100755 new mode 100644 index 7262833b462..6f2de5a2e5d --- a/graphql/schema/testdata/schemagen/output/hasInverse-with-interface.graphql +++ b/graphql/schema/testdata/schemagen/output/hasInverse-with-interface.graphql @@ -91,6 +91,7 @@ enum DgraphIndex { day hour geo + hnsw } input AuthRule { @@ -204,7 +205,8 @@ input GenerateMutationParams { } directive @hasInverse(field: String!) on FIELD_DEFINITION -directive @search(by: [DgraphIndex!]) on FIELD_DEFINITION +directive @search(by: [String!]) on FIELD_DEFINITION +directive @embedding on FIELD_DEFINITION directive @dgraph(type: String, pred: String) on OBJECT | INTERFACE | FIELD_DEFINITION directive @id(interface: Boolean) on FIELD_DEFINITION directive @withSubscription on OBJECT | INTERFACE | FIELD_DEFINITION diff --git a/graphql/schema/testdata/schemagen/output/hasInverse-with-type-having-directive.graphql b/graphql/schema/testdata/schemagen/output/hasInverse-with-type-having-directive.graphql old mode 100755 new mode 100644 index 2a6e74d672c..18d386066e0 --- a/graphql/schema/testdata/schemagen/output/hasInverse-with-type-having-directive.graphql +++ b/graphql/schema/testdata/schemagen/output/hasInverse-with-type-having-directive.graphql @@ -89,6 +89,7 @@ enum DgraphIndex { day hour geo + hnsw } input AuthRule { @@ -202,7 +203,8 @@ input GenerateMutationParams { } directive @hasInverse(field: String!) on FIELD_DEFINITION -directive @search(by: [DgraphIndex!]) on FIELD_DEFINITION +directive @search(by: [String!]) on FIELD_DEFINITION +directive @embedding on FIELD_DEFINITION directive @dgraph(type: String, pred: String) on OBJECT | INTERFACE | FIELD_DEFINITION directive @id(interface: Boolean) on FIELD_DEFINITION directive @withSubscription on OBJECT | INTERFACE | FIELD_DEFINITION diff --git a/graphql/schema/testdata/schemagen/output/hasInverse.graphql b/graphql/schema/testdata/schemagen/output/hasInverse.graphql old mode 100755 new mode 100644 index e550998e55a..2c5c948720f --- a/graphql/schema/testdata/schemagen/output/hasInverse.graphql +++ b/graphql/schema/testdata/schemagen/output/hasInverse.graphql @@ -70,6 +70,7 @@ enum DgraphIndex { day hour geo + hnsw } input AuthRule { @@ -183,7 +184,8 @@ input GenerateMutationParams { } directive @hasInverse(field: String!) on FIELD_DEFINITION -directive @search(by: [DgraphIndex!]) on FIELD_DEFINITION +directive @search(by: [String!]) on FIELD_DEFINITION +directive @embedding on FIELD_DEFINITION directive @dgraph(type: String, pred: String) on OBJECT | INTERFACE | FIELD_DEFINITION directive @id(interface: Boolean) on FIELD_DEFINITION directive @withSubscription on OBJECT | INTERFACE | FIELD_DEFINITION diff --git a/graphql/schema/testdata/schemagen/output/hasInverse_withSubscription.graphql b/graphql/schema/testdata/schemagen/output/hasInverse_withSubscription.graphql old mode 100755 new mode 100644 index 08cebd56299..67d0ebb7360 --- a/graphql/schema/testdata/schemagen/output/hasInverse_withSubscription.graphql +++ b/graphql/schema/testdata/schemagen/output/hasInverse_withSubscription.graphql @@ -70,6 +70,7 @@ enum DgraphIndex { day hour geo + hnsw } input AuthRule { @@ -183,7 +184,8 @@ input GenerateMutationParams { } directive @hasInverse(field: String!) on FIELD_DEFINITION -directive @search(by: [DgraphIndex!]) on FIELD_DEFINITION +directive @search(by: [String!]) on FIELD_DEFINITION +directive @embedding on FIELD_DEFINITION directive @dgraph(type: String, pred: String) on OBJECT | INTERFACE | FIELD_DEFINITION directive @id(interface: Boolean) on FIELD_DEFINITION directive @withSubscription on OBJECT | INTERFACE | FIELD_DEFINITION diff --git a/graphql/schema/testdata/schemagen/output/hasfilter.graphql b/graphql/schema/testdata/schemagen/output/hasfilter.graphql index e5adcc1acc6..3fd950ad953 100644 --- a/graphql/schema/testdata/schemagen/output/hasfilter.graphql +++ b/graphql/schema/testdata/schemagen/output/hasfilter.graphql @@ -72,6 +72,7 @@ enum DgraphIndex { day hour geo + hnsw } input AuthRule { @@ -185,7 +186,8 @@ input GenerateMutationParams { } directive @hasInverse(field: String!) on FIELD_DEFINITION -directive @search(by: [DgraphIndex!]) on FIELD_DEFINITION +directive @search(by: [String!]) on FIELD_DEFINITION +directive @embedding on FIELD_DEFINITION directive @dgraph(type: String, pred: String) on OBJECT | INTERFACE | FIELD_DEFINITION directive @id(interface: Boolean) on FIELD_DEFINITION directive @withSubscription on OBJECT | INTERFACE | FIELD_DEFINITION diff --git a/graphql/schema/testdata/schemagen/output/ignore-unsupported-directive.graphql b/graphql/schema/testdata/schemagen/output/ignore-unsupported-directive.graphql old mode 100755 new mode 100644 index 0d50f4fcb38..11a0c470f28 --- a/graphql/schema/testdata/schemagen/output/ignore-unsupported-directive.graphql +++ b/graphql/schema/testdata/schemagen/output/ignore-unsupported-directive.graphql @@ -71,6 +71,7 @@ enum DgraphIndex { day hour geo + hnsw } input AuthRule { @@ -184,7 +185,8 @@ input GenerateMutationParams { } directive @hasInverse(field: String!) on FIELD_DEFINITION -directive @search(by: [DgraphIndex!]) on FIELD_DEFINITION +directive @search(by: [String!]) on FIELD_DEFINITION +directive @embedding on FIELD_DEFINITION directive @dgraph(type: String, pred: String) on OBJECT | INTERFACE | FIELD_DEFINITION directive @id(interface: Boolean) on FIELD_DEFINITION directive @withSubscription on OBJECT | INTERFACE | FIELD_DEFINITION diff --git a/graphql/schema/testdata/schemagen/output/interface-with-dgraph-pred.graphql b/graphql/schema/testdata/schemagen/output/interface-with-dgraph-pred.graphql index a604dd070c5..315a044ef03 100644 --- a/graphql/schema/testdata/schemagen/output/interface-with-dgraph-pred.graphql +++ b/graphql/schema/testdata/schemagen/output/interface-with-dgraph-pred.graphql @@ -80,6 +80,7 @@ enum DgraphIndex { day hour geo + hnsw } input AuthRule { @@ -193,7 +194,8 @@ input GenerateMutationParams { } directive @hasInverse(field: String!) on FIELD_DEFINITION -directive @search(by: [DgraphIndex!]) on FIELD_DEFINITION +directive @search(by: [String!]) on FIELD_DEFINITION +directive @embedding on FIELD_DEFINITION directive @dgraph(type: String, pred: String) on OBJECT | INTERFACE | FIELD_DEFINITION directive @id(interface: Boolean) on FIELD_DEFINITION directive @withSubscription on OBJECT | INTERFACE | FIELD_DEFINITION diff --git a/graphql/schema/testdata/schemagen/output/interface-with-id-directive.graphql b/graphql/schema/testdata/schemagen/output/interface-with-id-directive.graphql old mode 100755 new mode 100644 index b69bab2269d..45286f85154 --- a/graphql/schema/testdata/schemagen/output/interface-with-id-directive.graphql +++ b/graphql/schema/testdata/schemagen/output/interface-with-id-directive.graphql @@ -76,6 +76,7 @@ enum DgraphIndex { day hour geo + hnsw } input AuthRule { @@ -189,7 +190,8 @@ input GenerateMutationParams { } directive @hasInverse(field: String!) on FIELD_DEFINITION -directive @search(by: [DgraphIndex!]) on FIELD_DEFINITION +directive @search(by: [String!]) on FIELD_DEFINITION +directive @embedding on FIELD_DEFINITION directive @dgraph(type: String, pred: String) on OBJECT | INTERFACE | FIELD_DEFINITION directive @id(interface: Boolean) on FIELD_DEFINITION directive @withSubscription on OBJECT | INTERFACE | FIELD_DEFINITION diff --git a/graphql/schema/testdata/schemagen/output/interface-with-no-ids.graphql b/graphql/schema/testdata/schemagen/output/interface-with-no-ids.graphql old mode 100755 new mode 100644 index 90161b27860..15d9aa8534d --- a/graphql/schema/testdata/schemagen/output/interface-with-no-ids.graphql +++ b/graphql/schema/testdata/schemagen/output/interface-with-no-ids.graphql @@ -74,6 +74,7 @@ enum DgraphIndex { day hour geo + hnsw } input AuthRule { @@ -187,7 +188,8 @@ input GenerateMutationParams { } directive @hasInverse(field: String!) on FIELD_DEFINITION -directive @search(by: [DgraphIndex!]) on FIELD_DEFINITION +directive @search(by: [String!]) on FIELD_DEFINITION +directive @embedding on FIELD_DEFINITION directive @dgraph(type: String, pred: String) on OBJECT | INTERFACE | FIELD_DEFINITION directive @id(interface: Boolean) on FIELD_DEFINITION directive @withSubscription on OBJECT | INTERFACE | FIELD_DEFINITION diff --git a/graphql/schema/testdata/schemagen/output/interfaces-with-types-and-password.graphql b/graphql/schema/testdata/schemagen/output/interfaces-with-types-and-password.graphql old mode 100755 new mode 100644 index 2314846daa9..d1f7645b71b --- a/graphql/schema/testdata/schemagen/output/interfaces-with-types-and-password.graphql +++ b/graphql/schema/testdata/schemagen/output/interfaces-with-types-and-password.graphql @@ -99,6 +99,7 @@ enum DgraphIndex { day hour geo + hnsw } input AuthRule { @@ -212,7 +213,8 @@ input GenerateMutationParams { } directive @hasInverse(field: String!) on FIELD_DEFINITION -directive @search(by: [DgraphIndex!]) on FIELD_DEFINITION +directive @search(by: [String!]) on FIELD_DEFINITION +directive @embedding on FIELD_DEFINITION directive @dgraph(type: String, pred: String) on OBJECT | INTERFACE | FIELD_DEFINITION directive @id(interface: Boolean) on FIELD_DEFINITION directive @withSubscription on OBJECT | INTERFACE | FIELD_DEFINITION diff --git a/graphql/schema/testdata/schemagen/output/interfaces-with-types.graphql b/graphql/schema/testdata/schemagen/output/interfaces-with-types.graphql old mode 100755 new mode 100644 index 53da87263b2..667dc065979 --- a/graphql/schema/testdata/schemagen/output/interfaces-with-types.graphql +++ b/graphql/schema/testdata/schemagen/output/interfaces-with-types.graphql @@ -99,6 +99,7 @@ enum DgraphIndex { day hour geo + hnsw } input AuthRule { @@ -212,7 +213,8 @@ input GenerateMutationParams { } directive @hasInverse(field: String!) on FIELD_DEFINITION -directive @search(by: [DgraphIndex!]) on FIELD_DEFINITION +directive @search(by: [String!]) on FIELD_DEFINITION +directive @embedding on FIELD_DEFINITION directive @dgraph(type: String, pred: String) on OBJECT | INTERFACE | FIELD_DEFINITION directive @id(interface: Boolean) on FIELD_DEFINITION directive @withSubscription on OBJECT | INTERFACE | FIELD_DEFINITION diff --git a/graphql/schema/testdata/schemagen/output/lambda-directive.graphql b/graphql/schema/testdata/schemagen/output/lambda-directive.graphql index 8df251f64a7..c083d55a1a4 100644 --- a/graphql/schema/testdata/schemagen/output/lambda-directive.graphql +++ b/graphql/schema/testdata/schemagen/output/lambda-directive.graphql @@ -66,6 +66,7 @@ enum DgraphIndex { day hour geo + hnsw } input AuthRule { @@ -179,7 +180,8 @@ input GenerateMutationParams { } directive @hasInverse(field: String!) on FIELD_DEFINITION -directive @search(by: [DgraphIndex!]) on FIELD_DEFINITION +directive @search(by: [String!]) on FIELD_DEFINITION +directive @embedding on FIELD_DEFINITION directive @dgraph(type: String, pred: String) on OBJECT | INTERFACE | FIELD_DEFINITION directive @id(interface: Boolean) on FIELD_DEFINITION directive @withSubscription on OBJECT | INTERFACE | FIELD_DEFINITION diff --git a/graphql/schema/testdata/schemagen/output/language-tags.graphql b/graphql/schema/testdata/schemagen/output/language-tags.graphql old mode 100755 new mode 100644 index 75d0f6daa9a..03c3a4b89cf --- a/graphql/schema/testdata/schemagen/output/language-tags.graphql +++ b/graphql/schema/testdata/schemagen/output/language-tags.graphql @@ -12,7 +12,7 @@ type Person implements Node { f2: String @dgraph(pred: "T.f@no") f3: String @dgraph(pred: "f3@en") name: String! @id - nameHi: String @dgraph(pred: "Person.name@hi") @search(by: [term,exact]) + nameHi: String @dgraph(pred: "Person.name@hi") @search(by: ["term","exact"]) nameEn: String @dgraph(pred: "Person.name@en") @search(by: [regexp]) nameHiEn: String @dgraph(pred: "Person.name@hi:en") nameHi_En_Untag: String @dgraph(pred: "Person.name@hi:en:.") @@ -79,6 +79,7 @@ enum DgraphIndex { day hour geo + hnsw } input AuthRule { @@ -192,7 +193,8 @@ input GenerateMutationParams { } directive @hasInverse(field: String!) on FIELD_DEFINITION -directive @search(by: [DgraphIndex!]) on FIELD_DEFINITION +directive @search(by: [String!]) on FIELD_DEFINITION +directive @embedding on FIELD_DEFINITION directive @dgraph(type: String, pred: String) on OBJECT | INTERFACE | FIELD_DEFINITION directive @id(interface: Boolean) on FIELD_DEFINITION directive @withSubscription on OBJECT | INTERFACE | FIELD_DEFINITION diff --git a/graphql/schema/testdata/schemagen/output/no-id-field-with-searchables.graphql b/graphql/schema/testdata/schemagen/output/no-id-field-with-searchables.graphql old mode 100755 new mode 100644 index 5430dbf9e17..fafc7d3ac19 --- a/graphql/schema/testdata/schemagen/output/no-id-field-with-searchables.graphql +++ b/graphql/schema/testdata/schemagen/output/no-id-field-with-searchables.graphql @@ -63,6 +63,7 @@ enum DgraphIndex { day hour geo + hnsw } input AuthRule { @@ -176,7 +177,8 @@ input GenerateMutationParams { } directive @hasInverse(field: String!) on FIELD_DEFINITION -directive @search(by: [DgraphIndex!]) on FIELD_DEFINITION +directive @search(by: [String!]) on FIELD_DEFINITION +directive @embedding on FIELD_DEFINITION directive @dgraph(type: String, pred: String) on OBJECT | INTERFACE | FIELD_DEFINITION directive @id(interface: Boolean) on FIELD_DEFINITION directive @withSubscription on OBJECT | INTERFACE | FIELD_DEFINITION diff --git a/graphql/schema/testdata/schemagen/output/no-id-field.graphql b/graphql/schema/testdata/schemagen/output/no-id-field.graphql old mode 100755 new mode 100644 index 1976d50b845..929f96e399b --- a/graphql/schema/testdata/schemagen/output/no-id-field.graphql +++ b/graphql/schema/testdata/schemagen/output/no-id-field.graphql @@ -76,6 +76,7 @@ enum DgraphIndex { day hour geo + hnsw } input AuthRule { @@ -189,7 +190,8 @@ input GenerateMutationParams { } directive @hasInverse(field: String!) on FIELD_DEFINITION -directive @search(by: [DgraphIndex!]) on FIELD_DEFINITION +directive @search(by: [String!]) on FIELD_DEFINITION +directive @embedding on FIELD_DEFINITION directive @dgraph(type: String, pred: String) on OBJECT | INTERFACE | FIELD_DEFINITION directive @id(interface: Boolean) on FIELD_DEFINITION directive @withSubscription on OBJECT | INTERFACE | FIELD_DEFINITION diff --git a/graphql/schema/testdata/schemagen/output/password-type.graphql b/graphql/schema/testdata/schemagen/output/password-type.graphql old mode 100755 new mode 100644 index 7bfe0bf4007..cb13e7d52fc --- a/graphql/schema/testdata/schemagen/output/password-type.graphql +++ b/graphql/schema/testdata/schemagen/output/password-type.graphql @@ -64,6 +64,7 @@ enum DgraphIndex { day hour geo + hnsw } input AuthRule { @@ -177,7 +178,8 @@ input GenerateMutationParams { } directive @hasInverse(field: String!) on FIELD_DEFINITION -directive @search(by: [DgraphIndex!]) on FIELD_DEFINITION +directive @search(by: [String!]) on FIELD_DEFINITION +directive @embedding on FIELD_DEFINITION directive @dgraph(type: String, pred: String) on OBJECT | INTERFACE | FIELD_DEFINITION directive @id(interface: Boolean) on FIELD_DEFINITION directive @withSubscription on OBJECT | INTERFACE | FIELD_DEFINITION diff --git a/graphql/schema/testdata/schemagen/output/random.graphql b/graphql/schema/testdata/schemagen/output/random.graphql index 117b334faef..196c54e6e2f 100644 --- a/graphql/schema/testdata/schemagen/output/random.graphql +++ b/graphql/schema/testdata/schemagen/output/random.graphql @@ -82,6 +82,7 @@ enum DgraphIndex { day hour geo + hnsw } input AuthRule { @@ -195,7 +196,8 @@ input GenerateMutationParams { } directive @hasInverse(field: String!) on FIELD_DEFINITION -directive @search(by: [DgraphIndex!]) on FIELD_DEFINITION +directive @search(by: [String!]) on FIELD_DEFINITION +directive @embedding on FIELD_DEFINITION directive @dgraph(type: String, pred: String) on OBJECT | INTERFACE | FIELD_DEFINITION directive @id(interface: Boolean) on FIELD_DEFINITION directive @withSubscription on OBJECT | INTERFACE | FIELD_DEFINITION diff --git a/graphql/schema/testdata/schemagen/output/searchables-references.graphql b/graphql/schema/testdata/schemagen/output/searchables-references.graphql old mode 100755 new mode 100644 index a1d33610f5a..e2ef7e884ad --- a/graphql/schema/testdata/schemagen/output/searchables-references.graphql +++ b/graphql/schema/testdata/schemagen/output/searchables-references.graphql @@ -12,8 +12,8 @@ type Author { type Post { postID: ID! - title: String! @search(by: [term,fulltext]) - text: String @search(by: [fulltext,term]) + title: String! @search(by: ["term","fulltext"]) + text: String @search(by: ["fulltext","term"]) datePublished: DateTime } @@ -74,6 +74,7 @@ enum DgraphIndex { day hour geo + hnsw } input AuthRule { @@ -187,7 +188,8 @@ input GenerateMutationParams { } directive @hasInverse(field: String!) on FIELD_DEFINITION -directive @search(by: [DgraphIndex!]) on FIELD_DEFINITION +directive @search(by: [String!]) on FIELD_DEFINITION +directive @embedding on FIELD_DEFINITION directive @dgraph(type: String, pred: String) on OBJECT | INTERFACE | FIELD_DEFINITION directive @id(interface: Boolean) on FIELD_DEFINITION directive @withSubscription on OBJECT | INTERFACE | FIELD_DEFINITION diff --git a/graphql/schema/testdata/schemagen/output/searchables.graphql b/graphql/schema/testdata/schemagen/output/searchables.graphql old mode 100755 new mode 100644 index 8e21af2ddcd..822434df817 --- a/graphql/schema/testdata/schemagen/output/searchables.graphql +++ b/graphql/schema/testdata/schemagen/output/searchables.graphql @@ -5,7 +5,7 @@ type Post { postID: ID! title: String! @search(by: [term]) - titleByEverything: String! @search(by: [term,fulltext,trigram,hash]) + titleByEverything: String! @search(by: ["term","fulltext","trigram","hash"]) text: String @search(by: [fulltext]) tags: [String] @search(by: [trigram]) tagsHash: [String] @search(by: [hash]) @@ -26,8 +26,8 @@ type Post { postTypeRegexp: PostType @search(by: [regexp]) postTypeExact: [PostType] @search(by: [exact]) postTypeHash: PostType @search(by: [hash]) - postTypeRegexpExact: PostType @search(by: [exact,regexp]) - postTypeHashRegexp: PostType @search(by: [hash,regexp]) + postTypeRegexpExact: PostType @search(by: ["exact","regexp"]) + postTypeHashRegexp: PostType @search(by: ["hash","regexp"]) postTypeNone: PostType @search(by: []) } @@ -94,6 +94,7 @@ enum DgraphIndex { day hour geo + hnsw } input AuthRule { @@ -207,7 +208,8 @@ input GenerateMutationParams { } directive @hasInverse(field: String!) on FIELD_DEFINITION -directive @search(by: [DgraphIndex!]) on FIELD_DEFINITION +directive @search(by: [String!]) on FIELD_DEFINITION +directive @embedding on FIELD_DEFINITION directive @dgraph(type: String, pred: String) on OBJECT | INTERFACE | FIELD_DEFINITION directive @id(interface: Boolean) on FIELD_DEFINITION directive @withSubscription on OBJECT | INTERFACE | FIELD_DEFINITION diff --git a/graphql/schema/testdata/schemagen/output/single-type-with-enum.graphql b/graphql/schema/testdata/schemagen/output/single-type-with-enum.graphql old mode 100755 new mode 100644 index dc67bd55967..9e618990057 --- a/graphql/schema/testdata/schemagen/output/single-type-with-enum.graphql +++ b/graphql/schema/testdata/schemagen/output/single-type-with-enum.graphql @@ -72,6 +72,7 @@ enum DgraphIndex { day hour geo + hnsw } input AuthRule { @@ -185,7 +186,8 @@ input GenerateMutationParams { } directive @hasInverse(field: String!) on FIELD_DEFINITION -directive @search(by: [DgraphIndex!]) on FIELD_DEFINITION +directive @search(by: [String!]) on FIELD_DEFINITION +directive @embedding on FIELD_DEFINITION directive @dgraph(type: String, pred: String) on OBJECT | INTERFACE | FIELD_DEFINITION directive @id(interface: Boolean) on FIELD_DEFINITION directive @withSubscription on OBJECT | INTERFACE | FIELD_DEFINITION diff --git a/graphql/schema/testdata/schemagen/output/single-type.graphql b/graphql/schema/testdata/schemagen/output/single-type.graphql old mode 100755 new mode 100644 index ffd93575abf..dfd68e48be5 --- a/graphql/schema/testdata/schemagen/output/single-type.graphql +++ b/graphql/schema/testdata/schemagen/output/single-type.graphql @@ -67,6 +67,7 @@ enum DgraphIndex { day hour geo + hnsw } input AuthRule { @@ -180,7 +181,8 @@ input GenerateMutationParams { } directive @hasInverse(field: String!) on FIELD_DEFINITION -directive @search(by: [DgraphIndex!]) on FIELD_DEFINITION +directive @search(by: [String!]) on FIELD_DEFINITION +directive @embedding on FIELD_DEFINITION directive @dgraph(type: String, pred: String) on OBJECT | INTERFACE | FIELD_DEFINITION directive @id(interface: Boolean) on FIELD_DEFINITION directive @withSubscription on OBJECT | INTERFACE | FIELD_DEFINITION diff --git a/graphql/schema/testdata/schemagen/output/type-implements-multiple-interfaces.graphql b/graphql/schema/testdata/schemagen/output/type-implements-multiple-interfaces.graphql old mode 100755 new mode 100644 index b789d8551f7..1307a08b8cf --- a/graphql/schema/testdata/schemagen/output/type-implements-multiple-interfaces.graphql +++ b/graphql/schema/testdata/schemagen/output/type-implements-multiple-interfaces.graphql @@ -81,6 +81,7 @@ enum DgraphIndex { day hour geo + hnsw } input AuthRule { @@ -194,7 +195,8 @@ input GenerateMutationParams { } directive @hasInverse(field: String!) on FIELD_DEFINITION -directive @search(by: [DgraphIndex!]) on FIELD_DEFINITION +directive @search(by: [String!]) on FIELD_DEFINITION +directive @embedding on FIELD_DEFINITION directive @dgraph(type: String, pred: String) on OBJECT | INTERFACE | FIELD_DEFINITION directive @id(interface: Boolean) on FIELD_DEFINITION directive @withSubscription on OBJECT | INTERFACE | FIELD_DEFINITION diff --git a/graphql/schema/testdata/schemagen/output/type-reference.graphql b/graphql/schema/testdata/schemagen/output/type-reference.graphql old mode 100755 new mode 100644 index 721497a18fd..30e7ab07d77 --- a/graphql/schema/testdata/schemagen/output/type-reference.graphql +++ b/graphql/schema/testdata/schemagen/output/type-reference.graphql @@ -71,6 +71,7 @@ enum DgraphIndex { day hour geo + hnsw } input AuthRule { @@ -184,7 +185,8 @@ input GenerateMutationParams { } directive @hasInverse(field: String!) on FIELD_DEFINITION -directive @search(by: [DgraphIndex!]) on FIELD_DEFINITION +directive @search(by: [String!]) on FIELD_DEFINITION +directive @embedding on FIELD_DEFINITION directive @dgraph(type: String, pred: String) on OBJECT | INTERFACE | FIELD_DEFINITION directive @id(interface: Boolean) on FIELD_DEFINITION directive @withSubscription on OBJECT | INTERFACE | FIELD_DEFINITION diff --git a/graphql/schema/testdata/schemagen/output/type-with-arguments-on-field.graphql b/graphql/schema/testdata/schemagen/output/type-with-arguments-on-field.graphql index 192482c12fa..dc5a5938e70 100644 --- a/graphql/schema/testdata/schemagen/output/type-with-arguments-on-field.graphql +++ b/graphql/schema/testdata/schemagen/output/type-with-arguments-on-field.graphql @@ -72,6 +72,7 @@ enum DgraphIndex { day hour geo + hnsw } input AuthRule { @@ -185,7 +186,8 @@ input GenerateMutationParams { } directive @hasInverse(field: String!) on FIELD_DEFINITION -directive @search(by: [DgraphIndex!]) on FIELD_DEFINITION +directive @search(by: [String!]) on FIELD_DEFINITION +directive @embedding on FIELD_DEFINITION directive @dgraph(type: String, pred: String) on OBJECT | INTERFACE | FIELD_DEFINITION directive @id(interface: Boolean) on FIELD_DEFINITION directive @withSubscription on OBJECT | INTERFACE | FIELD_DEFINITION diff --git a/graphql/schema/testdata/schemagen/output/type-with-custom-field-on-dgraph-type.graphql b/graphql/schema/testdata/schemagen/output/type-with-custom-field-on-dgraph-type.graphql index 7e4c71914f7..2ab2f58c4fa 100644 --- a/graphql/schema/testdata/schemagen/output/type-with-custom-field-on-dgraph-type.graphql +++ b/graphql/schema/testdata/schemagen/output/type-with-custom-field-on-dgraph-type.graphql @@ -71,6 +71,7 @@ enum DgraphIndex { day hour geo + hnsw } input AuthRule { @@ -184,7 +185,8 @@ input GenerateMutationParams { } directive @hasInverse(field: String!) on FIELD_DEFINITION -directive @search(by: [DgraphIndex!]) on FIELD_DEFINITION +directive @search(by: [String!]) on FIELD_DEFINITION +directive @embedding on FIELD_DEFINITION directive @dgraph(type: String, pred: String) on OBJECT | INTERFACE | FIELD_DEFINITION directive @id(interface: Boolean) on FIELD_DEFINITION directive @withSubscription on OBJECT | INTERFACE | FIELD_DEFINITION diff --git a/graphql/schema/testdata/schemagen/output/type-with-custom-fields-on-remote-type.graphql b/graphql/schema/testdata/schemagen/output/type-with-custom-fields-on-remote-type.graphql index e85ea68b041..e425637fb7c 100644 --- a/graphql/schema/testdata/schemagen/output/type-with-custom-fields-on-remote-type.graphql +++ b/graphql/schema/testdata/schemagen/output/type-with-custom-fields-on-remote-type.graphql @@ -71,6 +71,7 @@ enum DgraphIndex { day hour geo + hnsw } input AuthRule { @@ -184,7 +185,8 @@ input GenerateMutationParams { } directive @hasInverse(field: String!) on FIELD_DEFINITION -directive @search(by: [DgraphIndex!]) on FIELD_DEFINITION +directive @search(by: [String!]) on FIELD_DEFINITION +directive @embedding on FIELD_DEFINITION directive @dgraph(type: String, pred: String) on OBJECT | INTERFACE | FIELD_DEFINITION directive @id(interface: Boolean) on FIELD_DEFINITION directive @withSubscription on OBJECT | INTERFACE | FIELD_DEFINITION diff --git a/graphql/schema/testdata/schemagen/output/type-without-orderables.graphql b/graphql/schema/testdata/schemagen/output/type-without-orderables.graphql index ff78a20fbb1..7196b507ad5 100644 --- a/graphql/schema/testdata/schemagen/output/type-without-orderables.graphql +++ b/graphql/schema/testdata/schemagen/output/type-without-orderables.graphql @@ -66,6 +66,7 @@ enum DgraphIndex { day hour geo + hnsw } input AuthRule { @@ -179,7 +180,8 @@ input GenerateMutationParams { } directive @hasInverse(field: String!) on FIELD_DEFINITION -directive @search(by: [DgraphIndex!]) on FIELD_DEFINITION +directive @search(by: [String!]) on FIELD_DEFINITION +directive @embedding on FIELD_DEFINITION directive @dgraph(type: String, pred: String) on OBJECT | INTERFACE | FIELD_DEFINITION directive @id(interface: Boolean) on FIELD_DEFINITION directive @withSubscription on OBJECT | INTERFACE | FIELD_DEFINITION diff --git a/graphql/schema/testdata/schemagen/output/union.graphql b/graphql/schema/testdata/schemagen/output/union.graphql index 33eaa4c6c54..c6ea004e33f 100644 --- a/graphql/schema/testdata/schemagen/output/union.graphql +++ b/graphql/schema/testdata/schemagen/output/union.graphql @@ -113,6 +113,7 @@ enum DgraphIndex { day hour geo + hnsw } input AuthRule { @@ -226,7 +227,8 @@ input GenerateMutationParams { } directive @hasInverse(field: String!) on FIELD_DEFINITION -directive @search(by: [DgraphIndex!]) on FIELD_DEFINITION +directive @search(by: [String!]) on FIELD_DEFINITION +directive @embedding on FIELD_DEFINITION directive @dgraph(type: String, pred: String) on OBJECT | INTERFACE | FIELD_DEFINITION directive @id(interface: Boolean) on FIELD_DEFINITION directive @withSubscription on OBJECT | INTERFACE | FIELD_DEFINITION diff --git a/graphql/schema/wrappers.go b/graphql/schema/wrappers.go index 40239982a55..e756d681a36 100644 --- a/graphql/schema/wrappers.go +++ b/graphql/schema/wrappers.go @@ -85,24 +85,38 @@ type EntityRepresentations struct { // Query/Mutation types and arg names const ( - GetQuery QueryType = "get" - FilterQuery QueryType = "query" - AggregateQuery QueryType = "aggregate" - SchemaQuery QueryType = "schema" - EntitiesQuery QueryType = "entities" - PasswordQuery QueryType = "checkPassword" - HTTPQuery QueryType = "http" - DQLQuery QueryType = "dql" - NotSupportedQuery QueryType = "notsupported" - AddMutation MutationType = "add" - UpdateMutation MutationType = "update" - DeleteMutation MutationType = "delete" - HTTPMutation MutationType = "http" - NotSupportedMutation MutationType = "notsupported" - IDType = "ID" - InputArgName = "input" - UpsertArgName = "upsert" - FilterArgName = "filter" + GetQuery QueryType = "get" + SimilarByIdQuery QueryType = "querySimilarById" + SimilarByEmbeddingQuery QueryType = "querySimilarByEmbedding" + FilterQuery QueryType = "query" + AggregateQuery QueryType = "aggregate" + SchemaQuery QueryType = "schema" + EntitiesQuery QueryType = "entities" + PasswordQuery QueryType = "checkPassword" + HTTPQuery QueryType = "http" + DQLQuery QueryType = "dql" + NotSupportedQuery QueryType = "notsupported" + AddMutation MutationType = "add" + UpdateMutation MutationType = "update" + DeleteMutation MutationType = "delete" + HTTPMutation MutationType = "http" + NotSupportedMutation MutationType = "notsupported" + IDType = "ID" + InputArgName = "input" + UpsertArgName = "upsert" + FilterArgName = "filter" + SimilarByArgName = "by" + SimilarTopKArgName = "topK" + SimilarVectorArgName = "vector" + EmbeddingEnumSuffix = "Embedding" + SimilarQueryPrefix = "querySimilar" + SimilarByIdQuerySuffix = "ById" + SimilarByEmbeddingQuerySuffix = "ByEmbedding" + SimilarQueryResultTypeSuffix = "WithDistance" + SimilarQueryDistanceFieldName = "vector_distance" + SimilarSearchMetricEuclidian = "euclidian" + SimilarSearchMetricDotProduct = "dotproduct" + SimilarSearchMetricCosine = "cosine" ) // Schema represents a valid GraphQL schema @@ -269,6 +283,8 @@ type FieldDefinition interface { IsID() bool IsExternal() bool HasIDDirective() bool + HasEmbeddingDirective() bool + EmbeddingSearchMetric() string HasInterfaceArg() bool Inverse() FieldDefinition WithMemberType(string) FieldDefinition @@ -1376,8 +1392,11 @@ func (f *field) IDArgValue() (xids map[string]string, uid uint64, err error) { // or Password. Therefore the non ID and Password field is an XID. // TODO maybe there is a better way to do this. for _, arg := range f.field.Arguments { + xidArgName = "" if (idField == nil || arg.Name != idField.Name()) && - (passwordField == nil || arg.Name != passwordField.Name()) { + (passwordField == nil || arg.Name != passwordField.Name()) && + (queryType(f.field.Name, nil) != SimilarByIdQuery || + (arg.Name != SimilarTopKArgName && arg.Name != SimilarByArgName && arg.Name != "filter")) { xidArgName = arg.Name } @@ -2007,6 +2026,10 @@ func queryType(name string, custom *ast.Directive) QueryType { return GetQuery case name == "__schema" || name == "__type" || name == "__typename": return SchemaQuery + case strings.HasPrefix(name, SimilarQueryPrefix) && strings.HasSuffix(name, SimilarByIdQuerySuffix): + return SimilarByIdQuery + case strings.HasPrefix(name, SimilarQueryPrefix) && strings.HasSuffix(name, SimilarByEmbeddingQuerySuffix): + return SimilarByEmbeddingQuery case strings.HasPrefix(name, "query"): return FilterQuery case strings.HasPrefix(name, "check"): @@ -2325,6 +2348,30 @@ func hasIDDirective(fd *ast.FieldDefinition) bool { return id != nil } +func (fd *fieldDefinition) HasEmbeddingDirective() bool { + if fd.fieldDef == nil { + return false + } + return hasEmbeddingDirective(fd.fieldDef) +} + +func (fd *fieldDefinition) EmbeddingSearchMetric() string { + if fd.fieldDef == nil || !hasEmbeddingDirective(fd.fieldDef) || + fd.fieldDef.Directives.ForName(searchDirective) == nil { + return "" + } + + searchArg := getSearchArgs(fd.fieldDef)[0] + kvMap, _ := parseSearchOptions(searchArg) + + return kvMap["metric"] +} + +func hasEmbeddingDirective(fd *ast.FieldDefinition) bool { + id := fd.Directives.ForName(embeddingDirective) + return id != nil +} + func (fd *fieldDefinition) HasInterfaceArg() bool { if fd.fieldDef == nil { return false diff --git a/graphql/schema/wrappers_test.go b/graphql/schema/wrappers_test.go index b119842d41a..9867020bd13 100644 --- a/graphql/schema/wrappers_test.go +++ b/graphql/schema/wrappers_test.go @@ -37,7 +37,7 @@ func TestDgraphMapping_WithoutDirectives(t *testing.T) { type Author { id: ID! - name: String! @search(by: [hash, trigram]) + name: String! @search(by: ["hash", "trigram"]) dob: DateTime @search reputation: Float @search posts: [Post!] @hasInverse(field: author) @@ -222,7 +222,7 @@ func TestDgraphMapping_WithDirectives(t *testing.T) { type Author @dgraph(type: "dgraph.author") { id: ID! - name: String! @search(by: [hash, trigram]) + name: String! @search(by: ["hash", "trigram"]) dob: DateTime @search reputation: Float @search posts: [Post!] @hasInverse(field: author) diff --git a/query/vector/integration_test.go b/query/vector/integration_test.go new file mode 100644 index 00000000000..4897cfc5a53 --- /dev/null +++ b/query/vector/integration_test.go @@ -0,0 +1,41 @@ +//go:build integration + +/* + * Copyright 2023 Dgraph Labs, Inc. and Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package query + +import ( + "context" + "testing" + + "github.com/dgraph-io/dgraph/dgraphtest" + "github.com/dgraph-io/dgraph/x" +) + +func TestMain(m *testing.M) { + dc = dgraphtest.NewComposeCluster() + + var err error + var cleanup func() + client, cleanup, err = dc.Client() + x.Panic(err) + defer cleanup() + x.Panic(client.LoginIntoNamespace(context.Background(), dgraphtest.DefaultUser, + dgraphtest.DefaultPassword, x.GalaxyNamespace)) + + m.Run() +} diff --git a/query/vector/vector_graphql_test.go b/query/vector/vector_graphql_test.go new file mode 100644 index 00000000000..058e89dca04 --- /dev/null +++ b/query/vector/vector_graphql_test.go @@ -0,0 +1,206 @@ +//go:build integration + +/* + * Copyright 2016-2023 Dgraph Labs, Inc. and Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package query + +import ( + "encoding/json" + "testing" + + "github.com/dgraph-io/dgraph/dgraphtest" + "github.com/stretchr/testify/require" +) + +type ProjectInput struct { + Title string `json:"title"` + TitleV []float32 `json:"title_v"` +} + +const ( + graphQLVectorSchema = ` + type Project { + id: ID! + title: String! @search(by: [exact]) + title_v: [Float!] @embedding @search(by: ["hnsw(metric: euclidian, exponent: 4)"]) + } + ` +) + +var ( + projects = []ProjectInput{ProjectInput{ + Title: "iCreate with a Mini iPad", + TitleV: []float32{0.7, 0.8, 0.9, 0.1, 0.2}, + }, ProjectInput{ + Title: "Resistive Touchscreen", + TitleV: []float32{0.7, 0.8, 0.9, 0.1, 0.2}, + }, ProjectInput{ + Title: "Fitness Band", + TitleV: []float32{0.7, 0.8, 0.9, 0.1, 0.2}, + }, ProjectInput{ + Title: "Smart Watch", + TitleV: []float32{0.7, 0.8, 0.9, 0.1, 0.2}, + }, ProjectInput{ + Title: "Smart Ring", + TitleV: []float32{0.7, 0.8, 0.9, 0.1, 0.2}, + }} +) + +func addProject(t *testing.T, hc *dgraphtest.HTTPClient, project ProjectInput) { + query := ` + mutation addProject($project: AddProjectInput!) { + addProject(input: [$project]) { + project { + title + title_v + } + } + }` + + params := dgraphtest.GraphQLParams{ + Query: query, + Variables: map[string]interface{}{"project": project}, + } + + _, err := hc.RunGraphqlQuery(params, false) + require.NoError(t, err) +} +func queryProjectUsingTitle(t *testing.T, hc *dgraphtest.HTTPClient, title string) ProjectInput { + query := ` query QueryProject($title: String!) { + queryProject(filter: { title: { eq: $title } }) { + title + title_v + } + }` + + params := dgraphtest.GraphQLParams{ + Query: query, + Variables: map[string]interface{}{"title": title}, + } + response, err := hc.RunGraphqlQuery(params, false) + require.NoError(t, err) + type QueryResult struct { + QueryProject []ProjectInput `json:"queryProject"` + } + + var resp QueryResult + err = json.Unmarshal([]byte(string(response)), &resp) + require.NoError(t, err) + + return resp.QueryProject[0] +} + +func queryProjectsSimilarByEmbedding(t *testing.T, hc *dgraphtest.HTTPClient, vector []float32) []ProjectInput { + // query similar project by embedding + queryProduct := `query QuerySimilarProjectByEmbedding($by: ProjectEmbedding!, $topK: Int!, $vector: [Float!]!) { + querySimilarProjectByEmbedding(by: $by, topK: $topK, vector: $vector) { + id + title + title_v + } + } + + ` + + params := dgraphtest.GraphQLParams{ + Query: queryProduct, + Variables: map[string]interface{}{ + "by": "title_v", + "topK": 3, + "vector": vector, + }} + response, err := hc.RunGraphqlQuery(params, false) + require.NoError(t, err) + type QueryResult struct { + QueryProject []ProjectInput `json:"queryProject"` + } + var resp QueryResult + err = json.Unmarshal([]byte(string(response)), &resp) + require.NoError(t, err) + + return resp.QueryProject + +} + +func TestVectorGraphQLAddVectorPredicate(t *testing.T) { + require.NoError(t, client.DropAll()) + + hc, err := dc.HTTPClient() + require.NoError(t, err) + hc.LoginIntoNamespace("groot", "password", 0) + // add schema + require.NoError(t, hc.UpdateGQLSchema(graphQLVectorSchema)) +} + +func TestVectorGraphQlMutationAndQuery(t *testing.T) { + require.NoError(t, client.DropAll()) + + hc, err := dc.HTTPClient() + require.NoError(t, err) + hc.LoginIntoNamespace("groot", "password", 0) + + // add schema + require.NoError(t, hc.UpdateGQLSchema(graphQLVectorSchema)) + + // add project + var vectors [][]float32 + for _, project := range projects { + vectors = append(vectors, project.TitleV) + addProject(t, hc, project) + } + + for _, project := range projects { + p := queryProjectUsingTitle(t, hc, project.Title) + require.Equal(t, project.Title, p.Title) + require.Equal(t, project.TitleV, p.TitleV) + } + + for _, project := range projects { + p := queryProjectUsingTitle(t, hc, project.Title) + require.Equal(t, project.Title, p.Title) + require.Equal(t, project.TitleV, p.TitleV) + } + + // query similar project by embedding + for _, project := range projects { + similarProjects := queryProjectsSimilarByEmbedding(t, hc, project.TitleV) + + for _, similarVec := range similarProjects { + require.Contains(t, vectors, similarVec.TitleV) + } + } +} + +func TestVectorSchema(t *testing.T) { + require.NoError(t, client.DropAll()) + + hc, err := dc.HTTPClient() + require.NoError(t, err) + hc.LoginIntoNamespace("groot", "password", 0) + + schema := `type Project { + id: ID! + title: String! @search(by: [exact]) + title_v: [Float!] + }` + + // add schema + require.NoError(t, hc.UpdateGQLSchema(schema)) + require.Error(t, hc.UpdateGQLSchema(graphQLVectorSchema)) + require.NoError(t, client.DropAll()) + require.NoError(t, hc.UpdateGQLSchema(graphQLVectorSchema)) +} diff --git a/query/vector_test.go b/query/vector/vector_test.go similarity index 82% rename from query/vector_test.go rename to query/vector/vector_test.go index a387b4d8479..2725d6e322b 100644 --- a/query/vector_test.go +++ b/query/vector/vector_test.go @@ -25,8 +25,10 @@ import ( "math/rand" "strings" "testing" + "time" "github.com/dgraph-io/dgo/v230/protos/api" + "github.com/dgraph-io/dgraph/dgraphtest" "github.com/stretchr/testify/require" ) @@ -38,6 +40,124 @@ const ( vectorSchemaWithoutIndex = `%v: float32vector .` ) +var client *dgraphtest.GrpcClient +var dc dgraphtest.Cluster + +func setSchema(schema string) { + var err error + for retry := 0; retry < 60; retry++ { + err = client.Alter(context.Background(), &api.Operation{Schema: schema}) + if err == nil { + return + } + time.Sleep(time.Second) + } + panic(fmt.Sprintf("Could not alter schema. Got error %v", err.Error())) +} + +func dropPredicate(pred string) { + err := client.Alter(context.Background(), &api.Operation{ + DropAttr: pred, + }) + if err != nil { + panic(fmt.Sprintf("Could not drop predicate. Got error %v", err.Error())) + } +} + +func processQuery(ctx context.Context, t *testing.T, query string) (string, error) { + txn := client.NewTxn() + defer func() { + if err := txn.Discard(ctx); err != nil { + t.Logf("error discarding txn: %v", err) + } + }() + + res, err := txn.Query(ctx, query) + if err != nil { + return "", err + } + + response := map[string]interface{}{} + response["data"] = json.RawMessage(string(res.Json)) + + jsonResponse, err := json.Marshal(response) + require.NoError(t, err) + return string(jsonResponse), err +} + +func processQueryRDF(ctx context.Context, t *testing.T, query string) (string, error) { + txn := client.NewTxn() + defer func() { _ = txn.Discard(ctx) }() + + res, err := txn.Do(ctx, &api.Request{ + Query: query, + RespFormat: api.Request_RDF, + }) + if err != nil { + return "", err + } + return string(res.Rdf), err +} + +func processQueryNoErr(t *testing.T, query string) string { + res, err := processQuery(context.Background(), t, query) + require.NoError(t, err) + return res +} + +// processQueryForMetrics works like processQuery but returns metrics instead of response. +func processQueryForMetrics(t *testing.T, query string) *api.Metrics { + txn := client.NewTxn() + defer func() { _ = txn.Discard(context.Background()) }() + + res, err := txn.Query(context.Background(), query) + require.NoError(t, err) + return res.Metrics +} + +func processQueryWithVars(t *testing.T, query string, + vars map[string]string) (string, error) { + txn := client.NewTxn() + defer func() { _ = txn.Discard(context.Background()) }() + + res, err := txn.QueryWithVars(context.Background(), query, vars) + if err != nil { + return "", err + } + + response := map[string]interface{}{} + response["data"] = json.RawMessage(string(res.Json)) + + jsonResponse, err := json.Marshal(response) + require.NoError(t, err) + return string(jsonResponse), err +} + +func addTriplesToCluster(triples string) error { + txn := client.NewTxn() + ctx := context.Background() + defer func() { _ = txn.Discard(ctx) }() + + _, err := txn.Mutate(ctx, &api.Mutation{ + SetNquads: []byte(triples), + CommitNow: true, + }) + return err +} + +func deleteTriplesInCluster(triples string) { + txn := client.NewTxn() + ctx := context.Background() + defer func() { _ = txn.Discard(ctx) }() + + _, err := txn.Mutate(ctx, &api.Mutation{ + DelNquads: []byte(triples), + CommitNow: true, + }) + if err != nil { + panic(fmt.Sprintf("Could not delete triples. Got error %v", err.Error())) + } +} func updateVector(t *testing.T, triple string, pred string) []float32 { uid := strings.Split(triple, " ")[0] randomVec := generateRandomVector(10) diff --git a/systest/backup/common/utils.go b/systest/backup/common/utils.go index cab1b54004c..dc60fd2b81f 100644 --- a/systest/backup/common/utils.go +++ b/systest/backup/common/utils.go @@ -108,7 +108,7 @@ func AddItemSchema(t *testing.T, header http.Header, whichAlpha string) { updateSchemaParams := &common.GraphQLParams{ Query: `mutation { updateGQLSchema( - input: { set: { schema: "type Item {id: ID!, name: String! @search(by: [hash]), price: String!}"}}) + input: { set: { schema: "type Item {id: ID!, name: String! @search(by: [\"hash\"]), price: String!}"}}) { gqlSchema { schema diff --git a/systest/multi-tenancy/basic_test.go b/systest/multi-tenancy/basic_test.go index d3d15189bd7..a90b7b64240 100644 --- a/systest/multi-tenancy/basic_test.go +++ b/systest/multi-tenancy/basic_test.go @@ -194,7 +194,7 @@ func (msuite *MultitenancyTestSuite) TestPersistentQuery() { sch := `type Product { productID: ID! - name: String @search(by: [term]) + name: String @search(by: ["term"]) }` require.NoError(t, hcli1.UpdateGQLSchema(sch)) require.NoError(t, hcli2.UpdateGQLSchema(sch))