Created
October 9, 2018 09:27
-
-
Save michaljemala/ee40726ef3c0045557dad7b2e2c58985 to your computer and use it in GitHub Desktop.
Mongo-like expressions
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package graphql | |
import ( | |
"fmt" | |
"strings" | |
"github.com/aintu/api-go-poc/orm" | |
) | |
const ( | |
and = "$and" | |
or = "$or" | |
equal = "$eq" | |
notEqual = "$neq" | |
greaterThan = "$gt" | |
greaterOrEqual = "$gte" | |
lowerThan = "$lt" | |
lowerOrEqual = "$lte" | |
in = "$in" | |
notIn = "$nin" | |
isNull = "$null" | |
isNotNull = "$nnull" | |
textInfix = "$textInfix" | |
textPrefix = "$textPrefix" | |
textSuffix = "$textSuffix" | |
textIInfix = "$textIInfix" | |
textIPrefix = "$textIPrefix" | |
textISuffix = "$textISuffix" | |
) | |
func ConvertExpression(exp map[string]interface{}) (orm.Condition, error) { | |
return convert(exp) | |
} | |
func convert(exp map[string]interface{}) (orm.Condition, error) { | |
var conds []orm.Condition | |
for key, val := range exp { | |
switch key { | |
case and: | |
c, err := convertAnd(val) | |
if err != nil { | |
return nil, err | |
} | |
conds = append(conds, c) | |
case or: | |
c, err := convertOr(val) | |
if err != nil { | |
return nil, err | |
} | |
conds = append(conds, c) | |
default: | |
if strings.HasPrefix(key, "$") { | |
return nil, fmt.Errorf("field %q cannot start with \"$\" sign", key) | |
} | |
c, err := convertField(key, val) | |
if err != nil { | |
return nil, err | |
} | |
conds = append(conds, c) | |
} | |
} | |
if len(conds) == 1 { | |
return conds[0], nil | |
} | |
return orm.And(conds...), nil | |
} | |
func convertAnd(i interface{}) (orm.Condition, error) { | |
exps, ok := i.([]interface{}) | |
if !ok { | |
return nil, fmt.Errorf("operator %q expects an array of expressions", and) | |
} | |
if len(exps) < 2 { | |
return nil, fmt.Errorf("operator %q must have at least two expressions", and) | |
} | |
var conds []orm.Condition | |
for _, e := range exps { | |
subexp, ok := e.(map[string]interface{}) | |
if !ok { | |
return nil, fmt.Errorf("invalid expresion: %#v", e) | |
} | |
condition, err := convert(subexp) | |
if err != nil { | |
return nil, err | |
} | |
conds = append(conds, condition) | |
} | |
return orm.And(conds...), nil | |
} | |
func convertOr(i interface{}) (orm.Condition, error) { | |
exps, ok := i.([]interface{}) | |
if !ok { | |
return nil, fmt.Errorf("operator %q expects an array of expressions", or) | |
} | |
if len(exps) < 2 { | |
return nil, fmt.Errorf("operator %q must have at least two expressions", or) | |
} | |
var conds []orm.Condition | |
for _, e := range exps { | |
subexp, ok := e.(map[string]interface{}) | |
if !ok { | |
return nil, fmt.Errorf("invalid expresion: %#v", e) | |
} | |
condition, err := convert(subexp) | |
if err != nil { | |
return nil, err | |
} | |
conds = append(conds, condition) | |
} | |
return orm.Or(conds...), nil | |
} | |
func convertField(key string, i interface{}) (orm.Condition, error) { | |
exp, ok := i.(map[string]interface{}) | |
if !ok { | |
return orm.NewPredicate(key, orm.Equal, i), nil | |
} | |
if len(exp) != 1 { | |
return nil, fmt.Errorf("invalid expresion: %#v", exp) | |
} | |
for op, val := range exp { | |
switch op { | |
case equal: | |
return orm.NewPredicate(key, orm.Equal, val), nil | |
case notEqual: | |
return orm.NewPredicate(key, orm.NotEqual, val), nil | |
case greaterThan: | |
return orm.NewPredicate(key, orm.GreaterThan, val), nil | |
case greaterOrEqual: | |
return orm.NewPredicate(key, orm.GreaterOrEqual, val), nil | |
case lowerThan: | |
return orm.NewPredicate(key, orm.LessThan, val), nil | |
case lowerOrEqual: | |
return orm.NewPredicate(key, orm.LessOrEqual, val), nil | |
case in: | |
return convertIn(key, val) | |
case notIn: | |
c, err := convertIn(key, val) | |
if err != nil { | |
return nil, err | |
} | |
return orm.Not(c), nil | |
case isNull: | |
return orm.NewPredicate(key, orm.IsNull), nil | |
case isNotNull: | |
return orm.Not(orm.NewPredicate(key, orm.IsNull)), nil | |
case textInfix: | |
return orm.NewPredicate(key, orm.Infix, val), nil | |
case textSuffix: | |
return orm.NewPredicate(key, orm.Suffix, val), nil | |
case textPrefix: | |
return orm.NewPredicate(key, orm.Prefix, val), nil | |
case textIInfix: | |
return orm.NewPredicate(key, orm.IInfix, val), nil | |
case textISuffix: | |
return orm.NewPredicate(key, orm.ISuffix, val), nil | |
case textIPrefix: | |
return orm.NewPredicate(key, orm.IPrefix, val), nil | |
} | |
} | |
return nil, fmt.Errorf("invalid expression: %#v", exp) | |
} | |
func convertIn(key string, i interface{}) (orm.Condition, error) { | |
vals, ok := i.([]interface{}) | |
if !ok { | |
return nil, fmt.Errorf("operator %q expects an array of expressions", in) | |
} | |
if len(vals) == 0 { | |
return nil, fmt.Errorf("operato %q must have at least one value", in) | |
} | |
return orm.NewPredicate(key, orm.In, vals...), nil | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package graphql | |
import ( | |
"fmt" | |
"reflect" | |
"testing" | |
"github.com/aintu/api-go-poc/orm" | |
) | |
func TestConvertExpression(t *testing.T) { | |
for i, data := range []struct { | |
s string | |
in map[string]interface{} | |
want orm.Condition | |
hasErr bool | |
}{ | |
{ | |
in: map[string]interface{}{ | |
"key1": 10, | |
"key2": 20, | |
}, | |
want: orm.And( | |
orm.NewPredicate("key1", orm.Equal, 10), | |
orm.NewPredicate("key2", orm.Equal, 20), | |
), | |
hasErr: false, | |
}, | |
{ | |
in: map[string]interface{}{ | |
"key1": 10, | |
"key2": map[string]interface{}{ | |
"$eq": 20, | |
}, | |
}, | |
want: orm.And( | |
orm.NewPredicate("key1", orm.Equal, 10), | |
orm.NewPredicate("key2", orm.Equal, 20), | |
), | |
hasErr: false, | |
}, | |
{ | |
in: map[string]interface{}{ | |
"$and": []interface{}{ | |
map[string]interface{}{"key1": 10}, | |
map[string]interface{}{"key2": 20}, | |
}, | |
}, | |
want: orm.And( | |
orm.NewPredicate("key1", orm.Equal, 10), | |
orm.NewPredicate("key2", orm.Equal, 20), | |
), | |
hasErr: false, | |
}, | |
{ | |
in: map[string]interface{}{ | |
"$or": []interface{}{ | |
map[string]interface{}{ | |
"key1": 10, | |
"key2": map[string]interface{}{ | |
"$in": []interface{}{"alpha", "beta", "gamma"}, | |
}, | |
}, | |
map[string]interface{}{ | |
"key3": map[string]interface{}{ | |
"$gt": 0, | |
}, | |
}, | |
}, | |
}, | |
want: orm.Or( | |
orm.And( | |
orm.NewPredicate("key1", orm.Equal, 10), | |
orm.NewPredicate("key2", orm.In, "alpha", "beta", "gamma"), | |
), | |
orm.NewPredicate("key3", orm.GreaterThan, 0), | |
), | |
hasErr: false, | |
}, | |
{ | |
in: map[string]interface{}{ | |
"$eq": 10, | |
}, | |
hasErr: true, | |
}, | |
} { | |
t.Run(fmt.Sprintf("%d", i), func(t *testing.T) { | |
has, err := ConvertExpression(data.in) | |
if err != nil && !data.hasErr { | |
t.Error(err) | |
return | |
} | |
if !reflect.DeepEqual(data.want, has) { | |
t.Errorf("want %#v, has %#v", data.want, has) | |
} | |
}) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment