├── go.mod ├── example ├── vendor │ └── github.com │ │ └── tbaud0n │ │ └── go-rql-parser └── main.go ├── .travis.yml ├── README.md ├── Parser_test.go ├── Lexer.go ├── Sql.go └── Parser.go /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/tbaud0n/go-rql-parser 2 | -------------------------------------------------------------------------------- /example/vendor/github.com/tbaud0n/go-rql-parser: -------------------------------------------------------------------------------- 1 | ../../../../../rqlParser -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | go: 3 | - "1.10" 4 | before_install: 5 | - go get github.com/mattn/goveralls 6 | script: 7 | - $GOPATH/bin/goveralls -service=travis-ci 8 | -------------------------------------------------------------------------------- /example/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "strings" 7 | 8 | rqlParser "github.com/tbaud0n/go-rql-parser" 9 | ) 10 | 11 | func main() { 12 | p := rqlParser.NewParser() 13 | rqlNode, err := p.Parse(strings.NewReader(os.Args[1])) 14 | 15 | if err != nil { 16 | panic("Error scaning rql : " + err.Error()) 17 | } 18 | 19 | sql, err := rqlParser.NewSqlTranslator(rqlNode).Sql() 20 | if err != nil { 21 | panic(err) 22 | } 23 | 24 | fmt.Println(sql) 25 | } 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # GO-RQL-PARSER 2 | [![Build Status](https://travis-ci.org/tbaud0n/go-rql-parser.svg?branch=master)](https://travis-ci.org/tbaud0n/go-rql-parser) [![Coverage Status](https://coveralls.io/repos/github/tbaud0n/go-rql-parser/badge.svg?branch=master)](https://coveralls.io/github/tbaud0n/go-rql-parser?branch=master) 3 | 4 | Small, simple and lightweight library for Go web applications to translate [RQL (Resource Query Language)](http://www.persvr.org/rql/) queries to SQL 5 | 6 | ## Usage 7 | 8 | query := `and(eq(foo,3),lt(price,10))&sort(+price)` 9 | 10 | p := rqlParser.NewParser() // Instanciate a new parser 11 | 12 | // Parse the query and return the root node of the generated tree 13 | rqlRootNode, err := p.Parse(query) 14 | if err != nil { 15 | panic("Query is not a valid rql : " + err.Error()) 16 | } 17 | 18 | // Return the sql string for querying DB 19 | sql, err := rqlParser.NewSqlTranslator(rqlNode).Sql() 20 | if err != nil { 21 | panic(err) 22 | } 23 | 24 | fmt.Println(sql) 25 | // Print `WHERE ((foo=3) AND (price < 10)) ORDER BY price 26 | 27 | ## Supported operators 28 | The library support by default the following RQL operators : 29 | 30 | - AND 31 | - SQL Operator : `AND` 32 | - OR 33 | - SQL Operator : `OR` 34 | - NE 35 | - SQL Operator : `!=` (When value is `NULL` it is translated to the `IS NOT` SQL operator) 36 | - EQ 37 | - SQL Operator : `=` (When value is `NULL` it is translated to the `IS` SQL operator) 38 | - LIKE 39 | - SQL Operator : `LIKE` 40 | - MATCH 41 | - SQL Operator : `ILIKE` 42 | - GT 43 | - SQL Operator : `>` 44 | - LT 45 | - SQL Operator : `<` 46 | - GE 47 | - SQL Operator : `>=` 48 | - LE 49 | - SQL Operator : `<=` 50 | - SUM 51 | - SQL Operator : `SUM` 52 | - NOT 53 | - SQL Operator : `NOT` 54 | 55 | ## Append supported operators 56 | It's easy to handle new operators by simply adding a `TranslatorOpFunc` to the `SQLTranslator` : 57 | 58 | query := `or(eq(foo,42),between(price,10,100))` 59 | rqlRootNode, err := p.Parse(query) 60 | if err != nil { 61 | panic("Query is not a valid rql : " + err.Error()) 62 | } 63 | 64 | sqlTranslator := rqlParser.NewSqlTranslator(rqlNode) 65 | 66 | // Before translating the node, just add the custom operator 67 | // let's define a new function to handle beetween operator 68 | betweenTranslatorFunc := func(n *rqlParser.RqlNode) (s string, err error) { 69 | var ( 70 | min, max int64 71 | field string 72 | ) 73 | 74 | if len(n.Args) != 3 { 75 | err = fmt.Errorf("between operator require 3 arguments") 76 | return 77 | } 78 | 79 | for i, a := range n.Args { 80 | if v, ok := a.(string); ok { 81 | switch i { 82 | case 0: 83 | if rqlParser.IsValidField(v) { 84 | field = v 85 | } else { 86 | return "", fmt.Errorf("First argument must be a valid field name (arg: %s)", v) 87 | } 88 | case 1, 2: 89 | num, err := strconv.ParseInt(v, 10, 64) 90 | if err != nil { 91 | return "", fmt.Errorf("Argument n°%d must be an integer (arg: %s)", i, v) 92 | } 93 | if i == 1 { 94 | min = num 95 | } else { 96 | max = num 97 | } 98 | } 99 | } else { 100 | err = fmt.Errorf("Between example function only support string arguments") 101 | return 102 | } 103 | } 104 | 105 | s = fmt.Sprintf("(%s >= %d) AND (%s <= %d)", field, min, field, max) 106 | 107 | return 108 | } 109 | 110 | sqlTranslator.SetOpFunc(`between`, rqlParser.TranslatorOpFunc(betweenTranslatorFunc)) 111 | s, err := sqlTranslator.Sql() 112 | if err != nil { 113 | panic(err) 114 | } 115 | fmt.Println(s) // Will print : "WHERE ((foo=42) OR ((price >= 10) AND (price <= 100)))" 116 | 117 | ## Contributions 118 | 119 | Any contribution is welcome. 120 | 121 | There is still many improvements that should be added to this library : 122 | - Support type casting (ex: `string:42` to force the SQLTranslator to handle 42 as a string) 123 | - Create a per database architecture translator as all databases doesn't support the same operators. 124 | -------------------------------------------------------------------------------- /Parser_test.go: -------------------------------------------------------------------------------- 1 | package rqlParser 2 | 3 | import ( 4 | "strings" 5 | "testing" 6 | ) 7 | 8 | type Test struct { 9 | Name string // Name of the test 10 | RQL string // Input RQL query 11 | SQL string // Expected Output SQL 12 | WantParseError bool // Test should raise an error when parsing the RQL query 13 | WantTranslatorError bool // Test should raise an error when translating to SQL 14 | } 15 | 16 | func (test *Test) Run(t *testing.T) { 17 | p := NewParser() 18 | 19 | rqlNode, err := p.Parse(strings.NewReader(test.RQL)) 20 | if test.WantParseError != (err != nil) { 21 | t.Fatalf("(%s) Expecting error :%v\nGot error : %v", test.Name, test.WantParseError, err) 22 | } 23 | 24 | sqlTranslator := NewSqlTranslator(rqlNode) 25 | s, err := sqlTranslator.Sql() 26 | if test.WantTranslatorError != (err != nil) { 27 | t.Fatalf("(%s) Expecting error :%v\nGot error : %v \n\tSQL = %s", test.Name, test.WantTranslatorError, err, s) 28 | } 29 | 30 | if s != test.SQL { 31 | t.Fatalf("(%s) Translated SQL doesn’t match the expected one %s vs %s", test.Name, s, test.SQL) 32 | } 33 | } 34 | 35 | var tests = []Test{ 36 | { 37 | Name: `Basic translation with double equal operators`, 38 | RQL: `and(foo=eq=42,price=gt=10)`, 39 | SQL: `WHERE ((foo = 42) AND (price > 10))`, 40 | WantParseError: false, 41 | WantTranslatorError: false, 42 | }, 43 | { 44 | Name: `Basic translation with func style operators`, 45 | RQL: `and(eq(foo,42),gt(price,10),not(disabled))`, 46 | SQL: `WHERE ((foo = 42) AND (price > 10) AND NOT(disabled))`, 47 | WantParseError: false, 48 | WantTranslatorError: false, 49 | }, 50 | { 51 | Name: `Basic translation with func simple equal operators`, 52 | RQL: `foo=42&price=10`, 53 | SQL: `WHERE ((foo = 42) AND (price = 10))`, 54 | WantParseError: false, 55 | WantTranslatorError: false, 56 | }, 57 | { 58 | Name: `Sort and limit`, 59 | RQL: `eq(foo,42)&sort(+price,-length)&limit(10,20)`, 60 | SQL: `WHERE (foo = 42) ORDER BY price, length DESC LIMIT 10 OFFSET 20`, 61 | WantParseError: false, 62 | WantTranslatorError: false, 63 | }, 64 | { 65 | Name: `Sort only`, 66 | RQL: `sort(-price)`, 67 | SQL: ` ORDER BY price DESC`, 68 | WantParseError: false, 69 | WantTranslatorError: false, 70 | }, 71 | { 72 | Name: `LIKE empty string`, 73 | RQL: `foo=like=`, 74 | SQL: `WHERE (foo LIKE '')`, 75 | WantParseError: false, 76 | WantTranslatorError: false, 77 | }, 78 | { 79 | Name: `Mixed style translation`, 80 | RQL: `((eq(foo,42)>(price,10))|price=ge=500)&disabled=eq=false`, 81 | SQL: `WHERE ((((foo = 42) AND (price > 10)) OR (price >= 500)) AND (disabled IS FALSE))`, 82 | WantParseError: false, 83 | WantTranslatorError: false, 84 | }, 85 | { 86 | Name: `Try a simple SQL injection`, 87 | RQL: `foo=like=toto%27%3BSELECT%20column%20IN%20table`, 88 | SQL: `WHERE (foo LIKE 'toto'';SELECT column IN table')`, 89 | WantParseError: false, 90 | WantTranslatorError: false, 91 | }, 92 | { 93 | Name: `Empty RQL`, 94 | RQL: ``, 95 | SQL: ``, 96 | WantParseError: false, 97 | WantTranslatorError: false, 98 | }, 99 | { 100 | Name: `Invalid RQL query (Unmanaged RQL operator)`, 101 | RQL: `foo=missing_operator=42`, 102 | SQL: ``, 103 | WantParseError: false, 104 | WantTranslatorError: true, 105 | }, 106 | { 107 | Name: `Invalid RQL query (Unescaped character)`, 108 | RQL: `like(foo,hello world)`, 109 | SQL: ``, 110 | WantParseError: true, 111 | WantTranslatorError: false, 112 | }, 113 | { 114 | Name: `Invalid RQL query (Missing comma)`, 115 | RQL: `and(not(test),eq(foo,toto)gt(price,10))`, 116 | SQL: ``, 117 | WantParseError: true, 118 | WantTranslatorError: false, 119 | }, 120 | { 121 | Name: `Invalid RQL query (Invalid field name)`, 122 | RQL: `eq(foo%20tot,42)`, 123 | SQL: ``, 124 | WantParseError: false, 125 | WantTranslatorError: true, 126 | }, 127 | { 128 | Name: `Invalid RQL query (Invalid field name 2)`, 129 | RQL: `eq(foo*,toto)`, 130 | SQL: ``, 131 | WantParseError: false, 132 | WantTranslatorError: true, 133 | }, 134 | } 135 | 136 | func TestParser(t *testing.T) { 137 | for _, test := range tests { 138 | test.Run(t) 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /Lexer.go: -------------------------------------------------------------------------------- 1 | package rqlParser 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | "fmt" 7 | "io" 8 | "net/url" 9 | ) 10 | 11 | const ( 12 | // Special tokens 13 | ILLEGAL Token = iota 14 | EOF 15 | 16 | // Literals 17 | IDENT // fields, function names 18 | 19 | // Reserved characters 20 | SPACE // 21 | AMPERSAND // & 22 | OPENING_PARENTHESIS // ( 23 | CLOSING_PARENTHESIS // ) 24 | COMMA // , 25 | EQUAL_SIGN // = 26 | SLASH // / 27 | SEMI_COLON // ; 28 | QUESTION_MARK // ? 29 | AT_SYMBOL // @ 30 | PIPE // | 31 | 32 | // Keywords 33 | AND 34 | OR 35 | EQUAL 36 | GREATER 37 | GREATER_OR_EQUAL 38 | LOWER 39 | LOWER_OR_EQUAL 40 | NOT_EQUAL 41 | ) 42 | 43 | var ( 44 | ReservedRunes []rune = []rune{' ', '&', '(', ')', ',', '=', '/', ';', '?', '@', '|'} 45 | eof = rune(0) 46 | ) 47 | 48 | type TokenString struct { 49 | t Token 50 | s string 51 | } 52 | 53 | type Token int 54 | 55 | func NewTokenString(t Token, s string) TokenString { 56 | var unescapedString string 57 | if len(s) > 0 && string(s[0]) != "+" { 58 | unescapedString, _ = url.QueryUnescape(s) 59 | } else { 60 | //Golang`s "unescape" method replaces "+" with " ", however "+" literal is not possible in urlencoded string 61 | //this is a case of sorting argument specification: eg: sort(+name), thus in this case string left unmodified 62 | unescapedString = s 63 | } 64 | return TokenString{t: t, s: unescapedString} 65 | } 66 | 67 | type Scanner struct { 68 | r *bufio.Reader 69 | } 70 | 71 | func NewScanner() *Scanner { 72 | return &Scanner{} 73 | } 74 | 75 | // Scan returns the next token and literal value. 76 | func (s *Scanner) Scan(r io.Reader) (out []TokenString, err error) { 77 | s.r = bufio.NewReader(r) 78 | 79 | for true { 80 | tok, lit := s.ScanToken() 81 | if tok == EOF { 82 | break 83 | } else if tok == ILLEGAL { 84 | return out, fmt.Errorf("Illegal Token : %s", lit) 85 | } else { 86 | out = append(out, NewTokenString(tok, lit)) 87 | } 88 | } 89 | 90 | return 91 | } 92 | 93 | func (s *Scanner) ScanToken() (tok Token, lit string) { 94 | ch := s.read() 95 | 96 | if isReservedRune(ch) { 97 | s.unread() 98 | return s.scanReservedRune() 99 | } else if isIdent(ch) { 100 | s.unread() 101 | return s.scanIdent() 102 | } 103 | 104 | if ch == eof { 105 | return EOF, "" 106 | } 107 | 108 | return ILLEGAL, string(ch) 109 | } 110 | 111 | func (s *Scanner) read() rune { 112 | ch, _, err := s.r.ReadRune() 113 | if err != nil { 114 | return eof 115 | } 116 | return ch 117 | } 118 | 119 | // unread places the previously read rune back on the reader. 120 | func (s *Scanner) unread() { _ = s.r.UnreadRune() } 121 | 122 | func (s *Scanner) scanReservedRune() (tok Token, lit string) { 123 | // Create a buffer and read the current character into it. 124 | var buf bytes.Buffer 125 | 126 | buf.WriteRune(s.read()) 127 | lit = buf.String() 128 | 129 | // Read every subsequent whitespace character into the buffer. 130 | // Non-whitespace characters and EOF will cause the loop to exit. 131 | 132 | for _, rr := range ReservedRunes { 133 | if string(rr) == lit { 134 | switch rr { 135 | case '&': 136 | return AMPERSAND, lit 137 | case '(': 138 | return OPENING_PARENTHESIS, lit 139 | case ')': 140 | return CLOSING_PARENTHESIS, lit 141 | case ',': 142 | return COMMA, lit 143 | case '=': 144 | return EQUAL_SIGN, lit 145 | case '/': 146 | return SLASH, lit 147 | case ';': 148 | return SEMI_COLON, lit 149 | case '?': 150 | return QUESTION_MARK, lit 151 | case '@': 152 | return AT_SYMBOL, lit 153 | case '|': 154 | return PIPE, lit 155 | case eof: 156 | return EOF, lit 157 | default: 158 | return ILLEGAL, lit 159 | } 160 | } 161 | } 162 | return ILLEGAL, lit 163 | } 164 | 165 | func isReservedRune(ch rune) bool { 166 | for _, rr := range ReservedRunes { 167 | if ch == rr { 168 | return true 169 | } 170 | } 171 | return false 172 | } 173 | 174 | func isIdent(ch rune) bool { 175 | return isLetter(ch) || isDigit(ch) || isSpecialChar(ch) 176 | } 177 | 178 | func isSpecialChar(ch rune) bool { 179 | return ch == '*' || ch == '_' || ch == '%' || 180 | ch == '+' || ch == '-' || ch == '.' 181 | } 182 | 183 | // isLetter returns true if the rune is a letter. 184 | func isLetter(ch rune) bool { 185 | return (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') 186 | } 187 | 188 | // isDigit returns true if the rune is a digit. 189 | func isDigit(ch rune) bool { return (ch >= '0' && ch <= '9') } 190 | 191 | func (s *Scanner) scanIdent() (tok Token, lit string) { 192 | // Create a buffer and read the current character into it. 193 | var buf bytes.Buffer 194 | buf.WriteRune(s.read()) 195 | 196 | // Read every subsequent ident character into the buffer. 197 | // Non-ident characters and EOF will cause the loop to exit. 198 | for { 199 | if ch := s.read(); ch == eof { 200 | break 201 | } else if !isIdent(ch) { 202 | s.unread() 203 | break 204 | } else { 205 | _, _ = buf.WriteRune(ch) 206 | } 207 | } 208 | 209 | return IDENT, buf.String() 210 | } 211 | -------------------------------------------------------------------------------- /Sql.go: -------------------------------------------------------------------------------- 1 | package rqlParser 2 | 3 | import ( 4 | "fmt" 5 | "net/url" 6 | "strconv" 7 | "strings" 8 | ) 9 | 10 | type TranslatorOpFunc func(*RqlNode) (string, error) 11 | 12 | type SqlTranslator struct { 13 | rootNode *RqlRootNode 14 | sqlOpsDic map[string]TranslatorOpFunc 15 | } 16 | 17 | func (st *SqlTranslator) SetOpFunc(op string, f TranslatorOpFunc) { 18 | st.sqlOpsDic[strings.ToUpper(op)] = f 19 | } 20 | 21 | func (st *SqlTranslator) DeleteOpFunc(op string) { 22 | delete(st.sqlOpsDic, strings.ToUpper(op)) 23 | } 24 | 25 | func (st *SqlTranslator) Where() (string, error) { 26 | if st.rootNode == nil { 27 | return "", nil 28 | } 29 | return st.where(st.rootNode.Node) 30 | } 31 | 32 | func (st *SqlTranslator) where(n *RqlNode) (string, error) { 33 | if n == nil { 34 | return ``, nil 35 | } 36 | f := st.sqlOpsDic[strings.ToUpper(n.Op)] 37 | if f == nil { 38 | return "", fmt.Errorf("No TranslatorOpFunc for op : '%s'", n.Op) 39 | } 40 | return f(n) 41 | } 42 | 43 | func (st *SqlTranslator) Limit() (sql string) { 44 | if st.rootNode == nil { 45 | return 46 | } 47 | limit := st.rootNode.Limit() 48 | if limit != "" && strings.ToUpper(limit) != "INFINITY" { 49 | sql = " LIMIT " + limit 50 | } 51 | return 52 | } 53 | 54 | func (st *SqlTranslator) Offset() (sql string) { 55 | if st.rootNode != nil && st.rootNode.Offset() != "" { 56 | sql = " OFFSET " + st.rootNode.Offset() 57 | } 58 | return 59 | } 60 | 61 | func (st *SqlTranslator) Sort() (sql string) { 62 | if st.rootNode == nil { 63 | return 64 | } 65 | sorts := st.rootNode.Sort() 66 | if len(sorts) > 0 { 67 | sql = " ORDER BY " 68 | sep := "" 69 | for _, sort := range sorts { 70 | sql = sql + sep + sort.by 71 | if sort.desc { 72 | sql = sql + " DESC" 73 | } 74 | sep = ", " 75 | } 76 | } 77 | 78 | return 79 | } 80 | 81 | func (st *SqlTranslator) Sql() (sql string, err error) { 82 | var where string 83 | 84 | where, err = st.Where() 85 | if err != nil { 86 | return 87 | } 88 | 89 | if len(where) > 0 { 90 | sql = `WHERE ` + where 91 | } 92 | 93 | sort := st.Sort() 94 | if len(sort) > 0 { 95 | sql += sort 96 | } 97 | 98 | limit := st.Limit() 99 | if len(limit) > 0 { 100 | sql += limit 101 | } 102 | 103 | offset := st.Offset() 104 | if len(offset) > 0 { 105 | sql += offset 106 | } 107 | 108 | return sql, nil 109 | } 110 | 111 | func NewSqlTranslator(r *RqlRootNode) (st *SqlTranslator) { 112 | st = &SqlTranslator{r, map[string]TranslatorOpFunc{}} 113 | 114 | starToPercentFunc := AlterStringFunc(func(s string) (string, error) { 115 | // v, err := url.QueryUnescape(s) 116 | // if err != nil { 117 | // return ``, err 118 | // } 119 | return strings.Replace(Quote(s), `*`, `%`, -1), nil 120 | }) 121 | 122 | st.SetOpFunc("AND", st.GetAndOrTranslatorOpFunc("AND")) 123 | st.SetOpFunc("OR", st.GetAndOrTranslatorOpFunc("OR")) 124 | 125 | st.SetOpFunc("NE", st.GetEqualityTranslatorOpFunc("!=", "IS NOT")) 126 | st.SetOpFunc("EQ", st.GetEqualityTranslatorOpFunc("=", "IS")) 127 | 128 | st.SetOpFunc("LIKE", st.GetFieldValueTranslatorFunc("LIKE", starToPercentFunc)) 129 | st.SetOpFunc("MATCH", st.GetFieldValueTranslatorFunc("ILIKE", starToPercentFunc)) 130 | st.SetOpFunc("GT", st.GetFieldValueTranslatorFunc(">", nil)) 131 | st.SetOpFunc("LT", st.GetFieldValueTranslatorFunc("<", nil)) 132 | st.SetOpFunc("GE", st.GetFieldValueTranslatorFunc(">=", nil)) 133 | st.SetOpFunc("LE", st.GetFieldValueTranslatorFunc("<=", nil)) 134 | st.SetOpFunc("NOT", st.GetOpFirstTranslatorFunc("NOT", nil)) 135 | // st.SetOpFunc("SUM", st.GetOpFirstTranslatorFunc("SUM", nil)) 136 | 137 | return 138 | } 139 | 140 | func (st *SqlTranslator) GetEqualityTranslatorOpFunc(op, specialOp string) TranslatorOpFunc { 141 | return TranslatorOpFunc(func(n *RqlNode) (s string, err error) { 142 | value, err := url.QueryUnescape(n.Args[1].(string)) 143 | if err != nil { 144 | return "", err 145 | } 146 | 147 | if value == `null` || value == `true` || value == `false` { 148 | field := n.Args[0].(string) 149 | if !IsValidField(field) { 150 | return ``, fmt.Errorf("Invalid field name : %s", field) 151 | } 152 | 153 | return fmt.Sprintf("(%s %s %s)", field, specialOp, strings.ToUpper(value)), nil 154 | } 155 | 156 | return st.GetFieldValueTranslatorFunc(op, nil)(n) 157 | }) 158 | } 159 | 160 | func (st *SqlTranslator) GetAndOrTranslatorOpFunc(op string) TranslatorOpFunc { 161 | return TranslatorOpFunc(func(n *RqlNode) (s string, err error) { 162 | sep := "" 163 | 164 | for _, a := range n.Args { 165 | s = s + sep 166 | switch v := a.(type) { 167 | case string: 168 | if !IsValidField(v) { 169 | return "", fmt.Errorf("Invalid field name : %s", v) 170 | } 171 | s = s + v 172 | case *RqlNode: 173 | var _s string 174 | _s, err = st.where(v) 175 | if err != nil { 176 | return "", err 177 | } 178 | s = s + _s 179 | } 180 | 181 | sep = " " + op + " " 182 | } 183 | 184 | return "(" + s + ")", nil 185 | }) 186 | } 187 | 188 | type AlterStringFunc func(string) (string, error) 189 | 190 | func (st *SqlTranslator) GetFieldValueTranslatorFunc(op string, valueAlterFunc AlterStringFunc) TranslatorOpFunc { 191 | return TranslatorOpFunc(func(n *RqlNode) (s string, err error) { 192 | sep := "" 193 | 194 | for i, a := range n.Args { 195 | s += sep 196 | switch v := a.(type) { 197 | case string: 198 | var _s string 199 | if i == 0 { 200 | if IsValidField(v) { 201 | _s = v 202 | } else { 203 | return "", fmt.Errorf("First argument must be a valid field name (arg: %s)", v) 204 | } 205 | } else { 206 | _, err := strconv.ParseInt(v, 10, 64) 207 | if err == nil { 208 | _s = v 209 | } else if valueAlterFunc != nil { 210 | _s, err = valueAlterFunc(v) 211 | if err != nil { 212 | return "", err 213 | } 214 | } else { 215 | _s = Quote(v) 216 | } 217 | } 218 | 219 | s += _s 220 | case *RqlNode: 221 | var _s string 222 | _s, err = st.where(v) 223 | if err != nil { 224 | return "", err 225 | } 226 | s = s + _s 227 | } 228 | 229 | sep = " " + op + " " 230 | } 231 | 232 | return "(" + s + ")", nil 233 | }) 234 | } 235 | 236 | func (st *SqlTranslator) GetOpFirstTranslatorFunc(op string, valueAlterFunc AlterStringFunc) TranslatorOpFunc { 237 | return TranslatorOpFunc(func(n *RqlNode) (s string, err error) { 238 | sep := "" 239 | 240 | for _, a := range n.Args { 241 | s += sep 242 | switch v := a.(type) { 243 | case string: 244 | var _s string 245 | _, err := strconv.ParseInt(v, 10, 64) 246 | if err == nil || IsValidField(v) { 247 | _s = v 248 | } else if valueAlterFunc != nil { 249 | _s, err = valueAlterFunc(v) 250 | if err != nil { 251 | return "", err 252 | } 253 | } else { 254 | _s = Quote(v) 255 | } 256 | 257 | s += _s 258 | case *RqlNode: 259 | var _s string 260 | _s, err = st.where(v) 261 | if err != nil { 262 | return "", err 263 | } 264 | s = s + _s 265 | } 266 | 267 | sep = ", " 268 | } 269 | 270 | return op + "(" + s + ")", nil 271 | }) 272 | } 273 | 274 | func IsValidField(s string) bool { 275 | for _, ch := range s { 276 | if !isLetter(ch) && !isDigit(ch) && ch != '_' && ch != '-' && ch != '.' { 277 | return false 278 | } 279 | } 280 | 281 | return true 282 | } 283 | 284 | func Quote(s string) string { 285 | return `'` + strings.Replace(s, `'`, `''`, -1) + `'` 286 | } 287 | -------------------------------------------------------------------------------- /Parser.go: -------------------------------------------------------------------------------- 1 | package rqlParser 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "strconv" 7 | "strings" 8 | ) 9 | 10 | var IsValueError error = fmt.Errorf("Bloc is a value") 11 | 12 | type TokenBloc []TokenString 13 | 14 | // String print the TokenBloc value for test purpose only 15 | func (tb TokenBloc) String() (s string) { 16 | for _, t := range tb { 17 | s = s + fmt.Sprintf("'%s' ", t.s) 18 | } 19 | return 20 | } 21 | 22 | type RqlNode struct { 23 | Op string 24 | Args []interface{} 25 | } 26 | 27 | type Sort struct { 28 | by string 29 | desc bool 30 | } 31 | 32 | type RqlRootNode struct { 33 | Node *RqlNode 34 | limit string 35 | offset string 36 | sorts []Sort 37 | } 38 | 39 | func (r *RqlRootNode) Limit() string { 40 | return r.limit 41 | } 42 | 43 | func (r *RqlRootNode) Offset() string { 44 | return r.offset 45 | } 46 | 47 | func (r *RqlRootNode) OffsetInt() int { 48 | i, err := strconv.Atoi(r.offset) 49 | if err != nil { 50 | return 0 51 | } 52 | return i 53 | } 54 | 55 | func (r *RqlRootNode) Sort() []Sort { 56 | return r.sorts 57 | } 58 | 59 | func parseLimit(n *RqlNode, root *RqlRootNode) (isLimitOp bool) { 60 | if n == nil { 61 | return false 62 | } 63 | if strings.ToUpper(n.Op) == "LIMIT" { 64 | root.limit = n.Args[0].(string) 65 | if len(n.Args) > 1 { 66 | root.offset = n.Args[1].(string) 67 | } 68 | isLimitOp = true 69 | } 70 | return 71 | } 72 | 73 | func parseSort(n *RqlNode, root *RqlRootNode) (isSortOp bool) { 74 | if n == nil { 75 | return false 76 | } 77 | if strings.ToUpper(n.Op) == "SORT" { 78 | for _, s := range n.Args { 79 | property := s.(string) 80 | desc := false 81 | 82 | if property[0] == '+' { 83 | property = property[1:] 84 | } else if property[0] == '-' { 85 | desc = true 86 | property = property[1:] 87 | } 88 | root.sorts = append(root.sorts, Sort{by: property, desc: desc}) 89 | } 90 | 91 | isSortOp = true 92 | } 93 | return 94 | } 95 | 96 | func (r *RqlRootNode) ParseSpecialOps() (err error) { 97 | if parseLimit(r.Node, r) || parseSort(r.Node, r) { 98 | r.Node = nil 99 | } else if r.Node != nil { 100 | if strings.ToUpper(r.Node.Op) == "AND" { 101 | limitIndex := -1 102 | sortIndex := -1 103 | for i, c := range r.Node.Args { 104 | switch n := c.(type) { 105 | case *RqlNode: 106 | if parseLimit(n, r) { 107 | limitIndex = i 108 | } else if parseSort(n, r) { 109 | sortIndex = i 110 | } 111 | } 112 | } 113 | if limitIndex >= 0 { 114 | if sortIndex > limitIndex { 115 | sortIndex = sortIndex - 1 116 | } 117 | if len(r.Node.Args) == 2 { 118 | keepIndex := 0 119 | if limitIndex == 0 { 120 | keepIndex = 1 121 | } 122 | r.Node = r.Node.Args[keepIndex].(*RqlNode) 123 | } else { 124 | r.Node.Args = append(r.Node.Args[:limitIndex], r.Node.Args[limitIndex+1:]...) 125 | } 126 | } 127 | if sortIndex >= 0 { 128 | if len(r.Node.Args) == 2 { 129 | keepIndex := 0 130 | if sortIndex == 0 { 131 | keepIndex = 1 132 | } 133 | r.Node = r.Node.Args[keepIndex].(*RqlNode) 134 | } else { 135 | r.Node.Args = append(r.Node.Args[:sortIndex], r.Node.Args[sortIndex+1:]...) 136 | } 137 | } 138 | if len(r.Node.Args) == 0 { 139 | r.Node = nil 140 | } 141 | } 142 | } 143 | 144 | return 145 | } 146 | 147 | type Parser struct { 148 | s *Scanner 149 | } 150 | 151 | func NewParser() *Parser { 152 | return &Parser{s: NewScanner()} 153 | } 154 | 155 | func (p *Parser) Parse(r io.Reader) (root *RqlRootNode, err error) { 156 | // defer func() { 157 | // if r := recover(); r != nil { 158 | // err, ok := r.(error) 159 | // if !ok { 160 | // err = fmt.Errorf("pkg: %v", r) 161 | // } 162 | // fmt.Println(err) 163 | // } 164 | // }() 165 | var tokenStrings []TokenString 166 | if tokenStrings, err = p.s.Scan(r); err != nil { 167 | return nil, err 168 | } 169 | 170 | root = &RqlRootNode{} 171 | 172 | root.Node, err = parse(tokenStrings) 173 | if err != nil { 174 | return nil, err 175 | } 176 | 177 | root.ParseSpecialOps() 178 | 179 | return 180 | } 181 | 182 | func getTokenOp(t Token) string { 183 | switch t { 184 | case AMPERSAND, COMMA: 185 | return "AND" 186 | case PIPE, SEMI_COLON: 187 | return "OR" 188 | } 189 | return "" 190 | } 191 | 192 | func parse(ts []TokenString) (node *RqlNode, err error) { 193 | var childNode *RqlNode 194 | 195 | childTs := [][]TokenString{} 196 | node = &RqlNode{} 197 | 198 | if len(ts) == 0 { 199 | return nil, nil 200 | } 201 | 202 | if isParenthesisBloc(ts) && findClosingIndex(ts[1:]) == len(ts)-2 { 203 | ts = ts[1 : len(ts)-1] 204 | } 205 | 206 | // __printTB("", ts) 207 | node.Op, childTs = splitByBasisOp(ts) 208 | // fmt.Println(len(childTs)) 209 | if node.Op == "" || len(childTs) == 1 { 210 | return getBlocNode(ts) 211 | } 212 | 213 | for _, c := range childTs { 214 | childNode, err = parse(c) 215 | if err != nil { 216 | if err == IsValueError { 217 | node.Args = append(node.Args, c[0].s) 218 | } else { 219 | return nil, err 220 | } 221 | } else { 222 | node.Args = append(node.Args, childNode) 223 | } 224 | } 225 | 226 | return 227 | } 228 | 229 | func isTokenInSlice(tokens []Token, tok Token) bool { 230 | for _, t := range tokens { 231 | if t == tok { 232 | return true 233 | } 234 | } 235 | return false 236 | } 237 | 238 | func splitByBasisOp(tb []TokenString) (op string, tbs [][]TokenString) { 239 | matchingToken := ILLEGAL 240 | 241 | prof := 0 242 | lastIndex := 0 243 | 244 | basisTokenGroups := [][]Token{ 245 | []Token{AMPERSAND, COMMA}, 246 | []Token{PIPE, SEMI_COLON}, 247 | } 248 | for _, bt := range basisTokenGroups { 249 | btExtended := append(bt, ILLEGAL) 250 | for i, ts := range tb { 251 | if ts.t == OPENING_PARENTHESIS { // && lastIndex == i-1 { 252 | prof++ 253 | } else if ts.t == CLOSING_PARENTHESIS && prof > 0 { 254 | prof-- 255 | } else if prof == 0 { 256 | if isTokenInSlice(bt, ts.t) && isTokenInSlice(btExtended, matchingToken) { 257 | matchingToken = ts.t 258 | tbs = append(tbs, tb[lastIndex:i]) 259 | lastIndex = i + 1 260 | } 261 | } 262 | } 263 | if lastIndex != 0 { 264 | break 265 | } 266 | } 267 | 268 | tbs = append(tbs, tb[lastIndex:]) 269 | 270 | op = getTokenOp(matchingToken) 271 | 272 | return 273 | } 274 | 275 | func getBlocNode(tb []TokenString) (*RqlNode, error) { 276 | n := &RqlNode{} 277 | 278 | if isValue(tb) { 279 | return nil, IsValueError 280 | } else if isFuncStyleBloc(tb) { 281 | var err error 282 | n.Op = tb[0].s 283 | tb = tb[2:] 284 | ci := findClosingIndex(tb) 285 | // fmt.Println(len(tb), tb[ci].s) 286 | if len(tb) > ci+1 && tb[ci+1].t != CLOSING_PARENTHESIS && tb[ci+1].t != COMMA { 287 | return nil, fmt.Errorf("Unrecognized func style bloc (missing comma?)") 288 | } 289 | // __printTB("", tb) 290 | // __printTB("", tb[:ci]) 291 | n.Args, err = parseFuncArgs(tb[:ci]) 292 | if err != nil { 293 | return nil, err 294 | } 295 | } else if isSimpleEqualBloc(tb) { 296 | n.Op = "eq" 297 | n.Args = []interface{}{tb[0].s, tb[2].s} 298 | 299 | } else if isDoubleEqualBloc(tb) { 300 | n.Op = tb[2].s 301 | n.Args = []interface{}{tb[0].s} 302 | tbLen := len(tb) 303 | if tbLen == 4 { 304 | n.Args = append(n.Args, ``) 305 | } else if isParenthesisBloc(tb[4:]) && findClosingIndex(tb[5:]) == tbLen-6 { 306 | args, err := parseFuncArgs(tb[5 : tbLen-1]) 307 | if err != nil { 308 | return nil, err 309 | } 310 | n.Args = append(n.Args, args...) 311 | } else { 312 | arg := `` 313 | for _, a := range tb[4:] { 314 | arg = arg + a.s 315 | } 316 | n.Args = append(n.Args, arg) 317 | } 318 | 319 | } else { 320 | return nil, fmt.Errorf("Unrecognized bloc : " + TokenBloc(tb).String()) 321 | } 322 | 323 | return n, nil 324 | } 325 | 326 | func isValue(tb []TokenString) bool { 327 | return len(tb) == 1 && tb[0].t == IDENT 328 | } 329 | 330 | func isParenthesisBloc(tb []TokenString) bool { 331 | return tb[0].t == OPENING_PARENTHESIS 332 | } 333 | 334 | func isFuncStyleBloc(tb []TokenString) bool { 335 | return (tb[0].t == IDENT) && (tb[1].t == OPENING_PARENTHESIS) 336 | } 337 | 338 | func isSimpleEqualBloc(tb []TokenString) bool { 339 | isSimple := (tb[0].t == IDENT && tb[1].t == EQUAL_SIGN) 340 | if len(tb) > 3 { 341 | isSimple = isSimple && tb[3].t != EQUAL_SIGN 342 | } 343 | 344 | return isSimple 345 | } 346 | 347 | func isDoubleEqualBloc(tb []TokenString) bool { 348 | return tb[0].t == IDENT && tb[1].t == EQUAL_SIGN && tb[2].t == IDENT && tb[3].t == EQUAL_SIGN 349 | } 350 | 351 | func parseFuncArgs(tb []TokenString) (args []interface{}, err error) { 352 | var argTokens [][]TokenString 353 | 354 | indexes := findAllTokenIndexes(tb, COMMA) 355 | 356 | if len(indexes) == 0 { 357 | argTokens = append(argTokens, tb) 358 | } else { 359 | lastIndex := 0 360 | for _, i := range indexes { 361 | argTokens = append(argTokens, tb[lastIndex:i]) 362 | lastIndex = i + 1 363 | } 364 | argTokens = append(argTokens, tb[lastIndex:]) 365 | } 366 | 367 | for _, ts := range argTokens { 368 | n, err := parse(ts) 369 | if err != nil { 370 | if err == IsValueError { 371 | args = append(args, ts[0].s) 372 | } else { 373 | return args, err 374 | } 375 | } else { 376 | args = append(args, n) 377 | } 378 | } 379 | 380 | return 381 | } 382 | 383 | func findClosingIndex(tb []TokenString) int { 384 | i := findTokenIndex(tb, CLOSING_PARENTHESIS) 385 | return i 386 | } 387 | 388 | func findTokenIndex(tb []TokenString, token Token) int { 389 | prof := 0 390 | for i, ts := range tb { 391 | if ts.t == OPENING_PARENTHESIS { 392 | prof++ 393 | } else if ts.t == CLOSING_PARENTHESIS { 394 | if prof == 0 && token == CLOSING_PARENTHESIS { 395 | return i 396 | } 397 | prof-- 398 | } else if token == ts.t && prof == 0 { 399 | return i 400 | } 401 | } 402 | return -1 403 | } 404 | 405 | func findAllTokenIndexes(tb []TokenString, token Token) (indexes []int) { 406 | prof := 0 407 | for i, ts := range tb { 408 | if ts.t == OPENING_PARENTHESIS { 409 | prof++ 410 | } else if ts.t == CLOSING_PARENTHESIS { 411 | if prof == 0 && token == CLOSING_PARENTHESIS { 412 | indexes = append(indexes, i) 413 | } 414 | prof-- 415 | } else if token == ts.t && prof == 0 { 416 | indexes = append(indexes, i) 417 | } 418 | } 419 | return 420 | } 421 | 422 | func __printTB(s string, tb []TokenString) { 423 | fmt.Printf("%s%s\n", s, TokenBloc(tb).String()) 424 | } 425 | --------------------------------------------------------------------------------