├── .gitignore ├── LICENSE ├── README.md ├── exparser.go ├── exparser_test.go ├── jsonql.go ├── jsonql_test.go ├── lifo.go ├── lifo_test.go ├── sql_operators.go ├── utils.go └── utils_test.go /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | *.exe 23 | *.test 24 | *.prof 25 | 26 | .DS_Store 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | #License 2 | ISC License (ISC) 3 | 4 | Copyright (c) 2014, Elgs Qian Chen 5 | 6 | Permission to use, copy, modify, and/or distribute this software for any 7 | purpose with or without fee is hereby granted, provided that the above 8 | copyright notice and this permission notice appear in all copies. 9 | 10 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 11 | REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 12 | AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 13 | INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 14 | LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR 15 | OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 16 | PERFORMANCE OF THIS SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # jsonql 2 | JSON query expression library in Golang. 3 | 4 | This library enables query against JSON. Currently supported operators are: (precedences from low to high) 5 | 6 | ``` 7 | || 8 | && 9 | = != > < >= <= ~= !~= is isnot contains 10 | + - 11 | * / % 12 | ^ 13 | ( ) 14 | ``` 15 | 16 | The following are the operator mapings to SQL: 17 | * `AND` to `&&` 18 | * `OR` to `||` 19 | * `RLIKE` to `~=` 20 | * `NOT RLIKE` to `!~=` 21 | 22 | ## Install 23 | `go get -u github.com/elgs/jsonql` 24 | 25 | ## Example 26 | ```go 27 | package main 28 | 29 | import ( 30 | "fmt" 31 | 32 | "github.com/elgs/jsonql" 33 | ) 34 | 35 | var jsonString = ` 36 | [ 37 | { 38 | "name": "elgs", 39 | "gender": "m", 40 | "age": 35, 41 | "skills": [ 42 | "Golang", 43 | "Java", 44 | "C" 45 | ] 46 | }, 47 | { 48 | "name": "enny", 49 | "gender": "f", 50 | "age": 36, 51 | "hobby": null, 52 | "skills": [ 53 | "IC", 54 | "Electric design", 55 | "Verification" 56 | ] 57 | }, 58 | { 59 | "name": "sam", 60 | "gender": "m", 61 | "age": 1, 62 | "hobby": "dancing", 63 | "skills": [ 64 | "Eating", 65 | "Sleeping", 66 | "Crawling" 67 | ] 68 | } 69 | ] 70 | ` 71 | 72 | func main() { 73 | parser, err := jsonql.NewStringQuery(jsonString) 74 | if err != nil { 75 | fmt.Println(err) 76 | return 77 | } 78 | fmt.Println(parser.Query("name='elgs'")) 79 | //[map[skills:[Golang Java C] name:elgs gender:m age:35]] 80 | 81 | fmt.Println(parser.Query("name='elgs' && gender='f'")) 82 | //[] 83 | 84 | fmt.Println(parser.Query("age<10 || (name='enny' && gender='f')")) 85 | // [map[hobby: skills:[IC Electric design Verification] name:enny gender:f age:36] map[name:sam gender:m age:1 hobby:dancing skills:[Eating Sleeping Crawling]]] 86 | 87 | fmt.Println(parser.Query("age<10")) 88 | // [map[gender:m age:1 hobby:dancing skills:[Eating Sleeping Crawling] name:sam]] 89 | 90 | fmt.Println(parser.Query("1=0")) 91 | //[] 92 | 93 | fmt.Println(parser.Query("age=(2*3)^2")) 94 | //[map[skills:[IC Electric design Verification] name:enny gender:f age:36 hobby:]] 95 | 96 | fmt.Println(parser.Query("name ~= 'e.*'")) 97 | // [map[name:elgs gender:m age:35 skills:[Golang Java C]] map[hobby: skills:[IC Electric design Verification] name:enny gender:f age:36]] 98 | 99 | fmt.Println(parser.Query("name='el'+'gs'")) 100 | fmt.Println(parser.Query("age=30+5.0")) 101 | fmt.Println(parser.Query("age=40.0-5")) 102 | fmt.Println(parser.Query("age=70-5*7")) 103 | fmt.Println(parser.Query("age=70.0/2.0")) 104 | fmt.Println(parser.Query("age=71%36")) 105 | // [map[name:elgs gender:m age:35 skills:[Golang Java C]]] 106 | 107 | fmt.Println(parser.Query("hobby is defined")) 108 | // [map[name:enny gender:f age:36 hobby: skills:[IC Electric design Verification]] map[name:sam gender:m age:1 hobby:dancing skills:[Eating Sleeping Crawling]]] 109 | 110 | fmt.Println(parser.Query("hobby isnot defined")) 111 | // [map[name:sam gender:m age:1 skills:[Eating Sleeping Crawling]]] 112 | 113 | fmt.Println(parser.Query("hobby is null")) 114 | // [map[hobby: skills:[IC Electric design Verification] name:enny gender:f age:36]] 115 | 116 | fmt.Println(parser.Query("hobby isnot null")) 117 | // [map[name:sam gender:m age:1 hobby:dancing skills:[Eating Sleeping Crawling]]] 118 | 119 | fmt.Println(parser.Query("skills contains 'Eating'")) 120 | // [map[age:1 gender:m hobby:dancing name:sam skills:[Eating Sleeping Crawling]]] 121 | } 122 | ``` 123 | 124 | ## Query Expressions 125 | For details of query expressions, please read: https://github.com/elgs/gojq -------------------------------------------------------------------------------- /exparser.go: -------------------------------------------------------------------------------- 1 | package jsonql 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "strings" 7 | "unicode" 8 | ) 9 | 10 | // Operator - encapsulates the Precedence and behavior logic of the operators. 11 | type Operator struct { 12 | Precedence int 13 | Eval func(symbolTable interface{}, left string, right string) (string, error) 14 | } 15 | 16 | // Parser - the main struct that contains operators, symbol table. 17 | type Parser struct { 18 | Operators map[string]*Operator 19 | SymbolTable interface{} 20 | maxOpLen int 21 | initialized bool 22 | } 23 | 24 | // Init - init inspects the Operators and learns how long the longest operator string is 25 | func (thisParser *Parser) Init() { 26 | for k := range thisParser.Operators { 27 | if len(k) > thisParser.maxOpLen { 28 | thisParser.maxOpLen = len(k) 29 | } 30 | } 31 | } 32 | 33 | // Calculate - gets the final result of the expression 34 | func (thisParser *Parser) Calculate(expression string) (string, error) { 35 | tokens := thisParser.Tokenize(expression) 36 | //fmt.Println(expression, tokens) 37 | rpn, err := thisParser.ParseRPN(tokens) 38 | if err != nil { 39 | return "", err 40 | } 41 | return thisParser.Evaluate(rpn, true) 42 | } 43 | 44 | // Evaluate - evaluates the token stack until only one (as the final result) is left. 45 | func (thisParser *Parser) Evaluate(ts *Lifo, postfix bool) (string, error) { 46 | newTs := &Lifo{} 47 | usefulWork := false 48 | for ti := ts.Pop(); ti != nil; ti = ts.Pop() { 49 | t := ti.(string) 50 | // fmt.Println("t:", t) 51 | switch { 52 | case thisParser.Operators[t] != nil: 53 | // operators 54 | usefulWork = true 55 | if postfix { 56 | right := newTs.Pop() 57 | left := newTs.Pop() 58 | l := "0" 59 | r := "0" 60 | if left != nil { 61 | l = left.(string) 62 | } 63 | if right != nil { 64 | r = right.(string) 65 | } 66 | result, err := thisParser.Operators[t].Eval(thisParser.SymbolTable, l, r) 67 | newTs.Push(result) 68 | if err != nil { 69 | return "", errors.New(fmt.Sprint("Failed to evaluate:", l, t, r) + " " + err.Error()) 70 | } 71 | } else { 72 | right := ts.Pop() 73 | left := ts.Pop() 74 | l := "" 75 | r := "" 76 | if left != nil { 77 | l = left.(string) 78 | } 79 | if right != nil { 80 | r = right.(string) 81 | } 82 | result, err := thisParser.Operators[t].Eval(thisParser.SymbolTable, l, r) 83 | newTs.Push(result) 84 | if err != nil { 85 | return "", errors.New(fmt.Sprint("Failed to evaluate:", l, t, r) + " " + err.Error()) 86 | } 87 | } 88 | default: 89 | // operands 90 | newTs.Push(t) 91 | } 92 | //newTs.Print() 93 | } 94 | if !usefulWork { 95 | return "", errors.New("Failed to evaluate: no valid operator found.") 96 | } 97 | if newTs.Len() == 1 { 98 | return newTs.Pop().(string), nil 99 | } 100 | return thisParser.Evaluate(newTs, !postfix) 101 | } 102 | 103 | // false o1 in first, true o2 out first 104 | func (thisParser *Parser) shunt(o1, o2 string) (bool, error) { 105 | op1 := thisParser.Operators[o1] 106 | op2 := thisParser.Operators[o2] 107 | if op1 == nil || op2 == nil { 108 | return false, errors.New(fmt.Sprint("Invalid operators:", o1, o2)) 109 | } 110 | if op1.Precedence < op2.Precedence || (op1.Precedence <= op2.Precedence && op1.Precedence%2 == 1) { 111 | return true, nil 112 | } 113 | return false, nil 114 | } 115 | 116 | // ParseRPN - parses the RPN tokens 117 | func (thisParser *Parser) ParseRPN(tokens []string) (output *Lifo, err error) { 118 | opStack := &Lifo{} 119 | outputQueue := []string{} 120 | for _, token := range tokens { 121 | switch { 122 | case thisParser.Operators[token] != nil: 123 | // operator 124 | for o2 := opStack.Peep(); o2 != nil; o2 = opStack.Peep() { 125 | stackToken := o2.(string) 126 | if thisParser.Operators[stackToken] == nil { 127 | break 128 | } 129 | o2First, err := thisParser.shunt(token, stackToken) 130 | if err != nil { 131 | return output, err 132 | } 133 | if o2First { 134 | outputQueue = append(outputQueue, opStack.Pop().(string)) 135 | } else { 136 | break 137 | } 138 | } 139 | opStack.Push(token) 140 | case token == "(": 141 | opStack.Push(token) 142 | case token == ")": 143 | for o2 := opStack.Pop(); o2 != nil && o2.(string) != "("; o2 = opStack.Pop() { 144 | outputQueue = append(outputQueue, o2.(string)) 145 | } 146 | default: 147 | outputQueue = append(outputQueue, token) 148 | } 149 | } 150 | for o2 := opStack.Pop(); o2 != nil; o2 = opStack.Pop() { 151 | outputQueue = append(outputQueue, o2.(string)) 152 | } 153 | //fmt.Println(outputQueue) 154 | output = &Lifo{} 155 | for i := 0; i < len(outputQueue); i++ { 156 | (*output).Push(outputQueue[len(outputQueue)-i-1]) 157 | } 158 | return 159 | } 160 | 161 | // Tokenize - splits the expression into tokens. 162 | func (thisParser *Parser) Tokenize(exp string) (tokens []string) { 163 | if !thisParser.initialized { 164 | thisParser.Init() 165 | } 166 | sq, dq := false, false 167 | var tmp string 168 | expRunes := []rune(exp) 169 | for i := 0; i < len(expRunes); i++ { 170 | v := expRunes[i] 171 | s := string(v) 172 | switch { 173 | case unicode.IsSpace(v): 174 | if sq || dq { 175 | tmp += s 176 | } else if len(tmp) > 0 { 177 | tokens = append(tokens, tmp) 178 | tmp = "" 179 | } 180 | case s == "'": 181 | tmp += s 182 | if !dq { 183 | sq = !sq 184 | if !sq { 185 | tokens = append(tokens, tmp) 186 | tmp = "" 187 | } 188 | } 189 | case s == "\"": 190 | tmp += s 191 | if !sq { 192 | dq = !dq 193 | if !dq { 194 | tokens = append(tokens, tmp) 195 | tmp = "" 196 | } 197 | } 198 | case s == "+" || s == "-" || s == "(" || s == ")": 199 | if sq || dq { 200 | tmp += s 201 | } else { 202 | if len(tmp) > 0 { 203 | tokens = append(tokens, tmp) 204 | tmp = "" 205 | } 206 | lastToken := "" 207 | if len(tokens) > 0 { 208 | lastToken = tokens[len(tokens)-1] 209 | } 210 | if (s == "+" || s == "-") && (len(tokens) == 0 || lastToken == "(" || thisParser.Operators[lastToken] != nil) { 211 | // sign 212 | tmp += s 213 | } else { 214 | // operator 215 | tokens = append(tokens, s) 216 | } 217 | } 218 | default: 219 | if sq || dq { 220 | tmp += s 221 | } else { 222 | // until the max length of operators(n), check if next 1..n runes are operator, greedily 223 | opCandidateTmp := "" 224 | opCandidate := "" 225 | for j := 0; j < thisParser.maxOpLen && i < len(expRunes)-j-1; j++ { 226 | next := string(expRunes[i+j]) 227 | opCandidateTmp += strings.ToUpper(next) 228 | if thisParser.Operators[opCandidateTmp] != nil { 229 | opCandidate = opCandidateTmp 230 | } 231 | } 232 | if len(opCandidate) > 0 { 233 | if len(tmp) > 0 { 234 | tokens = append(tokens, tmp) 235 | tmp = "" 236 | } 237 | tokens = append(tokens, opCandidate) 238 | i += len(opCandidate) - 1 239 | } else { 240 | tmp += s 241 | } 242 | } 243 | } 244 | } 245 | if len(tmp) > 0 { 246 | tokens = append(tokens, tmp) 247 | tmp = "" 248 | } 249 | return 250 | } 251 | -------------------------------------------------------------------------------- /exparser_test.go: -------------------------------------------------------------------------------- 1 | package jsonql 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestTokenize(t *testing.T) { 8 | 9 | var pass = []struct { 10 | in string 11 | ex []string 12 | }{ 13 | {"-1 + 2", []string{"-1", "+", "2"}}, 14 | {"-(1+2)", []string{"-", "(", "1", "+", "2", ")"}}, 15 | {"+1+2", []string{"+1", "+", "2"}}, 16 | {"1+2+(3*4)", []string{"1", "+", "2", "+", "(", "3", "*", "4", ")"}}, 17 | {"1+2+(3*4)^34", []string{"1", "+", "2", "+", "(", "3", "*", "4", ")", "^", "34"}}, 18 | {"'123 456' 789", []string{"'123 456'", "789"}}, 19 | {`123 "456 789"`, []string{"123", "\"456 789\""}}, 20 | {`123 "456 '''789"`, []string{"123", "\"456 '''789\""}}, 21 | {`2*x+y is null`, []string{"2", "*", "x", "+", "y", "is", "null"}}, 22 | } 23 | var fail = []struct { 24 | in string 25 | ex []string 26 | }{} 27 | parser := &Parser{ 28 | Operators: sqlOperators, 29 | } 30 | for _, v := range pass { 31 | tokens := parser.Tokenize(v.in) 32 | if !CompareSlices(tokens, v.ex) { 33 | t.Error("Expected:", v.ex, len(v.ex), "actual:", tokens, len(tokens)) 34 | } 35 | } 36 | for _, v := range fail { 37 | tokens := parser.Tokenize(v.in) 38 | if CompareSlices(tokens, v.ex) { 39 | t.Error("Expected:", v.ex, "actual:", tokens) 40 | } 41 | } 42 | } 43 | 44 | func TestTokenizeSql(t *testing.T) { 45 | var pass = []struct { 46 | in string 47 | ex []string 48 | }{ 49 | {"true AND false", []string{"true", "AND", "false"}}, 50 | } 51 | var fail = []struct { 52 | in string 53 | ex []string 54 | }{} 55 | parser := &Parser{ 56 | Operators: sqlOperators, 57 | } 58 | for _, v := range pass { 59 | tokens := parser.Tokenize(v.in) 60 | if !CompareSlices(tokens, v.ex) { 61 | t.Error("Expected:", v.ex, "actual:", tokens) 62 | } 63 | } 64 | for range fail { 65 | 66 | } 67 | } 68 | 69 | func TestRPN(t *testing.T) { 70 | var pass = []struct { 71 | in string 72 | ex string 73 | }{ 74 | {"true && false", "false"}, 75 | {"true && true", "true"}, 76 | {"false && false", "false"}, 77 | {"true || true", "true"}, 78 | {"true || (true && false)", "true"}, 79 | {"true && (false && true)", "false"}, 80 | {"(true && false) || (false || true)", "true"}, 81 | } 82 | var fail = []struct { 83 | in string 84 | ex []string 85 | }{} 86 | parser := &Parser{ 87 | Operators: sqlOperators, 88 | } 89 | 90 | for _, v := range pass { 91 | result, err := parser.Calculate(v.in) 92 | if err != nil { 93 | t.Error(err) 94 | } 95 | if result != v.ex { 96 | t.Error(v.in, "Expected:", v.ex, "actual:", result) 97 | } 98 | } 99 | for range fail { 100 | 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /jsonql.go: -------------------------------------------------------------------------------- 1 | package jsonql 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "strconv" 7 | ) 8 | 9 | // JSONQL - JSON Query Lang struct encapsulating the JSON data. 10 | type JSONQL struct { 11 | Data interface{} 12 | } 13 | 14 | // NewStringQuery - creates a new &JSONQL from raw JSON string 15 | func NewStringQuery(jsonString string) (*JSONQL, error) { 16 | var data = new(interface{}) 17 | err := json.Unmarshal([]byte(jsonString), data) 18 | if err != nil { 19 | return nil, err 20 | } 21 | return &JSONQL{*data}, nil 22 | } 23 | 24 | // NewQuery - creates a new &JSONQL from an array of interface{} or a map of [string]interface{} 25 | func NewQuery(jsonObject interface{}) *JSONQL { 26 | return &JSONQL{jsonObject} 27 | } 28 | 29 | // Query - queries against the JSON using the conditions specified in the where stirng. 30 | func (thisJSONQL *JSONQL) Query(where string) (interface{}, error) { 31 | parser := &Parser{ 32 | Operators: sqlOperators, 33 | } 34 | tokens := parser.Tokenize(where) 35 | rpn, err := parser.ParseRPN(tokens) 36 | if err != nil { 37 | return nil, err 38 | } 39 | switch v := thisJSONQL.Data.(type) { 40 | case []interface{}: 41 | ret := []interface{}{} 42 | for _, obj := range v { 43 | parser.SymbolTable = obj 44 | r, err := thisJSONQL.processObj(parser, *rpn) 45 | if err != nil { 46 | return nil, err 47 | } 48 | if r { 49 | ret = append(ret, obj) 50 | } 51 | } 52 | return ret, nil 53 | case map[string]interface{}: 54 | parser.SymbolTable = v 55 | r, err := thisJSONQL.processObj(parser, *rpn) 56 | if err != nil { 57 | return nil, err 58 | } 59 | if r { 60 | return v, nil 61 | } 62 | return nil, nil 63 | default: 64 | return nil, fmt.Errorf("Failed to parse input data.") 65 | } 66 | } 67 | 68 | func (thisJSONQL *JSONQL) processObj(parser *Parser, rpn Lifo) (bool, error) { 69 | result, err := parser.Evaluate(&rpn, true) 70 | if err != nil { 71 | return false, nil 72 | } 73 | return strconv.ParseBool(result) 74 | } 75 | -------------------------------------------------------------------------------- /jsonql_test.go: -------------------------------------------------------------------------------- 1 | package jsonql 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | ) 7 | 8 | func TestParse(t *testing.T) { 9 | 10 | jsonString := ` 11 | [ 12 | { 13 | "name": "elgs", 14 | "gender": "m", 15 | "skills": [ 16 | "Golang", 17 | "Java", 18 | "C" 19 | ] 20 | }, 21 | { 22 | "name": "enny", 23 | "gender": "f", 24 | "skills": [ 25 | "IC", 26 | "Electric design", 27 | "Verification" 28 | ] 29 | }, 30 | { 31 | "name": "sam", 32 | "gender": "m", 33 | "skills": [ 34 | "Eating", 35 | "Sleeping", 36 | "Crawling" 37 | ] 38 | } 39 | ] 40 | ` 41 | parser, err := NewStringQuery(jsonString) 42 | if err != nil { 43 | t.Error(err) 44 | } 45 | 46 | var pass = []struct { 47 | in string 48 | }{ 49 | {"name='elgs'"}, 50 | {"gender='f'"}, 51 | {"skills.[1]='Sleeping'"}, 52 | {"skills contains 'Verification'"}, 53 | } 54 | var fail = []struct { 55 | in string 56 | ex interface{} 57 | }{} 58 | for _, v := range pass { 59 | result, err := parser.Query(v.in) 60 | if err != nil { 61 | t.Error(v.in, err) 62 | } 63 | fmt.Println(v.in, result) 64 | // if v.ex != result { 65 | // t.Error("Expected:", v.ex, "actual:", result) 66 | // } 67 | } 68 | for range fail { 69 | 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /lifo.go: -------------------------------------------------------------------------------- 1 | package jsonql 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | // Lifo - last in first out stack 8 | type Lifo struct { 9 | top *Element 10 | size int 11 | } 12 | 13 | // Element - an item in the stack 14 | type Element struct { 15 | value interface{} 16 | next *Element 17 | } 18 | 19 | // Stack - a set of functions of the Stack 20 | type Stack interface { 21 | Len() int 22 | Push(value interface{}) 23 | Pop() (value interface{}) 24 | Peep() (value interface{}) 25 | Print() 26 | } 27 | 28 | // Len - gets the length of the stack. 29 | func (s *Lifo) Len() int { 30 | return s.size 31 | } 32 | 33 | // Push - pushes the value into the stack. 34 | func (s *Lifo) Push(value interface{}) { 35 | s.top = &Element{value, s.top} 36 | s.size++ 37 | } 38 | 39 | // Pop - pops the last value out of the stack. 40 | func (s *Lifo) Pop() (value interface{}) { 41 | if s.size > 0 { 42 | value, s.top = s.top.value, s.top.next 43 | s.size-- 44 | return 45 | } 46 | return nil 47 | } 48 | 49 | // Peep - gets the last value in the stack without popping it out. 50 | func (s *Lifo) Peep() (value interface{}) { 51 | if s.size > 0 { 52 | value = s.top.value 53 | return 54 | } 55 | return nil 56 | } 57 | 58 | // Print - shows what's in the stack. 59 | func (s *Lifo) Print() { 60 | tmp := s.top 61 | for i := 0; i < s.Len(); i++ { 62 | fmt.Print(tmp.value, ", ") 63 | tmp = tmp.next 64 | } 65 | fmt.Println() 66 | } 67 | -------------------------------------------------------------------------------- /lifo_test.go: -------------------------------------------------------------------------------- 1 | package jsonql 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | ) 7 | 8 | func TestLifo(t *testing.T) { 9 | lifo := &Lifo{} 10 | lifo.Push("1") 11 | lifo.Push("2") 12 | lifo.Push("3") 13 | fmt.Println(lifo.Len()) 14 | lifo.Print() 15 | } 16 | -------------------------------------------------------------------------------- /sql_operators.go: -------------------------------------------------------------------------------- 1 | package jsonql 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "math" 7 | "regexp" 8 | "strconv" 9 | "strings" 10 | 11 | "github.com/elgs/gojq" 12 | ) 13 | 14 | func evalToken(symbolTable interface{}, token string) (interface{}, error) { 15 | if token == "true" || token == "false" || token == "defined" || token == "null" { 16 | return token, nil 17 | } 18 | if (strings.HasPrefix(token, "'") && strings.HasSuffix(token, "'")) || 19 | (strings.HasPrefix(token, "\"") && strings.HasSuffix(token, "\"")) { 20 | // string 21 | return token[1 : len(token)-1], nil 22 | } 23 | intToken, err := strconv.ParseInt(token, 10, 0) 24 | if err == nil { 25 | return intToken, nil 26 | } 27 | floatToken, err := strconv.ParseFloat(token, 64) 28 | if err == nil { 29 | return floatToken, nil 30 | } 31 | jq := gojq.NewQuery(symbolTable) 32 | return jq.Query(token) 33 | } 34 | 35 | var sqlOperators = map[string]*Operator{ 36 | // Tokenizer will be responsible to put a space before and after each ')OR(', but not priORity. 37 | "||": { 38 | Precedence: 1, 39 | Eval: func(symbolTable interface{}, left string, right string) (string, error) { 40 | l, err := strconv.ParseBool(left) 41 | if err != nil { 42 | return "false", err 43 | } 44 | r, err := strconv.ParseBool(right) 45 | if err != nil { 46 | return "false", err 47 | } 48 | return strconv.FormatBool(l || r), nil 49 | }, 50 | }, 51 | "&&": { 52 | Precedence: 3, 53 | Eval: func(symbolTable interface{}, left string, right string) (string, error) { 54 | l, err := strconv.ParseBool(left) 55 | if err != nil { 56 | return "false", err 57 | } 58 | r, err := strconv.ParseBool(right) 59 | if err != nil { 60 | return "false", err 61 | } 62 | return strconv.FormatBool(l && r), nil 63 | }, 64 | }, 65 | "is": { 66 | Precedence: 5, 67 | Eval: func(symbolTable interface{}, left string, right string) (string, error) { 68 | // only support "null" and "defined" here 69 | if right != "null" && right != "defined" && left != "null" && left != "defined" { 70 | return "false", errors.New("Unsupported evaluation [ " + left + " is " + right + " ]") 71 | } 72 | l, lUndefined := evalToken(symbolTable, left) 73 | r, rUndefined := evalToken(symbolTable, right) 74 | 75 | // if either side is not defined, we don't have a match 76 | if lUndefined != nil || rUndefined != nil { 77 | return "false", nil 78 | } 79 | // matching on null? 80 | if right == "null" && l != nil { 81 | return "false", nil 82 | } 83 | if left == "null" && r != nil { 84 | return "false", nil 85 | } 86 | // otherwise 87 | return "true", nil 88 | }, 89 | }, 90 | "isnot": { 91 | Precedence: 5, 92 | Eval: func(symbolTable interface{}, left string, right string) (string, error) { 93 | // only support "null" and "defined" here 94 | if right != "null" && right != "defined" && left != "null" && left != "defined" { 95 | return "false", errors.New("Unsupported evaluation [ " + left + " isnot " + right + " ]") 96 | } 97 | l, lUndefined := evalToken(symbolTable, left) 98 | r, rUndefined := evalToken(symbolTable, right) 99 | 100 | // if either side is checking for "defined" and we don't have a nil on the other, we don't have a match 101 | if (left == "defined" && rUndefined != nil) || 102 | (right == "defined" && lUndefined != nil) || 103 | // truly null 104 | (left == "null" && r != nil && rUndefined == nil) || 105 | (right == "null" && l != nil && lUndefined == nil) { 106 | return "true", nil 107 | } 108 | 109 | // otherwise 110 | return "false", nil 111 | }, 112 | }, 113 | "contains": { 114 | Precedence: 5, 115 | Eval: func(symbolTable interface{}, left string, right string) (string, error) { 116 | l, err := evalToken(symbolTable, left) 117 | if err != nil { 118 | return "false", err 119 | } 120 | r, err := evalToken(symbolTable, right) 121 | if err != nil { 122 | return "false", err 123 | } 124 | if al, ok := l.([]interface{}); ok { 125 | for _, item := range al { 126 | if item == r { 127 | return "true", nil 128 | } 129 | } 130 | } 131 | return "false", nil 132 | }, 133 | }, 134 | "=": { 135 | Precedence: 5, 136 | Eval: func(symbolTable interface{}, left string, right string) (string, error) { 137 | l, err := evalToken(symbolTable, left) 138 | if err != nil { 139 | return "false", err 140 | } 141 | r, err := evalToken(symbolTable, right) 142 | if err != nil { 143 | return "false", err 144 | } 145 | // fmt.Println(reflect.TypeOf(l).String()) 146 | // fmt.Println(reflect.TypeOf(r).String()) 147 | switch vr := r.(type) { 148 | case string: 149 | if sl, oksl := l.(string); oksl { 150 | return strconv.FormatBool(sl == vr), nil 151 | } else if bl, okbl := l.(bool); okbl { 152 | br, err := strconv.ParseBool(vr) 153 | if err != nil { 154 | return "false", nil 155 | } 156 | return strconv.FormatBool(bl == br), nil 157 | } else { 158 | return "false", nil 159 | } 160 | case int64: 161 | switch vl := l.(type) { 162 | case string: 163 | il, err := strconv.ParseInt(vl, 10, 0) 164 | if err != nil { 165 | return "false", nil 166 | } 167 | return strconv.FormatBool(il == vr), nil 168 | case int64: 169 | return strconv.FormatBool(vl == vr), nil 170 | case int: 171 | return strconv.FormatBool(vl == int(vr)), nil 172 | case float64: 173 | return strconv.FormatBool(vl == float64(vr)), nil 174 | default: 175 | return "false", nil 176 | } 177 | case float64: 178 | switch vl := l.(type) { 179 | case string: 180 | fl, err := strconv.ParseFloat(vl, 64) 181 | if err != nil { 182 | return "false", nil 183 | } 184 | return strconv.FormatBool(fl == vr), nil 185 | case int64: 186 | return strconv.FormatBool(float64(vl) == vr), nil 187 | case int: 188 | return strconv.FormatBool(vl == int(vr)), nil 189 | case float64: 190 | return strconv.FormatBool(vl == vr), nil 191 | default: 192 | return "false", nil 193 | } 194 | default: 195 | return "false", errors.New(fmt.Sprint("Failed to compare: ", left, right)) 196 | } 197 | }, 198 | }, 199 | "!=": { 200 | Precedence: 5, 201 | Eval: func(symbolTable interface{}, left string, right string) (string, error) { 202 | l, err := evalToken(symbolTable, left) 203 | if err != nil { 204 | return "false", err 205 | } 206 | r, err := evalToken(symbolTable, right) 207 | if err != nil { 208 | return "false", err 209 | } 210 | if sr, oksr := r.(string); oksr { 211 | if sl, oksl := l.(string); oksl { 212 | return strconv.FormatBool(sl != sr), nil 213 | } else if bl, okbl := l.(bool); okbl { 214 | br, err := strconv.ParseBool(sr) 215 | if err != nil { 216 | return "false", nil 217 | } 218 | return strconv.FormatBool(bl != br), nil 219 | } else { 220 | return "false", nil 221 | } 222 | } 223 | if ir, okir := r.(int64); okir { 224 | switch vl := l.(type) { 225 | case string: 226 | il, err := strconv.ParseInt(vl, 10, 0) 227 | if err != nil { 228 | return "false", nil 229 | } 230 | return strconv.FormatBool(il != ir), nil 231 | case int64: 232 | return strconv.FormatBool(vl != ir), nil 233 | case int: 234 | return strconv.FormatBool(vl != int(ir)), nil 235 | case float64: 236 | return strconv.FormatBool(vl != float64(ir)), nil 237 | default: 238 | return "false", nil 239 | } 240 | } 241 | if fr, okfr := r.(float64); okfr { 242 | switch vl := l.(type) { 243 | case string: 244 | fl, err := strconv.ParseFloat(vl, 64) 245 | if err != nil { 246 | return "false", nil 247 | } 248 | return strconv.FormatBool(fl != fr), nil 249 | case int64: 250 | return strconv.FormatBool(float64(vl) != fr), nil 251 | case int: 252 | return strconv.FormatBool(vl != int(vl)), nil 253 | case float64: 254 | return strconv.FormatBool(vl != fr), nil 255 | default: 256 | return "false", nil 257 | } 258 | } 259 | return "false", errors.New(fmt.Sprint("Failed to compare: ", left, right)) 260 | }, 261 | }, 262 | ">": { 263 | Precedence: 5, 264 | Eval: func(symbolTable interface{}, left string, right string) (string, error) { 265 | l, err := evalToken(symbolTable, left) 266 | if err != nil { 267 | return "false", err 268 | } 269 | r, err := evalToken(symbolTable, right) 270 | if err != nil { 271 | return "false", err 272 | } 273 | if sr, oksr := r.(string); oksr { 274 | sl, oksl := l.(string) 275 | if oksl { 276 | return strconv.FormatBool(sl > sr), nil 277 | } 278 | } 279 | if ir, okir := r.(int64); okir { 280 | switch vl := l.(type) { 281 | case string: 282 | il, err := strconv.ParseInt(vl, 10, 0) 283 | if err != nil { 284 | return "false", nil 285 | } 286 | return strconv.FormatBool(il > ir), nil 287 | case int64: 288 | return strconv.FormatBool(vl > ir), nil 289 | case int: 290 | return strconv.FormatBool(vl > int(ir)), nil 291 | case float64: 292 | return strconv.FormatBool(vl > float64(ir)), nil 293 | default: 294 | return "false", nil 295 | } 296 | } 297 | if fr, okfr := r.(float64); okfr { 298 | switch vl := l.(type) { 299 | case string: 300 | fl, err := strconv.ParseFloat(vl, 64) 301 | if err != nil { 302 | return "false", nil 303 | } 304 | return strconv.FormatBool(fl > fr), nil 305 | case int64: 306 | return strconv.FormatBool(float64(vl) > fr), nil 307 | case int: 308 | return strconv.FormatBool(vl > int(fr)), nil 309 | case float64: 310 | return strconv.FormatBool(vl > fr), nil 311 | default: 312 | return "false", nil 313 | } 314 | } 315 | return "false", errors.New(fmt.Sprint("Failed to compare: ", left, right)) 316 | }, 317 | }, 318 | "<": { 319 | Precedence: 5, 320 | Eval: func(symbolTable interface{}, left string, right string) (string, error) { 321 | l, err := evalToken(symbolTable, left) 322 | if err != nil { 323 | return "false", err 324 | } 325 | r, err := evalToken(symbolTable, right) 326 | if err != nil { 327 | return "false", err 328 | } 329 | if sr, oksr := r.(string); oksr { 330 | sl, oksl := l.(string) 331 | if oksl { 332 | return strconv.FormatBool(sl < sr), nil 333 | } 334 | } 335 | if ir, okir := r.(int64); okir { 336 | switch vl := l.(type) { 337 | case string: 338 | il, err := strconv.ParseInt(vl, 10, 0) 339 | if err != nil { 340 | return "false", nil 341 | } 342 | return strconv.FormatBool(il < ir), nil 343 | case int64: 344 | return strconv.FormatBool(vl < ir), nil 345 | case int: 346 | return strconv.FormatBool(vl < int(ir)), nil 347 | case float64: 348 | return strconv.FormatBool(vl < float64(ir)), nil 349 | default: 350 | return "false", nil 351 | } 352 | } 353 | if fr, okfr := r.(float64); okfr { 354 | switch vl := l.(type) { 355 | case string: 356 | fl, err := strconv.ParseFloat(vl, 64) 357 | if err != nil { 358 | return "false", nil 359 | } 360 | return strconv.FormatBool(fl < fr), nil 361 | case int64: 362 | return strconv.FormatBool(float64(vl) < fr), nil 363 | case int: 364 | return strconv.FormatBool(vl < int(fr)), nil 365 | case float64: 366 | return strconv.FormatBool(vl < fr), nil 367 | default: 368 | return "false", nil 369 | } 370 | } 371 | return "false", errors.New(fmt.Sprint("Failed to compare: ", left, right)) 372 | }, 373 | }, 374 | ">=": { 375 | Precedence: 5, 376 | Eval: func(symbolTable interface{}, left string, right string) (string, error) { 377 | l, err := evalToken(symbolTable, left) 378 | if err != nil { 379 | return "false", err 380 | } 381 | r, err := evalToken(symbolTable, right) 382 | if err != nil { 383 | return "false", err 384 | } 385 | if sr, oksr := r.(string); oksr { 386 | sl, oksl := l.(string) 387 | if oksl { 388 | return strconv.FormatBool(sl >= sr), nil 389 | } 390 | } 391 | if ir, okir := r.(int64); okir { 392 | switch vl := l.(type) { 393 | case string: 394 | il, err := strconv.ParseInt(vl, 10, 0) 395 | if err != nil { 396 | return "false", nil 397 | } 398 | return strconv.FormatBool(il >= ir), nil 399 | case int64: 400 | return strconv.FormatBool(vl >= ir), nil 401 | case int: 402 | return strconv.FormatBool(vl >= int(ir)), nil 403 | case float64: 404 | return strconv.FormatBool(vl >= float64(ir)), nil 405 | default: 406 | return "false", nil 407 | } 408 | } 409 | if fr, okfr := r.(float64); okfr { 410 | switch vl := l.(type) { 411 | case string: 412 | fl, err := strconv.ParseFloat(vl, 64) 413 | if err != nil { 414 | return "false", nil 415 | } 416 | return strconv.FormatBool(fl >= fr), nil 417 | case int64: 418 | return strconv.FormatBool(float64(vl) >= fr), nil 419 | case int: 420 | return strconv.FormatBool(vl >= int(fr)), nil 421 | case float64: 422 | return strconv.FormatBool(vl >= fr), nil 423 | default: 424 | return "false", nil 425 | } 426 | } 427 | return "false", errors.New(fmt.Sprint("Failed to compare: ", left, right)) 428 | }, 429 | }, 430 | "<=": { 431 | Precedence: 5, 432 | Eval: func(symbolTable interface{}, left string, right string) (string, error) { 433 | l, err := evalToken(symbolTable, left) 434 | if err != nil { 435 | return "false", err 436 | } 437 | r, err := evalToken(symbolTable, right) 438 | if err != nil { 439 | return "false", err 440 | } 441 | if sr, oksr := r.(string); oksr { 442 | sl, oksl := l.(string) 443 | if oksl { 444 | return strconv.FormatBool(sl <= sr), nil 445 | } 446 | } 447 | if ir, okir := r.(int64); okir { 448 | switch vl := l.(type) { 449 | case string: 450 | il, err := strconv.ParseInt(vl, 10, 0) 451 | if err != nil { 452 | return "false", nil 453 | } 454 | return strconv.FormatBool(il <= ir), nil 455 | case int64: 456 | return strconv.FormatBool(vl <= ir), nil 457 | case int: 458 | return strconv.FormatBool(vl <= int(ir)), nil 459 | case float64: 460 | return strconv.FormatBool(vl <= float64(ir)), nil 461 | default: 462 | return "false", nil 463 | } 464 | } 465 | if fr, okfr := r.(float64); okfr { 466 | switch vl := l.(type) { 467 | case string: 468 | fl, err := strconv.ParseFloat(vl, 64) 469 | if err != nil { 470 | return "false", nil 471 | } 472 | return strconv.FormatBool(fl <= fr), nil 473 | case int64: 474 | return strconv.FormatBool(float64(vl) <= fr), nil 475 | case int: 476 | return strconv.FormatBool(vl <= int(fr)), nil 477 | case float64: 478 | return strconv.FormatBool(vl <= fr), nil 479 | default: 480 | return "false", nil 481 | } 482 | } 483 | return "false", errors.New(fmt.Sprint("Failed to compare: ", left, right)) 484 | }, 485 | }, 486 | "~=": { 487 | Precedence: 5, 488 | Eval: func(symbolTable interface{}, left string, right string) (string, error) { 489 | l, err := evalToken(symbolTable, left) 490 | if err != nil { 491 | return "false", err 492 | } 493 | r, err := evalToken(symbolTable, right) 494 | if err != nil { 495 | return "false", err 496 | } 497 | sl, foundl := l.(string) 498 | sr, foundr := r.(string) 499 | if foundl && foundr { 500 | matches, err := regexp.MatchString(sr, sl) 501 | if err != nil { 502 | return "false", err 503 | } 504 | return strconv.FormatBool(matches), nil 505 | } 506 | return "false", errors.New(fmt.Sprint("Failed to compare: ", left, right)) 507 | 508 | }, 509 | }, 510 | "!~=": { 511 | Precedence: 5, 512 | Eval: func(symbolTable interface{}, left string, right string) (string, error) { 513 | l, err := evalToken(symbolTable, left) 514 | if err != nil { 515 | return "false", err 516 | } 517 | r, err := evalToken(symbolTable, right) 518 | if err != nil { 519 | return "false", err 520 | } 521 | sl, foundl := l.(string) 522 | sr, foundr := r.(string) 523 | if foundl && foundr { 524 | matches, err := regexp.MatchString(sr, sl) 525 | if err != nil { 526 | return "false", err 527 | } 528 | return strconv.FormatBool(!matches), nil 529 | } 530 | return "false", errors.New(fmt.Sprint("Failed to compare: ", left, right)) 531 | 532 | }, 533 | }, 534 | "+": { 535 | Precedence: 7, 536 | Eval: func(symbolTable interface{}, left string, right string) (string, error) { 537 | l, err := evalToken(symbolTable, left) 538 | if err != nil { 539 | return "false", err 540 | } 541 | r, err := evalToken(symbolTable, right) 542 | if err != nil { 543 | return "false", err 544 | } 545 | il, okil := l.(int64) 546 | ir, okir := r.(int64) 547 | fl, okfl := l.(float64) 548 | fr, okfr := r.(float64) 549 | if okil && okir { //ii 550 | return fmt.Sprint(il + ir), nil 551 | } else if okfl && okfr { //ff 552 | return fmt.Sprint(fl + fr), nil 553 | } else if okil && okfr { //if 554 | return fmt.Sprint(float64(il) + fr), nil 555 | } else if okfl && okir { //fi 556 | return fmt.Sprint(fl + float64(ir)), nil 557 | } else { //else 558 | return fmt.Sprint("'", l, r, "'"), nil 559 | } 560 | }, 561 | }, 562 | "-": { 563 | Precedence: 7, 564 | Eval: func(symbolTable interface{}, left string, right string) (string, error) { 565 | l, err := evalToken(symbolTable, left) 566 | if err != nil { 567 | return "false", err 568 | } 569 | r, err := evalToken(symbolTable, right) 570 | if err != nil { 571 | return "false", err 572 | } 573 | il, okil := l.(int64) 574 | ir, okir := r.(int64) 575 | fl, okfl := l.(float64) 576 | fr, okfr := r.(float64) 577 | if okil && okir { //ii 578 | return fmt.Sprint(il - ir), nil 579 | } else if okfl && okfr { //ff 580 | return fmt.Sprint(fl - fr), nil 581 | } else if okil && okfr { //if 582 | return fmt.Sprint(float64(il) - fr), nil 583 | } else if okfl && okir { //fi 584 | return fmt.Sprint(fl - float64(ir)), nil 585 | } else { //else 586 | return "", errors.New(fmt.Sprint("Failed to evaluate: ", left, right)) 587 | } 588 | }, 589 | }, 590 | "*": { 591 | Precedence: 9, 592 | Eval: func(symbolTable interface{}, left string, right string) (string, error) { 593 | l, err := evalToken(symbolTable, left) 594 | if err != nil { 595 | return "false", err 596 | } 597 | r, err := evalToken(symbolTable, right) 598 | if err != nil { 599 | return "false", err 600 | } 601 | il, okil := l.(int64) 602 | ir, okir := r.(int64) 603 | fl, okfl := l.(float64) 604 | fr, okfr := r.(float64) 605 | if okil && okir { //ii 606 | return fmt.Sprint(il * ir), nil 607 | } else if okfl && okfr { //ff 608 | return fmt.Sprint(fl * fr), nil 609 | } else if okil && okfr { //if 610 | return fmt.Sprint(float64(il) * fr), nil 611 | } else if okfl && okir { //fi 612 | return fmt.Sprint(fl * float64(ir)), nil 613 | } else { //else 614 | return "", errors.New(fmt.Sprint("Failed to evaluate: ", left, right)) 615 | } 616 | }, 617 | }, 618 | "/": { 619 | Precedence: 9, 620 | Eval: func(symbolTable interface{}, left string, right string) (string, error) { 621 | l, err := evalToken(symbolTable, left) 622 | if err != nil { 623 | return "false", err 624 | } 625 | r, err := evalToken(symbolTable, right) 626 | if err != nil { 627 | return "false", err 628 | } 629 | il, okil := l.(int64) 630 | ir, okir := r.(int64) 631 | fl, okfl := l.(float64) 632 | fr, okfr := r.(float64) 633 | if (okir && ir == 0) || okfr && fr == 0 { 634 | return "", errors.New(fmt.Sprint("Divide by zero: ", left, right)) 635 | } 636 | if okil && okir { //ii 637 | return fmt.Sprint(il / ir), nil 638 | } else if okfl && okfr { //ff 639 | return fmt.Sprint(fl / fr), nil 640 | } else if okil && okfr { //if 641 | return fmt.Sprint(float64(il) / fr), nil 642 | } else if okfl && okir { //fi 643 | return fmt.Sprint(fl / float64(ir)), nil 644 | } else { //else 645 | return "", errors.New(fmt.Sprint("Failed to evaluate: ", left, right)) 646 | } 647 | }, 648 | }, 649 | "%": { 650 | Precedence: 9, 651 | Eval: func(symbolTable interface{}, left string, right string) (string, error) { 652 | l, err := evalToken(symbolTable, left) 653 | if err != nil { 654 | return "false", err 655 | } 656 | r, err := evalToken(symbolTable, right) 657 | if err != nil { 658 | return "false", err 659 | } 660 | il, okil := l.(int64) 661 | ir, okir := r.(int64) 662 | if okir && ir == 0 { 663 | return "", errors.New(fmt.Sprint("Failed to evaluate: ", left, right)) 664 | } 665 | if okil && okir { //ii 666 | return fmt.Sprint(il % ir), nil 667 | } 668 | return "", errors.New(fmt.Sprint("Failed to evaluate: ", left, right)) 669 | }, 670 | }, 671 | "^": { 672 | Precedence: 10, 673 | Eval: func(symbolTable interface{}, left string, right string) (string, error) { 674 | l, err := evalToken(symbolTable, left) 675 | if err != nil { 676 | return "false", err 677 | } 678 | r, err := evalToken(symbolTable, right) 679 | if err != nil { 680 | return "false", err 681 | } 682 | il, okil := l.(int64) 683 | ir, okir := r.(int64) 684 | fl, okfl := l.(float64) 685 | fr, okfr := r.(float64) 686 | if okil && okir { //ii 687 | return fmt.Sprint(math.Pow(float64(il), float64(ir))), nil 688 | } else if okfl && okfr { //ff 689 | return fmt.Sprint(math.Pow(fl, fr)), nil 690 | } else if okil && okfr { //if 691 | return fmt.Sprint(math.Pow(float64(il), fr)), nil 692 | } else if okfl && okir { //fi 693 | return fmt.Sprint(math.Pow(fl, float64(ir))), nil 694 | } else { //else 695 | return "", errors.New(fmt.Sprint("Failed to evaluate: ", left, right)) 696 | } 697 | }, 698 | }, 699 | } 700 | -------------------------------------------------------------------------------- /utils.go: -------------------------------------------------------------------------------- 1 | package jsonql 2 | 3 | // CompareSlices - compares to slices, return false if they are not equal, true if they are. 4 | func CompareSlices(a, b []string) bool { 5 | if len(a) != len(b) { 6 | return false 7 | } 8 | for i, v := range a { 9 | if v != b[i] { 10 | return false 11 | } 12 | } 13 | return true 14 | } 15 | 16 | // ReverseString - reverses a string. 17 | func ReverseString(input string) string { 18 | r := []rune(input) 19 | for i := 0; i < len(r)/2; i++ { 20 | j := len(r) - i - 1 21 | r[i], r[j] = r[j], r[i] 22 | } 23 | output := string(r) 24 | return output 25 | } 26 | -------------------------------------------------------------------------------- /utils_test.go: -------------------------------------------------------------------------------- 1 | package jsonql 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestCompareSlices(t *testing.T) { 8 | var pass = []struct{ in, ex []string }{ 9 | {[]string{"1", "2", "3"}, []string{"1", "2", "3"}}, 10 | {[]string{}, []string{}}, 11 | {[]string{"a", "b", "你好"}, []string{"a", "b", "你好"}}, 12 | } 13 | var fail = []struct{ in, ex []string }{ 14 | {[]string{"1"}, []string{}}, 15 | {[]string{}, []string{"1", "2"}}, 16 | } 17 | for _, v := range pass { 18 | if !CompareSlices(v.in, v.ex) { 19 | t.Error("Expected:", v.ex, "actual:", v.in) 20 | } 21 | } 22 | for _, v := range fail { 23 | if CompareSlices(v.in, v.ex) { 24 | t.Error("Expected:", v.ex, "actual:", v.in) 25 | } 26 | } 27 | } 28 | 29 | func TestReverseString(t *testing.T) { 30 | var pass = []struct{ in, ex string }{ 31 | {"", ""}, 32 | {"Hello, 世界", "界世 ,olleH"}, 33 | {"Backward", "drawkcaB"}, 34 | } 35 | var fail = []struct{ in, ex string }{ 36 | {"", " "}, 37 | {" ", ""}, 38 | {"111", "112"}, 39 | } 40 | for _, v := range pass { 41 | if v.ex != ReverseString(v.in) { 42 | t.Error("Expected:", v.ex, "actual:", v.in) 43 | } 44 | } 45 | for _, v := range fail { 46 | if v.ex == ReverseString(v.in) { 47 | t.Error("Expected:", v.ex, "actual:", v.in) 48 | } 49 | } 50 | } 51 | --------------------------------------------------------------------------------