Skip to content

Commit

Permalink
fix(query): fix has function in filter (#87)
Browse files Browse the repository at this point in the history
Currently when we have a language tagged predicate (example: name).
Querying name should return only untagged values. name@. should return
any language.

Consider dataset
<1> <name> "first user" .
<2> <name@en> "second user" .

query (func: uid(1,2)) { uid name} would return "{uid: 1, name: first
user}".
query (func: uid(1,2)) { uid name@.} would return "{{uid: 1, name@.:
"first user"}, {uid: 2, name@.: "second user"}}".

However due to a bug, we are not enforcing this in @filter(has(name)).
So this results in

query (func: has(dgraph.type)) @filter(has(name)) { uid name} results in
"{{uid: 1, name: "first user'}, {uid: 2}}"

Fix: We update @filter(has(name)) to filter on basis of language.

Fixes:
https://linear.app/hypermode/issue/HYP-311/inconsistent-counts-using-has-at-root

---------

Co-authored-by: Harshil Goel <harshil@dgraph.io>
  • Loading branch information
2 people authored and mangalaman93 committed Mar 1, 2024
1 parent 3754e87 commit 2d7dc22
Show file tree
Hide file tree
Showing 2 changed files with 76 additions and 1 deletion.
46 changes: 46 additions & 0 deletions query/query0_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,52 @@ func TestGetUID(t *testing.T) {
js)
}

func TestFilterHas(t *testing.T) {
// Query untagged values
query := `
{
me(func: has(alias)) @filter(has(alias_lang)) {
uid
}
}
`
js := processQueryNoErr(t, query)
require.JSONEq(t, `{"data":{"me":[]}}`, js)

// Query all tagged values
query = `
{
me(func: has(alias)) @filter(has(alias_lang@.)) {
alias_lang@.
}
}
`
js = processQueryNoErr(t, query)
require.JSONEq(t, `{"data":{"me":[{"alias_lang@.":"Zambo Alice"},{"alias_lang@.":"John Alice"},{"alias_lang@.":"Bob Joe"},{"alias_lang@.":"Allan Matt"},{"alias_lang@.":"John Oliver"}]}}`, js)

// All tagged values in root function
query = `
{
me(func: has(lossy@.)){
lossy@.
}
}
`
js = processQueryNoErr(t, query)
require.JSONEq(t, `{"data":{"me":[{"lossy@.":"Badger"},{"lossy@.":"Honey badger"}]}}`, js)

// Query specific language
query = `
{
me(func: has(lossy@.)) @filter(has(lossy@fr)) {
lossy@fr
}
}
`
js = processQueryNoErr(t, query)
require.JSONEq(t, `{"data":{"me":[{"lossy@fr":"Blaireau européen"}]}}`, js)
}

func TestQueryEmptyDefaultNames(t *testing.T) {
query := `{
people(func: eq(name, "")) {
Expand Down
31 changes: 30 additions & 1 deletion worker/task.go
Original file line number Diff line number Diff line change
Expand Up @@ -718,6 +718,10 @@ func (qs *queryState) handleUidPostings(
x.AssertTrue(width > 0)
span.Annotatef(nil, "Width: %d. NumGo: %d", width, numGo)

lang := langForFunc(q.Langs)
needFiltering := needsStringFiltering(srcFn, q.Langs, q.Attr)
isList := schema.State().IsList(q.Attr)

errCh := make(chan error, numGo)
outputs := make([]*pb.Result, numGo)

Expand Down Expand Up @@ -784,7 +788,32 @@ func (qs *queryState) handleUidPostings(
if i == 0 {
span.Annotate(nil, "HasFn")
}
empty, err := pl.IsEmpty(args.q.ReadTs, 0)
// We figure out if need to filter on bases of lang attribute or not.
// If we don't need to do so, we can just check if the posting list
// is empty. If we need to filter on basis of lang, we need to check
// the value with its tag. if lang == "", in that case, we need to
// return if there are any untagged values. If lang != "", in that
// case we need to check exact value.
empty := false
var err error
if !needFiltering {
empty, err = pl.IsEmpty(args.q.ReadTs, 0)
} else {
if lang == "" {
if isList {
_, err = pl.AllValues(args.q.ReadTs)
} else {
_, err = pl.Value(args.q.ReadTs)
}
} else {
_, err = pl.ValueForTag(args.q.ReadTs, lang)
}

if err == posting.ErrNoValue {
empty = true
err = nil
}
}
if err != nil {
return err
}
Expand Down

0 comments on commit 2d7dc22

Please sign in to comment.