Skip to content

Commit 061731c

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

File tree

5 files changed

+72
-7
lines changed

5 files changed

+72
-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`
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: 22 additions & 0 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.
@@ -552,3 +553,24 @@ func (e ElemMatch) String() string {
552553
}
553554
return quoteField(e.Field) + ": {" + opElemMatch + ": {" + strings.Join(s, ", ") + "}}"
554555
}
556+
557+
type Not struct {
558+
Field string
559+
Value *regexp.Regexp
560+
}
561+
562+
// Match implements Expression interface.
563+
func (e Not) Match(payload map[string]interface{}) bool {
564+
return !e.Value.MatchString(payload[e.Field].(string))
565+
}
566+
567+
// Prepare implements Expression interface.
568+
func (e *Not) Prepare(validator schema.Validator) error {
569+
_, err := prepareValue(e.Field, e.Value.String(), validator)
570+
return err
571+
}
572+
573+
// String implements Expression interface.
574+
func (e Not) String() string {
575+
return quoteField(e.Field) + ": {" + opNot + ": \"" + e.Value.String() + "\"}"
576+
}

schema/query/predicate_parser.go

Lines changed: 25 additions & 7 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:
@@ -235,13 +235,9 @@ func (p *predicateParser) parseCommand(field string) (Expression, error) {
235235
return &GreaterOrEqual{Field: field, Value: value}, nil
236236
}
237237
case opRegex:
238-
str, err := p.parseString()
238+
re, err := p.parseRegex(label)
239239
if err != nil {
240-
return nil, fmt.Errorf("%s: %v", label, err)
241-
}
242-
re, err := regexp.Compile(str)
243-
if err != nil {
244-
return nil, fmt.Errorf("%s: invalid regex: %v", label, err)
240+
return nil, err
245241
}
246242
p.eatWhitespaces()
247243
if !p.expect('}') {
@@ -258,6 +254,16 @@ func (p *predicateParser) parseCommand(field string) (Expression, error) {
258254
return nil, fmt.Errorf("%s: expected '}' got %q", label, p.peek())
259255
}
260256
return &ElemMatch{Field: field, Exps: exps}, nil
257+
case opNot:
258+
re, err := p.parseRegex(label)
259+
if err != nil {
260+
return nil, err
261+
}
262+
p.eatWhitespaces()
263+
if !p.expect('}') {
264+
return nil, fmt.Errorf("%s: expected '}' got %q", label, p.peek())
265+
}
266+
return &Not{Field: field, Value: re}, nil
261267
}
262268
}
263269
VALUE:
@@ -524,3 +530,15 @@ func (p *predicateParser) eatWhitespaces() {
524530
break
525531
}
526532
}
533+
534+
func (p *predicateParser) parseRegex(label string) (*regexp.Regexp, error) {
535+
str, err := p.parseString()
536+
if err != nil {
537+
return nil, fmt.Errorf("%s: %v", label, err)
538+
}
539+
re, err := regexp.Compile(str)
540+
if err != nil {
541+
return nil, fmt.Errorf("%s: invalid regex: %v", label, err)
542+
}
543+
return re, nil
544+
}

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{&Not{Field: "foo", Value: regexp.MustCompile("regex.+awesome")}},
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)