Skip to content

Commit

Permalink
feat: Add _not operator (#1631)
Browse files Browse the repository at this point in the history
## Relevant issue(s)

Resolves #1542 

## Description

This PR add the `_not` operator as an option for filters in graphql
queries.
  • Loading branch information
fredcarle authored Jul 13, 2023
1 parent 10dc477 commit 9611024
Show file tree
Hide file tree
Showing 6 changed files with 273 additions and 0 deletions.
2 changes: 2 additions & 0 deletions connor/connor.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ func matchWith(op string, conditions, data any) (bool, error) {
return like(conditions, data)
case "_nlike":
return nlike(conditions, data)
case "_not":
return not(conditions, data)
default:
return false, NewErrUnknownOperator(op)
}
Expand Down
11 changes: 11 additions & 0 deletions connor/not.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package connor

// not is an operator which performs object equality test
// and returns the inverse of the result.
func not(condition, data any) (bool, error) {
m, err := eq(condition, data)
if err != nil {
return false, err
}
return !m, nil
}
50 changes: 50 additions & 0 deletions connor/not_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package connor

import (
"testing"

"github.com/stretchr/testify/require"
)

func TestNot_WithNotAndNotNot_NoError(t *testing.T) {
const testString = "Source is the glue of web3"

// not equal
result, err := not(testString, testString)
require.NoError(t, err)
require.False(t, result)

// not not equal
result, err = not("Source is the glue", testString)
require.NoError(t, err)
require.True(t, result)
}

func TestNot_WithEmptyCondition_ReturnError(t *testing.T) {
const testString = "Source is the glue of web3"

_, err := not(map[FilterKey]any{&operator{"_some"}: "test"}, testString)
require.ErrorIs(t, err, ErrUnknownOperator)
}

type operator struct {
// The filter operation string that this `operator`` represents.
//
// E.g. "_eq", or "_and".
Operation string
}

func (k *operator) GetProp(data any) any {
return data
}

func (k *operator) GetOperatorOrDefault(defaultOp string) string {
return k.Operation
}

func (k *operator) Equal(other FilterKey) bool {
if otherKey, isOk := other.(*operator); isOk && *k == *otherKey {
return true
}
return false
}
7 changes: 7 additions & 0 deletions planner/mapper/mapper.go
Original file line number Diff line number Diff line change
Expand Up @@ -1039,6 +1039,13 @@ func toFilterMap(
returnClauses = append(returnClauses, returnClause)
}
return key, returnClauses
case map[string]any:
innerMapClause := map[connor.FilterKey]any{}
for innerSourceKey, innerSourceValue := range typedClause {
rKey, rValue := toFilterMap(innerSourceKey, innerSourceValue, mapping)
innerMapClause[rKey] = rValue
}
return key, innerMapClause
default:
return key, typedClause
}
Expand Down
3 changes: 3 additions & 0 deletions planner/mapper/targetable.go
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,9 @@ func filterObjectToMap(mapping *core.DocumentMapping, obj map[connor.FilterKey]a
logicMapEntries[i] = filterObjectToMap(mapping, itemMap)
}
outmap[keyType.Operation] = logicMapEntries
case "_not":
itemMap := v.(map[connor.FilterKey]any)
outmap[keyType.Operation] = filterObjectToMap(mapping, itemMap)
default:
outmap[keyType.Operation] = v
}
Expand Down
200 changes: 200 additions & 0 deletions tests/integration/query/simple/with_filter/with_not_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
// Copyright 2023 Democratized Data Foundation
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.

package simple

import (
"testing"

testUtils "github.com/sourcenetwork/defradb/tests/integration"
)

func TestQuerySimple_WithNotEqualToXFilter_NoError(t *testing.T) {
test := testUtils.RequestTestCase{
Description: "Simple query with logical compound filter (not)",
Request: `query {
Users(filter: {_not: {Age: {_eq: 55}}}) {
Name
Age
}
}`,
Docs: map[int][]string{
0: {
`{
"Name": "John",
"Age": 21
}`,
`{
"Name": "Bob",
"Age": 32
}`,
`{
"Name": "Carlo",
"Age": 55
}`,
`{
"Name": "Alice",
"Age": 19
}`,
},
},
Results: []map[string]any{
{
"Name": "Bob",
"Age": uint64(32),
},
{
"Name": "Alice",
"Age": uint64(19),
},
{
"Name": "John",
"Age": uint64(21),
},
},
}

executeTestCase(t, test)
}

func TestQuerySimple_WithNotEqualToXorYFilter_NoError(t *testing.T) {
test := testUtils.RequestTestCase{
Description: "Simple query with logical compound filter (not)",
Request: `query {
Users(filter: {_not: {_or: [{Age: {_eq: 55}}, {Name: {_eq: "Alice"}}]}}) {
Name
Age
}
}`,
Docs: map[int][]string{
0: {
`{
"Name": "John",
"Age": 21
}`,
`{
"Name": "Bob",
"Age": 32
}`,
`{
"Name": "Carlo",
"Age": 55
}`,
`{
"Name": "Alice",
"Age": 19
}`,
},
},
Results: []map[string]any{
{
"Name": "Bob",
"Age": uint64(32),
},
{
"Name": "John",
"Age": uint64(21),
},
},
}

executeTestCase(t, test)
}

func TestQuerySimple_WithEmptyNotFilter_ReturnError(t *testing.T) {
test := testUtils.RequestTestCase{
Description: "Simple query with empty logical compound filter (not) returns empty result set",
Request: `query {
Users(filter: {_not: {}}) {
Name
Age
}
}`,
Docs: map[int][]string{
0: {
`{
"Name": "John",
"Age": 21
}`,
`{
"Name": "Bob",
"Age": 32
}`,
`{
"Name": "Carlo",
"Age": 55
}`,
`{
"Name": "Alice",
"Age": 19
}`,
},
},
Results: []map[string]any{},
}

executeTestCase(t, test)
}

func TestQuerySimple_WithNotEqualToXAndNotYFilter_NoError(t *testing.T) {
test := testUtils.RequestTestCase{
Description: "Simple query with logical compound filter (not)",
Request: `query {
Users(filter: {_not: {Age: {_eq: 55}, _not: {Name: {_eq: "Carlo"}}}}) {
Name
Age
}
}`,
Docs: map[int][]string{
0: {
`{
"Name": "John",
"Age": 21
}`,
`{
"Name": "Bob",
"Age": 32
}`,
`{
"Name": "Carlo",
"Age": 55
}`,
`{
"Name": "Alice",
"Age": 19
}`,
`{
"Name": "Frank",
"Age": 55
}`,
},
},
Results: []map[string]any{
{
"Name": "Bob",
"Age": uint64(32),
},
{
"Name": "Alice",
"Age": uint64(19),
},
{
"Name": "John",
"Age": uint64(21),
},
{
"Name": "Carlo",
"Age": uint64(55),
},
},
}

executeTestCase(t, test)
}

0 comments on commit 9611024

Please sign in to comment.