Skip to content

Commit 9c68597

Browse files
Implement $and, $or and $in
1 parent 156e8dc commit 9c68597

File tree

3 files changed

+192
-25
lines changed

3 files changed

+192
-25
lines changed

filter/converter.go

Lines changed: 66 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import (
77
"strings"
88
)
99

10-
var OperatorMap = map[string]string{
10+
var BasicOperatorMap = map[string]string{
1111
"$gt": ">",
1212
"$gte": ">=",
1313
}
@@ -36,15 +36,15 @@ func (c *Converter) Convert(query []byte) (string, []any, error) {
3636
return "", nil, err
3737
}
3838

39-
conditions, values, err := c.convertFilter(mongoFilter)
39+
conditions, values, err := c.convertFilter(mongoFilter, 0)
4040
if err != nil {
4141
return "", nil, err
4242
}
4343

4444
return conditions, values, nil
4545
}
4646

47-
func (c *Converter) convertFilter(filter map[string]any) (string, []any, error) {
47+
func (c *Converter) convertFilter(filter map[string]any, paramIndex int) (string, []any, error) {
4848
var conditions []string
4949
var values []any
5050

@@ -56,31 +56,75 @@ func (c *Converter) convertFilter(filter map[string]any) (string, []any, error)
5656

5757
for _, key := range keys {
5858
value := filter[key]
59-
switch v := value.(type) {
60-
case map[string]any:
61-
inner := []string{}
62-
operators := []string{}
63-
for operator := range v {
64-
operators = append(operators, operator)
59+
60+
switch key {
61+
case "$or", "$and":
62+
orConditions, ok := anyToSliceMapAny(value)
63+
if !ok {
64+
return "", nil, fmt.Errorf("invalid value for $or operator (must be array of objects): %v", value)
6565
}
66-
sort.Strings(operators)
67-
for _, operator := range operators {
68-
value := v[operator]
69-
op, ok := OperatorMap[operator]
70-
if !ok {
71-
return "", nil, fmt.Errorf("unknown operator: %s", operator)
66+
67+
inner := []string{}
68+
for _, orCondition := range orConditions {
69+
innerConditions, innerValues, err := c.convertFilter(orCondition, paramIndex)
70+
if err != nil {
71+
return "", nil, err
7272
}
73-
inner = append(inner, fmt.Sprintf("(%s %s $%d)", c.columnName(key), op, len(values)+1))
74-
values = append(values, value)
73+
paramIndex += len(innerValues)
74+
inner = append(inner, innerConditions)
75+
values = append(values, innerValues...)
76+
}
77+
op := "AND"
78+
if key == "$or" {
79+
op = "OR"
7580
}
76-
innerResult := strings.Join(inner, " AND ")
7781
if len(inner) > 1 {
78-
innerResult = "(" + innerResult + ")"
82+
conditions = append(conditions, "("+strings.Join(inner, " "+op+" ")+")")
83+
} else {
84+
conditions = append(conditions, strings.Join(inner, " "+op+" "))
7985
}
80-
conditions = append(conditions, innerResult)
8186
default:
82-
conditions = append(conditions, fmt.Sprintf("(%s = $%d)", c.columnName(key), len(values)+1))
83-
values = append(values, value)
87+
switch v := value.(type) {
88+
case map[string]any:
89+
inner := []string{}
90+
operators := []string{}
91+
for operator := range v {
92+
operators = append(operators, operator)
93+
}
94+
sort.Strings(operators)
95+
for _, operator := range operators {
96+
switch operator {
97+
case "$or":
98+
return "", nil, fmt.Errorf("$or as scalar operator not supported")
99+
case "$and":
100+
return "", nil, fmt.Errorf("$and as scalar operator not supported")
101+
case "$in":
102+
inner = append(inner, fmt.Sprintf("(%s = ANY(?))", c.columnName(key)))
103+
if !isScalarSlice(v[operator]) {
104+
return "", nil, fmt.Errorf("invalid value for $in operator (must array of primatives): %v", v[operator])
105+
}
106+
values = append(values, v[operator])
107+
default:
108+
value := v[operator]
109+
op, ok := BasicOperatorMap[operator]
110+
if !ok {
111+
return "", nil, fmt.Errorf("unknown operator: %s", operator)
112+
}
113+
paramIndex++
114+
inner = append(inner, fmt.Sprintf("(%s %s $%d)", c.columnName(key), op, paramIndex))
115+
values = append(values, value)
116+
}
117+
}
118+
innerResult := strings.Join(inner, " AND ")
119+
if len(inner) > 1 {
120+
innerResult = "(" + innerResult + ")"
121+
}
122+
conditions = append(conditions, innerResult)
123+
default:
124+
paramIndex++
125+
conditions = append(conditions, fmt.Sprintf("(%s = $%d)", c.columnName(key), paramIndex))
126+
values = append(values, value)
127+
}
84128
}
85129
}
86130

filter/converter_test.go

Lines changed: 80 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package filter_test
22

33
import (
4+
"fmt"
45
"reflect"
56
"testing"
67

@@ -76,19 +77,95 @@ func TestConverter_Convert(t *testing.T) {
7677
"in-array operator simple",
7778
nil,
7879
`{"status": {"$in": ["NEW", "OPEN"]}}`,
79-
`("status" IN ($1, $2))`,
80-
[]any{"NEW", "OPEN"},
80+
`("status" = ANY(?))`,
81+
[]any{[]any{"NEW", "OPEN"}},
82+
nil,
83+
},
84+
{
85+
"in-array operator invalid value",
86+
nil,
87+
`{"status": {"$in": [{"hacker": 1}, "OPEN"]}}`,
88+
``,
89+
nil,
90+
fmt.Errorf("invalid value for $in operator (must array of primatives): [map[hacker:1] OPEN]"),
91+
},
92+
{
93+
"in-array operator scalar value",
94+
nil,
95+
`{"status": {"$in": "text"}}`,
96+
``,
97+
nil,
98+
fmt.Errorf("invalid value for $in operator (must array of primatives): text"),
99+
},
100+
{
101+
"in-array operator with null value",
102+
nil,
103+
`{"status": {"$in": ["guest", null]}}`,
104+
`("status" = ANY(?))`,
105+
[]any{[]any{"guest", nil}},
106+
nil,
107+
},
108+
{
109+
"or operator basic",
110+
nil,
111+
`{"$or": [{"name": "John"}, {"name": "Doe"}]}`,
112+
`(("name" = $1) OR ("name" = $2))`,
113+
[]any{"John", "Doe"},
114+
nil,
115+
},
116+
{
117+
"or operator complex",
118+
nil,
119+
`{"$or": [{"org": "poki", "admin": true}, {"age": {"$gte": 18}}]}`,
120+
`((("admin" = $1) AND ("org" = $2)) OR ("age" >= $3))`,
121+
[]any{true, "poki", float64(18)},
122+
nil,
123+
},
124+
{
125+
"nested or",
126+
nil,
127+
`{"$or": [{"$or": [{"name": "John"}, {"name": "Doe"}]}, {"name": "Jane"}]}`,
128+
`((("name" = $1) OR ("name" = $2)) OR ("name" = $3))`,
129+
[]any{"John", "Doe", "Jane"},
130+
nil,
131+
},
132+
{
133+
"or in the wrong place",
134+
nil,
135+
`{"foo": { "$or": [ "bar", "baz" ] }}`,
136+
``,
137+
nil,
138+
fmt.Errorf("$or as scalar operator not supported"),
139+
},
140+
{
141+
"and operator basic",
142+
nil,
143+
`{"$and": [{"name": "John"}, {"version": 3}]}`,
144+
`(("name" = $1) AND ("version" = $2))`,
145+
[]any{"John", float64(3)},
146+
nil,
147+
},
148+
{
149+
"and operator in one object",
150+
nil,
151+
`{"$and": [{"name": "John", "version": 3}]}`,
152+
`(("name" = $1) AND ("version" = $2))`,
153+
[]any{"John", float64(3)},
81154
nil,
82155
},
83156
}
84157
for _, tt := range tests {
85158
t.Run(tt.name, func(t *testing.T) {
86159
c := filter.NewConverter(tt.option)
87160
conditions, values, err := c.Convert([]byte(tt.input))
88-
if err != tt.err {
161+
if err != nil && (tt.err == nil || err.Error() != tt.err.Error()) {
89162
t.Errorf("Converter.Convert() error = %v, wantErr %v", err, tt.err)
90163
return
91164
}
165+
if err == nil && tt.err != nil {
166+
t.Errorf("Converter.Convert() error = nil, wantErr %v", tt.err)
167+
return
168+
}
92169
if conditions != tt.conditions {
93170
t.Errorf("Converter.Convert() conditions:\n%v\nwant:\n%v", conditions, tt.conditions)
94171
}

filter/util.go

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package filter
2+
3+
func isScalar(v any) bool {
4+
if v == nil {
5+
return true
6+
}
7+
8+
switch v.(type) {
9+
case bool, float64, string:
10+
return true
11+
default:
12+
return false
13+
}
14+
}
15+
16+
func isScalarSlice(v any) bool {
17+
switch v := v.(type) {
18+
case []any:
19+
for _, e := range v {
20+
if !isScalar(e) {
21+
return false
22+
}
23+
}
24+
return true
25+
default:
26+
return false
27+
}
28+
}
29+
30+
func anyToSliceMapAny(v any) ([]map[string]any, bool) {
31+
switch v := v.(type) {
32+
case []any:
33+
var result []map[string]any
34+
for _, e := range v {
35+
switch e := e.(type) {
36+
case map[string]any:
37+
result = append(result, e)
38+
default:
39+
return nil, false
40+
}
41+
}
42+
return result, true
43+
default:
44+
return nil, false
45+
}
46+
}

0 commit comments

Comments
 (0)