Skip to content

Commit

Permalink
feat: add advanced queries
Browse files Browse the repository at this point in the history
- add generate antlr files
- add antlr grammar query
  • Loading branch information
ajclopez committed Oct 29, 2023
1 parent 3e850b9 commit 6c9632f
Show file tree
Hide file tree
Showing 19 changed files with 2,133 additions and 38 deletions.
6 changes: 5 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,5 @@
*.out
*.out
.antlr/


.DS_Store
16 changes: 16 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
* [Pagination](#pagination)
* [Sorting](#sorting)
* [Projection](#projection)
* [Advanced queries](#advanced-queries)
* [Available options](#available-options)
* [Customize limit value](#customize-limit-value)
* [Specify casting per param keys](#specify-casting-per-param-keys)
Expand Down Expand Up @@ -204,6 +205,21 @@ fields=firstname,lastname,phone,email
**Note:**
* The `_id` field (returned by default).

### Advanced queries

For more advanced usage (`and`, `or` logic operations), pass query `filter` as string with the logical operations, for example:

```json
filter=(country=Mexico OR country=Spain) and gender=female
````

##### What operations are possible?

* Filtering operations.
* The `AND/and` operator.
* The `OR/or` operator.
* Parenthesis can be used for grouping.

## Available options

You can use advanced options:
Expand Down
162 changes: 162 additions & 0 deletions antlr4/Query.g4
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
grammar Query;

input
: query EOF
;

query
: left=query logicalOp=(AND | OR) right=query #opQuery
| LPAREN query RPAREN #priorityQuery
| criteria #atomQuery
;

criteria
: key op value
| key
;

key
: IDENTIFIER
| NEG_IDENTIFIER
;

value
: IDENTIFIER
| STRING
| ENCODED_STRING
;

op
: EQ
| NE
| GT
| GTE
| LT
| LTE
;

STRING
: '\'' StringCharacter* '\''
;

fragment StringCharacter
: ~["\\\r\n]
| '\\' EscapeSequence
| LineContinuation
;
fragment EscapeSequence
: CharacterEscapeSequence
| HexEscapeSequence
| UnicodeEscapeSequence
;
fragment CharacterEscapeSequence
: SingleEscapeCharacter
| NonEscapeCharacter
;
fragment HexEscapeSequence
: 'x' HexDigit HexDigit
;
fragment UnicodeEscapeSequence
: 'u' HexDigit HexDigit HexDigit HexDigit
;
fragment SingleEscapeCharacter
: ['"\\bfnrtv]
;

fragment NonEscapeCharacter
: ~['"\\bfnrtv0-9xu\r\n]
;
fragment EscapeCharacter
: SingleEscapeCharacter
| DecimalDigit
| [xu]
;
fragment LineContinuation
: '\\' LineTerminatorSequence
;
fragment LineTerminatorSequence
: '\r\n'
| LineTerminator
;
fragment DecimalDigit
: [0-9]
;
fragment HexDigit
: [0-9a-fA-F]
;
fragment OctalDigit
: [0-7]
;
AND
: 'AND'
| 'and'
;
OR
: 'OR'
| 'or'
;
LPAREN
: '('
;
RPAREN
: ')'
;
EQ
: '='
;
NE
: '!='
;
GT
: '>'
;
GTE
: '>='
;
LT
: '<'
;
LTE
: '<='
;
IDENTIFIER
: [A-Za-z0-9.:_-]+
;
NEG_IDENTIFIER
: '!'[A-Za-z0-9.:_-]+
;
ENCODED_STRING
: ~([ \\[\]<>!=()])+
;
LineTerminator
: [\r\n\u2028\u2029] -> channel(HIDDEN)
;
WS
: [ \t\r\n]+ -> skip
;
5 changes: 4 additions & 1 deletion convert.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@ import (
)

// Convert converts the criteria value to a MongoDB query
func Convert(criteria SearchCriteria, filter map[string]interface{}) {
func Convert(criteria SearchCriteria) map[string]interface{} {

value := ParseValue(criteria.Value, criteria.Caster)
filter := make(map[string]interface{})

switch criteria.Operation {
case EQUAL:
Expand Down Expand Up @@ -39,6 +40,8 @@ func Convert(criteria SearchCriteria, filter map[string]interface{}) {
case EXISTS:
filter[criteria.Key] = buildMongoQuery("$exists", !criteria.Prefix)
}

return filter
}

func buildMongoQuery(operator string, value interface{}) map[string]interface{} {
Expand Down
16 changes: 2 additions & 14 deletions convert_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import (

var converetTests = []struct {
Criteria SearchCriteria
Filter map[string]interface{}
Expected map[string]interface{}
}{
{
Expand All @@ -18,7 +17,6 @@ var converetTests = []struct {
Operation: EQUAL,
Value: "Jhon",
},
map[string]interface{}{},
map[string]interface{}{"name": "Jhon"},
},
{
Expand All @@ -28,7 +26,6 @@ var converetTests = []struct {
Operation: EQUAL,
Value: "QUEUED,DEQUEUED",
},
map[string]interface{}{},
map[string]interface{}{"status": map[string]interface{}{"$in": []interface{}{"QUEUED", "DEQUEUED"}}},
},
{
Expand All @@ -38,7 +35,6 @@ var converetTests = []struct {
Operation: EQUAL,
Value: "/@gmail\\.com$/",
},
map[string]interface{}{},
map[string]interface{}{"email": map[string]interface{}{"$regex": "@gmail\\.com$", "$options": ""}},
},
{
Expand All @@ -48,7 +44,6 @@ var converetTests = []struct {
Operation: NOT_EQUAL,
Value: "SENT",
},
map[string]interface{}{},
map[string]interface{}{"status": map[string]interface{}{"$ne": "SENT"}},
},
{
Expand All @@ -58,7 +53,6 @@ var converetTests = []struct {
Operation: NOT_EQUAL,
Value: "QUEUED,DEQUEUED",
},
map[string]interface{}{},
map[string]interface{}{"status": map[string]interface{}{"$nin": []interface{}{"QUEUED", "DEQUEUED"}}},
},
{
Expand All @@ -68,7 +62,6 @@ var converetTests = []struct {
Operation: NOT_EQUAL,
Value: "/^58/",
},
map[string]interface{}{},
map[string]interface{}{"phone": map[string]interface{}{"$not": map[string]interface{}{"$regex": "^58", "$options": ""}}},
},
{
Expand All @@ -78,7 +71,6 @@ var converetTests = []struct {
Operation: GREATER_THAN,
Value: "5",
},
map[string]interface{}{},
map[string]interface{}{"price": map[string]interface{}{"$gt": int64(5)}},
},
{
Expand All @@ -88,7 +80,6 @@ var converetTests = []struct {
Operation: GREATER_THAN_EQUAL,
Value: "5",
},
map[string]interface{}{},
map[string]interface{}{"price": map[string]interface{}{"$gte": int64(5)}},
},
{
Expand All @@ -98,7 +89,6 @@ var converetTests = []struct {
Operation: LESS_THAN,
Value: "5",
},
map[string]interface{}{},
map[string]interface{}{"price": map[string]interface{}{"$lt": int64(5)}},
},
{
Expand All @@ -108,7 +98,6 @@ var converetTests = []struct {
Operation: LESS_THAN_EQUAL,
Value: "5",
},
map[string]interface{}{},
map[string]interface{}{"price": map[string]interface{}{"$lte": int64(5)}},
},
{
Expand All @@ -118,14 +107,13 @@ var converetTests = []struct {
Operation: EXISTS,
Value: "",
},
map[string]interface{}{},
map[string]interface{}{"email": map[string]interface{}{"$exists": false}},
},
}

func TestShouldConvertFromSearchCriteria(t *testing.T) {
for _, test := range converetTests {
Convert(test.Criteria, test.Filter)
assert.Equal(t, test.Expected, test.Filter)
result := Convert(test.Criteria)
assert.Equal(t, test.Expected, result)
}
}
8 changes: 7 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,15 @@ module github.com/ajclopez/mgs

go 1.21.2

require (
github.com/antlr/antlr4/runtime/Go/antlr v1.4.10
github.com/antlr4-go/antlr/v4 v4.13.0
github.com/stretchr/testify v1.8.4
)

require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/stretchr/testify v1.8.4 // indirect
golang.org/x/exp v0.0.0-20230515195305-f3d0a9c9a5cc // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
7 changes: 7 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,9 +1,16 @@
github.com/antlr/antlr4/runtime/Go/antlr v1.4.10 h1:yL7+Jz0jTC6yykIK/Wh74gnTJnrGr5AyrNMXuA0gves=
github.com/antlr/antlr4/runtime/Go/antlr v1.4.10/go.mod h1:F7bn7fEU90QkQ3tnmaTx3LTKLEDqnwWODIYppRQ5hnY=
github.com/antlr4-go/antlr/v4 v4.13.0 h1:lxCg3LAv+EUK6t1i0y1V6/SLeUi0eKEKdhQAlS8TVTI=
github.com/antlr4-go/antlr/v4 v4.13.0/go.mod h1:pfChB/xh/Unjila75QW7+VU4TSnWnnk9UTnmpPaOR2g=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
golang.org/x/exp v0.0.0-20230515195305-f3d0a9c9a5cc h1:mCRnTeVUjcrhlRmO0VK8a6k6Rrf6TF9htwo2pJVSjIU=
golang.org/x/exp v0.0.0-20230515195305-f3d0a9c9a5cc/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
Loading

0 comments on commit 6c9632f

Please sign in to comment.