Skip to content

Commit cc9c103

Browse files
committed
implement '$not' regex
1 parent 0fa67ca commit cc9c103

File tree

5 files changed

+41
-7
lines changed

5 files changed

+41
-7
lines changed

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -998,6 +998,8 @@ The same example with flags:
998998
However, keep in mind that Storers have to support regular expression and depending on the implementation of the storage handler the accepted syntax may vary.
999999
An error of `ErrNotImplemented` will be returned for those storage back-ends which do not support the `$regex` operator.
10001000
1001+
The operator `$not` functions as an opposite operator to `$regex`. Unlike MongoDB, we do not allow `$not` as a general negation operator.
1002+
10011003
The `$elemMatch` operator matches documents that contain an array field with at least one element that matches all the specified query criteria.
10021004
```go
10031005
"telephones": schema.Field{
@@ -1041,6 +1043,7 @@ The snippet above will return all documents, which `telephones` array field cont
10411043
| `$gte` | `{a: {$gte: 10}}` | Fields value is greater than or equal to the specified number.
10421044
| `$exists` | `{a: {$exists: true}}` | Match if the field is present (or not if set to `false`) in the item, event if `nil`.
10431045
| `$regex` | `{a: {$regex: "fo[o]{1}"}}` | Match regular expression on a field's value.
1046+
| `$not` | `{a: {$not: "fo[o]{1}"}}` | Opposite of `$regex`.
10441047
| `$elemMatch` | `{a: {$elemMatch: {b: "foo"}}}` | Match array items against multiple query criteria.
10451048
10461049
*Some storage handlers may not support all operators. Refer to the storage handler's documentation for more info.*

schema/query/predicate.go

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ const (
2222
opGreaterOrEqual = "$gte"
2323
opRegex = "$regex"
2424
opElemMatch = "$elemMatch"
25+
opNot = "$not"
2526
)
2627

2728
// Predicate defines an expression against a schema to perform a match on schema's data.
@@ -476,13 +477,14 @@ func (e LowerOrEqual) String() string {
476477

477478
// Regex matches values that match to a specified regular expression.
478479
type Regex struct {
479-
Field string
480-
Value *regexp.Regexp
480+
Field string
481+
Value *regexp.Regexp
482+
Negated bool
481483
}
482484

483485
// Match implements Expression interface.
484486
func (e Regex) Match(payload map[string]interface{}) bool {
485-
return e.Value.MatchString(payload[e.Field].(string))
487+
return e.Value.MatchString(payload[e.Field].(string)) != e.Negated
486488
}
487489

488490
// Prepare implements Expression interface.
@@ -493,7 +495,13 @@ func (e *Regex) Prepare(validator schema.Validator) error {
493495

494496
// String implements Expression interface.
495497
func (e Regex) String() string {
496-
return quoteField(e.Field) + ": {" + opRegex + ": " + valueString(e.Value) + "}"
498+
var regexOperation string
499+
if e.Negated {
500+
regexOperation = opNot
501+
} else {
502+
regexOperation = opRegex
503+
}
504+
return quoteField(e.Field) + ": {" + regexOperation + ": " + valueString(e.Value) + "}"
497505
}
498506

499507
// ElemMatch matches object values specified in an array.

schema/query/predicate_parser.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ func (p *predicateParser) parseExpression() (Expression, error) {
107107
or := Or(subExp)
108108
return &or, nil
109109
case opExists, opIn, opNotIn, opNotEqual, opRegex, opElemMatch,
110-
opLowerThan, opLowerOrEqual, opGreaterThan, opGreaterOrEqual:
110+
opLowerThan, opLowerOrEqual, opGreaterThan, opGreaterOrEqual, opNot:
111111
p.pos = oldPos
112112
return nil, fmt.Errorf("%s: invalid placement", label)
113113
default:
@@ -234,7 +234,7 @@ func (p *predicateParser) parseCommand(field string) (Expression, error) {
234234
case opGreaterOrEqual:
235235
return &GreaterOrEqual{Field: field, Value: value}, nil
236236
}
237-
case opRegex:
237+
case opRegex, opNot:
238238
str, err := p.parseString()
239239
if err != nil {
240240
return nil, fmt.Errorf("%s: %v", label, err)
@@ -247,7 +247,8 @@ func (p *predicateParser) parseCommand(field string) (Expression, error) {
247247
if !p.expect('}') {
248248
return nil, fmt.Errorf("%s: expected '}' got %q", label, p.peek())
249249
}
250-
return &Regex{Field: field, Value: re}, nil
250+
negated := label == opNot
251+
return &Regex{Field: field, Value: re, Negated: negated}, nil
251252
case opElemMatch:
252253
exps, err := p.parseExpressions()
253254
if err != nil {

schema/query/predicate_parser_test.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,11 @@ func TestParse(t *testing.T) {
103103
Predicate{&Regex{Field: "foo", Value: regexp.MustCompile("regex.+awesome")}},
104104
nil,
105105
},
106+
{
107+
`{"foo": {"$not": "regex.+awesome"}}`,
108+
Predicate{&Regex{Field: "foo", Value: regexp.MustCompile("regex.+awesome"), Negated: true}},
109+
nil,
110+
},
106111
{
107112
`{"$and": [{"foo": "bar"}, {"foo": "baz"}]}`,
108113
Predicate{&And{&Equal{Field: "foo", Value: "bar"}, &Equal{Field: "foo", Value: "baz"}}},
@@ -361,6 +366,11 @@ func TestParse(t *testing.T) {
361366
Predicate{},
362367
errors.New("char 1: $elemMatch: invalid placement"),
363368
},
369+
{
370+
`{"$not": "someregexpression"}`,
371+
Predicate{},
372+
errors.New("char 1: $not: invalid placement"),
373+
},
364374
}
365375
for i := range tests {
366376
tt := tests[i]

schema/query/predicate_test.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,12 +140,24 @@ func TestMatch(t *testing.T) {
140140
},
141141
nil,
142142
},
143+
{
144+
`{"foo": {"$not": "rege[x]{1}.+some"}}`, []test{
145+
{map[string]interface{}{"foo": "regex-is-awesome"}, false},
146+
},
147+
nil,
148+
},
143149
{
144150
`{"foo": {"$regex": "^(?i)my.+-rest.+$"}}`, []test{
145151
{map[string]interface{}{"foo": "myAwesome-RESTApplication"}, true},
146152
},
147153
nil,
148154
},
155+
{
156+
`{"foo": {"$not": "^(?i)my.+-rest.+$"}}`, []test{
157+
{map[string]interface{}{"foo": "myAwesome-RESTApplication"}, false},
158+
},
159+
nil,
160+
},
149161
{
150162
`{"$and": [{"foo": "bar"}, {"foo": "baz"}]}`, []test{
151163
{map[string]interface{}{"foo": "bar"}, false},

0 commit comments

Comments
 (0)