Added Not() criterion modifier

examples

```go
bolthold.Where("Category").Not().In("food", "animal")
bolthold.Where("Tags").Not().IsNil()
bolthold.Where("Tags").Not().IsNil().And("Name").Not().RegExp(regexp.MustCompile("ea"))
bolthold.Where(bolthold.Key).Not().Eq(testData[4].Key)
bolthold.Where("Category").Not().Eq("food").And("Category").Not().Eq("animal").Index("Category")

```

We now unfortunately have an overlap with the `Ne()` and `Not().Eq()`,
but it's not worth dropping the `Ne` operator and breaking backwards
compatibility.
This commit is contained in:
Tim Shannon
2019-06-23 19:01:51 -05:00
parent eed35b7556
commit e52bb1c20b
4 changed files with 50 additions and 7 deletions
+1
View File
@@ -22,3 +22,4 @@ _testmain.go
*.exe
*.test
*.prof
*.swp
+36 -5
View File
@@ -303,7 +303,7 @@ var testResults = []test{
field := ra.Field()
_, ok := field.(string)
if !ok {
return false, fmt.Errorf("Field not a string, it's a %T!", field)
return false, fmt.Errorf("field not a string, it's a %T", field)
}
return strings.HasPrefix(field.(string), "oat"), nil
@@ -316,7 +316,7 @@ var testResults = []test{
record := ra.Record()
_, ok := record.(*ItemTest)
if !ok {
return false, fmt.Errorf("Record not an ItemTest, it's a %T!", record)
return false, fmt.Errorf("record not an ItemTest, it's a %T", record)
}
return strings.HasPrefix(record.(*ItemTest).Name, "oat"), nil
@@ -435,7 +435,7 @@ var testResults = []test{
field := ra.Field()
_, ok := field.(string)
if !ok {
return false, fmt.Errorf("Field not a string, it's a %T!", field)
return false, fmt.Errorf("field not a string, it's a %T", field)
}
return !strings.HasPrefix(field.(string), "veh"), nil
@@ -448,7 +448,7 @@ var testResults = []test{
field := ra.Field()
_, ok := field.(string)
if !ok {
return false, fmt.Errorf("Field not a string, it's a %T!", field)
return false, fmt.Errorf("field not a string, it's a %T", field)
}
return !strings.HasPrefix(field.(string), "veh"), nil
@@ -491,6 +491,37 @@ var testResults = []test{
query: bolthold.Where("Category").Eq("food").Index("Category").And(bolthold.Key).Gt(testData[10].Key),
result: []int{12, 15},
},
test{
name: "Not In",
query: bolthold.Where("Category").Not().In("food", "animal"),
result: []int{0, 1, 3, 6, 11},
},
test{
name: "Not IsNil",
query: bolthold.Where("Tags").Not().IsNil(),
result: []int{4, 7, 10, 12, 15},
},
test{
name: "Multiple Not criteria",
query: bolthold.Where("Tags").Not().IsNil().And("Name").Not().RegExp(regexp.MustCompile("ea")),
result: []int{4, 7, 10, 15},
},
test{
name: "Not Equal Key with not modifier",
query: bolthold.Where(bolthold.Key).Not().Eq(testData[4].Key),
result: []int{0, 1, 2, 3, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16},
},
test{
name: "Double Negative", // don't do this, it's confusing
query: bolthold.Where(bolthold.Key).Not().Not().Eq(testData[4].Key),
result: []int{4},
},
test{
name: "Not on Index",
query: bolthold.Where("Category").Not().Eq("food").And("Category").Not().Eq("animal").
Index("Category"),
result: []int{0, 1, 3, 6, 11},
},
}
func insertTestData(t *testing.T, store *bolthold.Store) {
@@ -861,7 +892,7 @@ func TestKeyMatchFunc(t *testing.T) {
field := ra.Field()
_, ok := field.(string)
if !ok {
return false, fmt.Errorf("Field not a string, it's a %T!", field)
return false, fmt.Errorf("field not a string, it's a %T", field)
}
return strings.HasPrefix(field.(string), "oat"), nil
+1 -1
View File
@@ -288,7 +288,7 @@ func newIterator(tx *bolt.Tx, typeName string, query *Query) *iterator {
// however if there is only one critrion and it is either > = or >= then we can seek to the value and
// save reads
func seekCursor(cursor *bolt.Cursor, criteria []*Criterion) (key, value []byte) {
if len(criteria) != 1 {
if len(criteria) != 1 || criteria[0].negate {
return cursor.First()
}
+12 -1
View File
@@ -76,6 +76,7 @@ type Criterion struct {
operator int
value interface{}
inValues []interface{}
negate bool
}
func hasMatchFunc(criteria []*Criterion) bool {
@@ -324,6 +325,12 @@ func (c *Criterion) IsNil() *Query {
return c.op(isnil, nil)
}
// Not will negate the following critierion
func (c *Criterion) Not() *Criterion {
c.negate = !c.negate
return c
}
// MatchFunc is a function used to test an arbitrary matching value in a query
type MatchFunc func(ra *RecordAccess) (bool, error)
@@ -449,7 +456,8 @@ func matchesAllCriteria(criteria []*Criterion, value interface{}, encoded bool,
if err != nil {
return false, err
}
if !ok {
if criteria[i].negate == ok {
return false, nil
}
}
@@ -496,6 +504,9 @@ func (q *Query) String() string {
func (c *Criterion) String() string {
s := ""
if c.negate {
s += "NOT "
}
switch c.operator {
case eq:
s += "=="