Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[dev] aliases feature #6

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 28 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,4 +70,31 @@ For those operations, both start and end values can be provided
}
```

if only *start* or *end* provided, the operation will change to *less* or *greater* automatically
if only *start* or *end* provided, the operation will change to *less* or *greater* automatically


### aliases

The rule can has alias value. This means that the value of the rule will be replaced by the alias value

```json
{
"field":"age",
"alias": "userId",
"condition":{
"type": "between",
"filter": { "start": 10, "end": 99 }
}
}
```

The **alias:value** hash is passed with the filter config

```json
{
"userId": 3,
"ids": [1, 2, 3]
}
```

This feature allows to reuse a filter config as templates and replace the values of aliases with any others
12 changes: 8 additions & 4 deletions condition.go
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
package querysql

type Condition struct {
Rule string `json:"type"`
Rule string `json:"type"`
Value interface{} `json:"filter"`
}

func (c *Condition) getValues() []interface{} {
valueMap, ok := c.Value.(map[string]interface{})
return getValues(c.Value)
}

func getValues(v interface{}) []interface{} {
valueMap, ok := v.(map[string]interface{})
if !ok {
return []interface{}{c.Value}
return []interface{}{v}
}

return []interface{}{valueMap["start"], valueMap["end"]}
}
}
33 changes: 28 additions & 5 deletions sql.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ type Filter struct {
Field string `json:"field"`
Condition Condition `json:"condition"`
Includes []interface{} `json:"includes"`
Alias string `json:"alias"`
Kids []Filter `json:"rules"`
}

Expand Down Expand Up @@ -54,7 +55,7 @@ func inSQL(field string, data []interface{}, db DBDriver) (string, []interface{}
return sql, data, nil
}

func GetSQL(data Filter, config *SQLConfig, dbArr ...DBDriver) (string, []interface{}, error) {
func GetSQL(data Filter, aliases map[string]interface{}, config *SQLConfig, dbArr ...DBDriver) (string, []interface{}, error) {
var db DBDriver
if len(dbArr) > 0 {
db = dbArr[0]
Expand All @@ -73,11 +74,33 @@ func GetSQL(data Filter, config *SQLConfig, dbArr ...DBDriver) (string, []interf

name, isDynamicField := db.IsJSON(data.Field)

if len(data.Includes) > 0 {
return inSQL(name, data.Includes, db)
var values []interface{}
var includes bool
if data.Alias != "" && len(aliases) > 0 {
aliasValue, ok := aliases[data.Alias]
if ok {
// get alias values
values, includes = aliasValue.([]interface{})
if !includes {
values = getValues(aliasValue)
}
}
}

if values == nil {
// get filter values
if len(data.Includes) > 0 {
includes = true
values = data.Includes
} else {
values = data.Condition.getValues()
}
}

if includes {
return inSQL(name, values, db)
}

values := data.Condition.getValues()
switch data.Condition.Rule {
case "":
return "", NoValues, nil
Expand Down Expand Up @@ -145,7 +168,7 @@ func GetSQL(data Filter, config *SQLConfig, dbArr ...DBDriver) (string, []interf
values := make([]interface{}, 0)

for _, r := range data.Kids {
subSql, subValues, err := GetSQL(r, config, db)
subSql, subValues, err := GetSQL(r, aliases, config, db)
if err != nil {
return "", nil, err
}
Expand Down
122 changes: 105 additions & 17 deletions sql_test.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
package querysql

import (
"encoding/json"
"fmt"
"strconv"
"strings"
"testing"
)

var aAndB = `{ "glue":"and", "rules":[{ "field": "a", "condition":{ "type":"less", "filter":1}}, { "field": "b", "condition":{ "type":"greater", "filter":"abc" }}]}`
var aAndB = `{ "glue":"and", "rules":[{ "field": "a", "alias": "a", "condition":{ "type":"less", "filter":1}}, { "field": "b", "alias": "b", "condition":{ "type":"greater", "filter":"abc" }}]}`
var aOrB = `{ "glue":"or", "rules":[{ "field": "a", "condition":{ "type":"less", "filter":1}}, { "field": "b", "condition":{ "type":"greater", "filter":"abc" }}]}`
var cOrC = `{ "glue":"or", "rules":[{ "field": "a", "condition":{ "type":"is null" }}, { "field": "b", "condition":{ "type":"range100", "filter":500 }}]}`
var JSONaAndB = `{ "glue":"and", "rules":[{ "field": "json:cfg.a", "condition":{ "type":"less", "filter":1}}, { "field": "json:cfg.b", "condition":{ "type":"greater", "filter":"abc" }}]}`
Expand Down Expand Up @@ -212,6 +213,55 @@ var psqlCases = [][]string{
},
}

var casesWithAliases = [][]string{
{
`{ "glue":"and", "rules":[{ "field": "a", "alias": "userId", "condition":{ "type":"equal", "filter":1 }}]}`,
"a = ?",
"a = $1",
"3",
`{ "userId": "3" }`, // aliases
},
{
`{ "glue":"and", "rules":[{ "field": "a", "alias": "userId", "condition":{ "type":"equal", "filter":1 }}]}`,
"a = ?",
"a = $1",
"1",
`{}`, // aliases
},
{
`{ "glue":"and", "rules":[{ "field": "a", "alias": "userId", "condition":{ "type":"notEqual", "filter":1 }}]}`,
"a <> ?",
"a <> $1",
"3",
`{ "userId": 3 }`, // aliases
},
{
aAndB,
"( a < ? AND b > ? )",
"( a < $1 AND b > $2 )",
"4,def",
// aliases
`{
"a": 4,
"b": "def"
}`,
},
{
`{ "glue":"and", "rules":[{ "field": "a", "alias": "ids", "includes":[1,2,3]}]}`,
"a IN(?,?,?)",
"a IN($1,$2,$3)",
"3,4,5",
`{ "ids": [3,4,5] }`, // aliases
},
{
`{ "glue":"and", "rules":[{ "field": "a", "alias": "ids", "includes":["a","b","c"]}]}`,
"a IN(?,?,?)",
"a IN($1,$2,$3)",
"c,d,e",
`{ "ids": ["c","d","e"] }`, // aliases
},
}

func anyToStringArray(some []interface{}) (string, error) {
out := make([]string, 0, len(some))
for _, x := range some {
Expand Down Expand Up @@ -241,7 +291,7 @@ func TestSQL(t *testing.T) {
continue
}

sql, vals, err := GetSQL(format, nil)
sql, vals, err := GetSQL(format, nil, nil)
if err != nil {
t.Errorf("can't generate sql\nj: %s\n%f", line[0], err)
continue
Expand Down Expand Up @@ -279,7 +329,7 @@ func TestPostgre(t *testing.T) {
continue
}

sql, vals, err := GetSQL(format, &queryConfig, &PostgreSQL{})
sql, vals, err := GetSQL(format, nil, &queryConfig, &PostgreSQL{})
if err != nil {
t.Errorf("can't generate sql\nj: %s\n%f", line[0], err)
continue
Expand Down Expand Up @@ -317,7 +367,7 @@ func TestPostgreJSON(t *testing.T) {
continue
}

sql, vals, err := GetSQL(format, &queryConfig, &PostgreSQL{})
sql, vals, err := GetSQL(format, nil, &queryConfig, &PostgreSQL{})
if err != nil {
t.Errorf("can't generate sql\nj: %s\n%f", line[0], err)
continue
Expand Down Expand Up @@ -347,31 +397,31 @@ func TestWhitelist(t *testing.T) {
return
}

_, _, err = GetSQL(format, nil)
_, _, err = GetSQL(format, nil, nil)
if err != nil {
t.Errorf("doesn't work without config")
return
}

_, _, err = GetSQL(format, &SQLConfig{})
_, _, err = GetSQL(format, nil, &SQLConfig{})
if err != nil {
t.Errorf("doesn't work without whitelist")
return
}

_, _, err = GetSQL(format, &SQLConfig{Whitelist: map[string]bool{"a": true, "b": true}})
_, _, err = GetSQL(format, nil, &SQLConfig{Whitelist: map[string]bool{"a": true, "b": true}})
if err != nil {
t.Errorf("doesn't work with fields allowed")
return
}

_, _, err = GetSQL(format, &SQLConfig{Whitelist: map[string]bool{"a": true}})
_, _, err = GetSQL(format, nil, &SQLConfig{Whitelist: map[string]bool{"a": true}})
if err == nil {
t.Errorf("doesn't return error when field is not allowed")
return
}

_, _, err = GetSQL(format, &SQLConfig{Whitelist: map[string]bool{"b": true}})
_, _, err = GetSQL(format, nil, &SQLConfig{Whitelist: map[string]bool{"b": true}})
if err == nil {
t.Errorf("doesn't return error when field is not allowed")
return
Expand All @@ -385,19 +435,19 @@ func TestWhitelistPG(t *testing.T) {
return
}

_, _, err = GetSQL(format, nil, &PostgreSQL{})
_, _, err = GetSQL(format, nil, nil, &PostgreSQL{})
if err != nil {
t.Errorf("doesn't work without config")
return
}

_, _, err = GetSQL(format, &SQLConfig{}, &PostgreSQL{})
_, _, err = GetSQL(format, nil, &SQLConfig{}, &PostgreSQL{})
if err != nil {
t.Errorf("doesn't work without whitelist")
return
}

_, _, err = GetSQL(format, &SQLConfig{
_, _, err = GetSQL(format, nil, &SQLConfig{
WhitelistFunc: func(name string) bool {
return strings.HasPrefix(name, "json:cfg.")
}})
Expand All @@ -406,7 +456,7 @@ func TestWhitelistPG(t *testing.T) {
return
}

_, _, err = GetSQL(format, &SQLConfig{
_, _, err = GetSQL(format, nil, &SQLConfig{
WhitelistFunc: func(name string) bool {
return strings.HasPrefix(name, "json:cfg.a")
},
Expand All @@ -417,23 +467,23 @@ func TestWhitelistPG(t *testing.T) {
return
}

_, _, err = GetSQL(format, &SQLConfig{
_, _, err = GetSQL(format, nil, &SQLConfig{
Whitelist: map[string]bool{"json:cfg.a": true, "json:cfg.b": true},
})
if err != nil {
t.Errorf("doesn't work with fields allowed")
return
}

_, _, err = GetSQL(format, &SQLConfig{
_, _, err = GetSQL(format, nil, &SQLConfig{
Whitelist: map[string]bool{"json:cfg.b": true},
})
if err == nil {
t.Errorf("doesn't return error when field is not allowed")
return
}

_, _, err = GetSQL(format, &SQLConfig{
_, _, err = GetSQL(format, nil, &SQLConfig{
WhitelistFunc: func(name string) bool {
return strings.HasPrefix(name, "json:cfgx")
},
Expand All @@ -451,7 +501,7 @@ func TestCustomOperation(t *testing.T) {
return
}

sql, vals, err := GetSQL(format, &SQLConfig{
sql, vals, err := GetSQL(format, nil, &SQLConfig{
Operations: map[string]CustomOperation{
"is null": func(n string, r string, values []interface{}) (string, []interface{}, error) {
return fmt.Sprintf("%s IS NULL", n), NoValues, nil
Expand Down Expand Up @@ -486,3 +536,41 @@ func TestCustomOperation(t *testing.T) {
return
}
}

func TestAliases(t *testing.T) {
for _, line := range casesWithAliases {
format, err := FromJSON([]byte(line[0]))
if err != nil {
t.Errorf("can't parse json\nj: %s\n%f", line[0], err)
continue
}

aliases := make(map[string]interface{})
err = json.Unmarshal([]byte(line[4]), &aliases)
if err != nil {
t.Errorf("can't parse json aliases\nj: %s\n%f", line[4], err)
continue
}

sql, vals, err := GetSQL(format, aliases, nil)
if err != nil {
t.Errorf("can't generate sql\nj: %s\n%f", line[0], err)
continue
}
if sql != line[1] {
t.Errorf("wrong sql generated\nj: %s\ns: %s\nr: %s", line[0], line[1], sql)
continue
}

valsStr, err := anyToStringArray(vals)
if err != nil {
t.Errorf("can't convert parameters\nj: %s\n%f", line[0], err)
continue
}

if valsStr != line[3] {
t.Errorf("wrong sql generated (values)\nj: %s\ns: %s\nr: %s", line[0], line[3], valsStr)
continue
}
}
}