Skip to content
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
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ The following operators are added: (Credit goes to Ahmad Baitalmal)
* isnot null

Previously I was hoping to make the query as similar to SQL `WHERE` clause as possible. Later I found a problem parsing the term `PRIORITY>5`. The tokenizer split it as `PRI`, `OR`, `ITY`, `>`, `5`, since `OR` was then an operator, which is terribly wrong. At that point, I thought of two choices:
1. to force the query expression to contain at least one white space between tokens, thus `PRIORITY>5` should be written as `PRIORITY > 5`;
1. to force the query expression to contain at least one white space between tokens, thus `PRIORITY>5` should be written as `PRIORITY > 5`;
2. to replace operators as follows:

* `AND` to `&&`
Expand Down
36 changes: 30 additions & 6 deletions jsonql_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@ func TestParse(t *testing.T) {
"Golang",
"Java",
"C"
]
],
"comments": "",
"score": 40
},
{
"name": "enny",
Expand All @@ -25,7 +27,10 @@ func TestParse(t *testing.T) {
"IC",
"Electric design",
"Verification"
]
],
"comments": "2nd record",
"score": 10

},
{
"name": "sam",
Expand All @@ -34,7 +39,9 @@ func TestParse(t *testing.T) {
"Eating",
"Sleeping",
"Crawling"
]
],
"comments": "3rd record",
"score": 30
}
]
`
Expand All @@ -49,7 +56,20 @@ func TestParse(t *testing.T) {
}{
{"name='elgs'", 1},
{"gender='f'", 1},
{"comments='2nd record'", 1},
{"comments=\"\"", 1},
{"comments=\"\"", 1},
{"skills.[1]='Sleeping'", 1},
{"skills contains 'Sleeping'", 1},
{"skills contains 'Awake'", 0},
{"name contains 'lgs'", 1},
{"name contains 'e'", 2},
{"'Sleeping' in skills", 1},
{"'Verification' notin skills", 2},
{"'Awake' in skills", 0},
{"'lgs' in name", 1},
{"'e' in name", 2},
{"score > 10 && score < 40", 1},
}
var fail = []struct {
in string
Expand All @@ -61,9 +81,13 @@ func TestParse(t *testing.T) {
t.Error(v.in, err)
}
fmt.Println(v.in, result)
// if v.ex != result {
// t.Error("Expected:", v.ex, "actual:", result)
// }
numResults, ok := result.([]interface{})
if !ok {
t.Error("Failed to convert result")
}
if len(numResults) != v.ex {
t.Errorf("Got %v results, expected %v for `%v`", len(numResults), v.ex, v.in)
}
}
for range fail {

Expand Down
98 changes: 97 additions & 1 deletion sql_operators.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,48 @@ func evalToken(symbolTable interface{}, token string) (interface{}, error) {
return jq.Query(token)
}

func opIn(symbolTable interface{}, left string, right string) (string, error) {
l, lUndefined := evalToken(symbolTable, left)
if lUndefined != nil {
return "false", nil
}
ls := l.(string)

r, rUndefined := evalToken(symbolTable, right)
if rUndefined != nil {
return "false", nil
}

switch r.(type) {
case []interface{}:
jsn, _ := r.([]interface{})
for _, j := range jsn {
if s, ok := j.(string); ok {
if s == ls {
return "true", nil
}
}
}
return "false", nil
case interface{}:
str, ok := r.(string)
if !ok {
return "false", nil
}
contains := strings.Contains(str, ls)
if contains {
return "true", nil
}
return "false", nil

default:
return "false", nil
}

}

var sqlOperators = map[string]*Operator{
// Tokenizer will be responsible to put a space before and after each ')OR(', but not priORity.
// Tokenizer will be responsible to put a space before and after each ')OR(', but not priority.
"||": {
Precedence: 1,
Eval: func(symbolTable interface{}, left string, right string) (string, error) {
Expand Down Expand Up @@ -62,6 +102,62 @@ var sqlOperators = map[string]*Operator{
return strconv.FormatBool(l && r), nil
},
},
"contains": {
Precedence: 6,
Eval: func(symbolTable interface{}, left string, right string) (string, error) {
l, lUndefined := evalToken(symbolTable, left)
if lUndefined != nil {
return "false", nil
}
r, rUndefined := evalToken(symbolTable, right)
if rUndefined != nil {
return "false", nil
}
rs := r.(string)

switch l.(type) {
case []interface{}:
jsn, _ := l.([]interface{})
for _, j := range jsn {
if s, ok := j.(string); ok {
if s == rs {
return "true", nil
}
}
}
return "false", nil
case interface{}:
str, ok := l.(string)
if !ok {
return "false", nil
}
contains := strings.Contains(str, rs)
if contains {
return "true", nil
}
return "false", nil

default:
return "false", nil
}

},
},
"in": {
Precedence: 6,
Eval: opIn,
},
"notin": {
Precedence: 6,
Eval: func(symbolTable interface{}, left string, right string) (string, error) {
s, err := opIn(symbolTable, left, right)
if err != nil {
return s, err
}
b, _ := strconv.ParseBool(s)
return strconv.FormatBool(!b), err
},
},
"is": {
Precedence: 5,
Eval: func(symbolTable interface{}, left string, right string) (string, error) {
Expand Down