Skip to content

Commit

Permalink
Add type function to allow querying types. (#2933)
Browse files Browse the repository at this point in the history
type(typeName) is the equivalent of eq("type", typeName).

Also added the type predicate as one of the initial predicates
and made sure it does not get deleted when the schema is dropped.
  • Loading branch information
martinmr authored Jan 25, 2019
1 parent 82c6797 commit 783afd1
Show file tree
Hide file tree
Showing 10 changed files with 205 additions and 11 deletions.
57 changes: 52 additions & 5 deletions dgraph/cmd/alpha/run_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -282,7 +282,8 @@ func TestDeletePredicate(t *testing.T) {
require.JSONEq(t, `{"data":{"schema":[`+
`{"predicate":"_predicate_","type":"string","list":true},`+
`{"predicate":"age","type":"default"},`+
`{"predicate":"name","type":"string","index":true, "tokenizer":["term"]}`+
`{"predicate":"name","type":"string","index":true, "tokenizer":["term"]},`+
`{"predicate":"type","type":"string","index":true, "tokenizer":["exact"]}`+
`]}}`, output)

output, err = runQuery(q1)
Expand Down Expand Up @@ -1358,8 +1359,8 @@ func TestListTypeSchemaChange(t *testing.T) {
require.NoError(t, err)
require.JSONEq(t, `{"data":{"schema":[`+
`{"predicate":"_predicate_","type":"string","list":true},`+
`{"predicate":"occupations","type":"string"}]}}`, res)

`{"predicate":"occupations","type":"string"},`+
`{"predicate":"type", "type":"string", "index":true, "tokenizer": ["exact"]}]}}`, res)
}

func TestDeleteAllSP2(t *testing.T) {
Expand Down Expand Up @@ -1502,8 +1503,8 @@ func TestDropAll(t *testing.T) {
output, err = runQuery(q3)
require.NoError(t, err)
require.JSONEq(t,
`{"data":{"schema":[{"predicate":"_predicate_","type":"string","list":true}`+
`]}}`, output)
`{"data":{"schema":[{"predicate":"_predicate_","type":"string","list":true},
{"predicate":"type", "type":"string", "index":true, "tokenizer":["exact"]}]}}`, output)

// Reinstate schema so that we can re-run the original query.
err = alterSchemaWithRetry(s)
Expand Down Expand Up @@ -1604,6 +1605,52 @@ func TestGrpcCompressionSupport(t *testing.T) {
require.NoError(t, err)
}

func TestTypeMutationAndQuery(t *testing.T) {
var m = `
{
"set": [
{
"name": "Alice",
"type": "Employee"
},
{
"name": "Bob",
"type": "Employer"
}
]
}
`

var q = `
{
q(func: has(name)) @filter(type(Employee)){
uid
name
}
}
`

var s = `
name: string @index(exact) .
`

require.NoError(t, dropAll())
err := alterSchemaWithRetry(s)
require.NoError(t, err)

err = runJsonMutation(m)
require.NoError(t, err)

output, err := runQuery(q)
require.NoError(t, err)
result := map[string]interface{}{}
require.NoError(t, json.Unmarshal([]byte(output), &result))
queryResults := result["data"].(map[string]interface{})["q"].([]interface{})
require.Equal(t, 1, len(queryResults))
name := queryResults[0].(map[string]interface{})["name"].(string)
require.Equal(t, "Alice", name)
}

func TestMain(m *testing.M) {
// Increment lease, so that mutations work.
conn, err := grpc.Dial("localhost:5080", grpc.WithInsecure())
Expand Down
11 changes: 8 additions & 3 deletions gql/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import (
const (
uid = "uid"
value = "val"
typ = "type"
)

// GraphQuery stores the parsed Query in a tree format. This gets converted to
Expand Down Expand Up @@ -1348,7 +1349,7 @@ func validFuncName(name string) bool {

switch name {
case "regexp", "anyofterms", "allofterms", "alloftext", "anyoftext",
"has", "uid", "uid_in", "anyof", "allof":
"has", "uid", "uid_in", "anyof", "allof", "type":
return true
}
return false
Expand Down Expand Up @@ -1558,7 +1559,7 @@ L:
}

// Unlike other functions, uid function has no attribute, everything is args.
if len(function.Attr) == 0 && function.Name != "uid" {
if len(function.Attr) == 0 && function.Name != uid && function.Name != typ {
if strings.ContainsRune(itemInFunc.Val, '"') {
return nil, itemInFunc.Errorf("Attribute in function"+
" must not be quoted with \": %s", itemInFunc.Val)
Expand Down Expand Up @@ -1616,10 +1617,14 @@ L:
}
}

if function.Name != uid && len(function.Attr) == 0 {
if function.Name != uid && function.Name != typ && len(function.Attr) == 0 {
return nil, it.Errorf("Got empty attr for function: [%s]", function.Name)
}

if function.Name == typ && len(function.Args) != 1 {
return nil, it.Errorf("type function only supports one argument. Got: %v", function.Args)
}

return function, nil
}

Expand Down
45 changes: 45 additions & 0 deletions gql/parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4493,3 +4493,48 @@ func TestLineAndColumnNumberInErrorOutput(t *testing.T) {
require.Contains(t, err.Error(),
"line 4 column 35: Unrecognized character in lexDirective: U+002C ','")
}

func TestTypeFunction(t *testing.T) {
q := `
query {
me(func: type(Person)) {
name
}
}`
gq, err := Parse(Request{Str: q})
require.NoError(t, err)
require.Equal(t, 1, len(gq.Query))
require.Equal(t, "type", gq.Query[0].Func.Name)
require.Equal(t, 1, len(gq.Query[0].Func.Args))
require.Equal(t, "Person", gq.Query[0].Func.Args[0].Value)
}

func TestTypeFunctionError1(t *testing.T) {
q := `
query {
me(func: type(Person, School)) {
name
}
}`
_, err := Parse(Request{Str: q})
require.Error(t, err)
require.Contains(t, err.Error(), "type function only supports one argument")
}

func TestTypeInFilter(t *testing.T) {
q := `
query {
me(func: uid(0x01)) @filter(type(Person)) {
name
}
}`
gq, err := Parse(Request{Str: q})
require.NoError(t, err)
require.Equal(t, 1, len(gq.Query))
require.Equal(t, "uid", gq.Query[0].Func.Name)
require.Equal(t, 1, len(gq.Query[0].Children))
require.Equal(t, "name", gq.Query[0].Children[0].Attr)
require.Equal(t, "type", gq.Query[0].Filter.Func.Name)
require.Equal(t, 1, len(gq.Query[0].Filter.Func.Args))
require.Equal(t, "Person", gq.Query[0].Filter.Func.Args[0].Value)
}
4 changes: 4 additions & 0 deletions query/common_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,7 @@ symbol : string @index(exact) .
room : string @index(term) .
office.room : [uid] .
best_friend : uid .
type : string @index(exact) .
`

err := schema.ParseBytes([]byte(schemaStr), 1)
Expand Down Expand Up @@ -361,6 +362,9 @@ best_friend : uid .
addEdgeToUID(t, "friend", 23, 1, nil)

addEdgeToUID(t, "best_friend", 2, 64, nil)
addEdgeToValue(t, "type", 2, "Person", nil)
addEdgeToValue(t, "type", 3, "Person", nil)
addEdgeToValue(t, "type", 4, "Person", nil)

addEdgeToUID(t, "school", 1, 5000, nil)
addEdgeToUID(t, "school", 23, 5001, nil)
Expand Down
16 changes: 15 additions & 1 deletion query/query.go
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,19 @@ func (sg *SubGraph) createSrcFunction(gf *gql.Function) {
if gf == nil {
return
}

// type function is just an alias for eq("type", type).
if gf.Name == "type" {
sg.Attr = "type"
sg.SrcFunc = &Function{
Name: "eq",
Args: append(gf.Args[:0:0], gf.Args...),
IsCount: false,
IsValueVar: false,
}
return
}

sg.SrcFunc = &Function{
Name: gf.Name,
Args: append(gf.Args[:0:0], gf.Args...),
Expand Down Expand Up @@ -949,6 +962,7 @@ func newGraph(ctx context.Context, gq *gql.GraphQuery) (*SubGraph, error) {
if !isValidFuncName(gq.Func.Name) {
return nil, x.Errorf("Invalid function name : %s", gq.Func.Name)
}

sg.createSrcFunction(gq.Func)
}

Expand Down Expand Up @@ -2391,7 +2405,7 @@ func isValidArg(a string) bool {
func isValidFuncName(f string) bool {
switch f {
case "anyofterms", "allofterms", "val", "regexp", "anyoftext", "alloftext",
"has", "uid", "uid_in", "anyof", "allof":
"has", "uid", "uid_in", "anyof", "allof", "type":
return true
}
return isInequalityFn(f) || types.IsGeoFunc(f)
Expand Down
52 changes: 52 additions & 0 deletions query/query3_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1868,3 +1868,55 @@ func TestFilterRegex16(t *testing.T) {
`{"data": {"me":[{"name@ru":"Артём Ткаченко"}]}}`,
js)
}

func TestTypeFunction(t *testing.T) {
query := `
{
me(func: type(Person)) {
uid
}
}
`
js := processToFastJsonNoErr(t, query)
require.JSONEq(t,
`{"data": {"me":[{"uid":"0x2"}, {"uid":"0x3"}, {"uid":"0x4"}]}}`,
js)
}

func TestTypeFunctionUnknownType(t *testing.T) {
query := `
{
me(func: type(UnknownType)) {
uid
}
}
`
js := processToFastJsonNoErr(t, query)
require.JSONEq(t, `{"data": {"me":[]}}`, js)
}

func TestTypeFilter(t *testing.T) {
query := `
{
me(func: uid(0x2)) @filter(type(Person)) {
uid
}
}
`
js := processToFastJsonNoErr(t, query)
require.JSONEq(t,
`{"data": {"me":[{"uid" :"0x2"}]}}`,
js)
}

func TestTypeFilterUnknownType(t *testing.T) {
query := `
{
me(func: uid(0x2)) @filter(type(UnknownType)) {
uid
}
}
`
js := processToFastJsonNoErr(t, query)
require.JSONEq(t, `{"data": {"me":[]}}`, js)
}
6 changes: 4 additions & 2 deletions systest/mutations_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -693,7 +693,8 @@ func SchemaAfterDeleteNode(t *testing.T, c *dgo.Dgraph) {
`{"predicate":"_predicate_","type":"string","list":true},`+
`{"predicate":"friend","type":"uid","list":true},`+
`{"predicate":"married","type":"bool"},`+
`{"predicate":"name","type":"default"}]`, string(b))
`{"predicate":"name","type":"default"},`+
`{"predicate":"type","type":"string","index":true, "tokenizer":["exact"]}]`, string(b))

require.NoError(t, c.Alter(ctx, &api.Operation{DropAttr: "married"}))

Expand All @@ -714,7 +715,8 @@ func SchemaAfterDeleteNode(t *testing.T, c *dgo.Dgraph) {
require.JSONEq(t, `[`+
`{"predicate":"_predicate_","type":"string","list":true},`+
`{"predicate":"friend","type":"uid","list":true},`+
`{"predicate":"name","type":"default"}]`, string(b))
`{"predicate":"name","type":"default"},`+
`{"predicate":"type","type":"string","index":true, "tokenizer":["exact"]}]`, string(b))
}

func FullTextEqual(t *testing.T, c *dgo.Dgraph) {
Expand Down
17 changes: 17 additions & 0 deletions systest/queries_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -336,6 +336,14 @@ func SchemaQueryTest(t *testing.T, c *dgo.Dgraph) {
"type": "string",
"list": true
},
{
"predicate": "type",
"type": "string",
"index": true,
"tokenizer": [
"exact"
]
},
{
"predicate": "name",
"type": "string",
Expand Down Expand Up @@ -384,6 +392,9 @@ func SchemaQueryTestPredicate1(t *testing.T, c *dgo.Dgraph) {
{
"predicate": "friends"
},
{
"predicate": "type"
},
{
"predicate": "name"
},
Expand Down Expand Up @@ -508,6 +519,12 @@ func SchemaQueryTestHTTP(t *testing.T, c *dgo.Dgraph) {
"type": "string",
"list": true
},
{
"index": true,
"predicate": "type",
"type": "string",
"tokenizer": ["exact"]
},
{
"predicate": "name",
"type": "string",
Expand Down
7 changes: 7 additions & 0 deletions worker/groups.go
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,13 @@ func (g *groupi) proposeInitialSchema() {
})
}

g.upsertSchema(&pb.SchemaUpdate{
Predicate: "type",
ValueType: pb.Posting_STRING,
Directive: pb.SchemaUpdate_INDEX,
Tokenizer: []string{"exact"},
})

if Config.AclEnabled {
// propose the schema update for acl predicates
g.upsertSchema(&pb.SchemaUpdate{
Expand Down
1 change: 1 addition & 0 deletions x/x.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ var (
"dgraph.password": {},
"dgraph.user.group": {},
"dgraph.group.acl": {},
"type": {},
}
Nilbyte []byte
)
Expand Down

0 comments on commit 783afd1

Please sign in to comment.