Skip to content

Commit

Permalink
implement '$not' regex (rs#274)
Browse files Browse the repository at this point in the history
  • Loading branch information
tsetsoo authored and Dragomir-Ivanov committed May 31, 2022
1 parent ea36a66 commit 907f6c2
Show file tree
Hide file tree
Showing 5 changed files with 41 additions and 7 deletions.
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -998,6 +998,8 @@ The same example with flags:
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.
An error of `ErrNotImplemented` will be returned for those storage back-ends which do not support the `$regex` operator.
The operator `$not` functions as an opposite operator to `$regex`. Unlike MongoDB, we do not allow `$not` as a general negation operator.
The `$elemMatch` operator matches documents that contain an array field with at least one element that matches all the specified query criteria.
```go
"telephones": schema.Field{
Expand Down Expand Up @@ -1041,6 +1043,7 @@ The snippet above will return all documents, which `telephones` array field cont
| `$gte` | `{a: {$gte: 10}}` | Fields value is greater than or equal to the specified number.
| `$exists` | `{a: {$exists: true}}` | Match if the field is present (or not if set to `false`) in the item, event if `nil`.
| `$regex` | `{a: {$regex: "fo[o]{1}"}}` | Match regular expression on a field's value.
| `$not` | `{a: {$not: "fo[o]{1}"}}` | Opposite of `$regex`.
| `$elemMatch` | `{a: {$elemMatch: {b: "foo"}}}` | Match array items against multiple query criteria.
*Some storage handlers may not support all operators. Refer to the storage handler's documentation for more info.*
Expand Down
16 changes: 12 additions & 4 deletions schema/query/predicate.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ const (
opGreaterOrEqual = "$gte"
opRegex = "$regex"
opElemMatch = "$elemMatch"
opNot = "$not"
)

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

// Regex matches values that match to a specified regular expression.
type Regex struct {
Field string
Value *regexp.Regexp
Field string
Value *regexp.Regexp
Negated bool
}

// Match implements Expression interface.
func (e Regex) Match(payload map[string]interface{}) bool {
return e.Value.MatchString(payload[e.Field].(string))
return e.Value.MatchString(payload[e.Field].(string)) != e.Negated
}

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

// String implements Expression interface.
func (e Regex) String() string {
return quoteField(e.Field) + ": {" + opRegex + ": " + valueString(e.Value) + "}"
var regexOperation string
if e.Negated {
regexOperation = opNot
} else {
regexOperation = opRegex
}
return quoteField(e.Field) + ": {" + regexOperation + ": " + valueString(e.Value) + "}"
}

// ElemMatch matches object values specified in an array.
Expand Down
7 changes: 4 additions & 3 deletions schema/query/predicate_parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ func (p *predicateParser) parseExpression() (Expression, error) {
or := Or(subExp)
return &or, nil
case opExists, opIn, opNotIn, opNotEqual, opRegex, opElemMatch,
opLowerThan, opLowerOrEqual, opGreaterThan, opGreaterOrEqual:
opLowerThan, opLowerOrEqual, opGreaterThan, opGreaterOrEqual, opNot:
p.pos = oldPos
return nil, fmt.Errorf("%s: invalid placement", label)
default:
Expand Down Expand Up @@ -234,7 +234,7 @@ func (p *predicateParser) parseCommand(field string) (Expression, error) {
case opGreaterOrEqual:
return &GreaterOrEqual{Field: field, Value: value}, nil
}
case opRegex:
case opRegex, opNot:
str, err := p.parseString()
if err != nil {
return nil, fmt.Errorf("%s: %v", label, err)
Expand All @@ -247,7 +247,8 @@ func (p *predicateParser) parseCommand(field string) (Expression, error) {
if !p.expect('}') {
return nil, fmt.Errorf("%s: expected '}' got %q", label, p.peek())
}
return &Regex{Field: field, Value: re}, nil
negated := label == opNot
return &Regex{Field: field, Value: re, Negated: negated}, nil
case opElemMatch:
exps, err := p.parseExpressions()
if err != nil {
Expand Down
10 changes: 10 additions & 0 deletions schema/query/predicate_parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,11 @@ func TestParse(t *testing.T) {
Predicate{&Regex{Field: "foo", Value: regexp.MustCompile("regex.+awesome")}},
nil,
},
{
`{"foo": {"$not": "regex.+awesome"}}`,
Predicate{&Regex{Field: "foo", Value: regexp.MustCompile("regex.+awesome"), Negated: true}},
nil,
},
{
`{"$and": [{"foo": "bar"}, {"foo": "baz"}]}`,
Predicate{&And{&Equal{Field: "foo", Value: "bar"}, &Equal{Field: "foo", Value: "baz"}}},
Expand Down Expand Up @@ -361,6 +366,11 @@ func TestParse(t *testing.T) {
Predicate{},
errors.New("char 1: $elemMatch: invalid placement"),
},
{
`{"$not": "someregexpression"}`,
Predicate{},
errors.New("char 1: $not: invalid placement"),
},
}
for i := range tests {
tt := tests[i]
Expand Down
12 changes: 12 additions & 0 deletions schema/query/predicate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -140,12 +140,24 @@ func TestMatch(t *testing.T) {
},
nil,
},
{
`{"foo": {"$not": "rege[x]{1}.+some"}}`, []test{
{map[string]interface{}{"foo": "regex-is-awesome"}, false},
},
nil,
},
{
`{"foo": {"$regex": "^(?i)my.+-rest.+$"}}`, []test{
{map[string]interface{}{"foo": "myAwesome-RESTApplication"}, true},
},
nil,
},
{
`{"foo": {"$not": "^(?i)my.+-rest.+$"}}`, []test{
{map[string]interface{}{"foo": "myAwesome-RESTApplication"}, false},
},
nil,
},
{
`{"$and": [{"foo": "bar"}, {"foo": "baz"}]}`, []test{
{map[string]interface{}{"foo": "bar"}, false},
Expand Down

0 comments on commit 907f6c2

Please sign in to comment.