github.com/rohankumardubey/aresdb@v0.0.2-0.20190517170215-e54e3ca06b9c/query/expr/parser_test.go (about)

     1  // Modifications Copyright (c) 2017-2018 Uber Technologies, Inc.
     2  // Copyright (c) 2013-2016 Errplane Inc.
     3  //
     4  // Permission is hereby granted, free of charge, to any person obtaining a copy of
     5  // this software and associated documentation files (the "Software"), to deal in
     6  // the Software without restriction, including without limitation the rights to
     7  // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
     8  // the Software, and to permit persons to whom the Software is furnished to do so,
     9  // subject to the following conditions:
    10  //
    11  // The above copyright notice and this permission notice shall be included in all
    12  // copies or substantial portions of the Software.
    13  //
    14  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    15  // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
    16  // FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
    17  // COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
    18  // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
    19  // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
    20  
    21  package expr_test
    22  
    23  import (
    24  	"reflect"
    25  	"strings"
    26  	"testing"
    27  
    28  	"github.com/uber/aresdb/query/expr"
    29  )
    30  
    31  // Ensure the parser can parse expressions into an AST.
    32  func TestParser_ParseExpr(t *testing.T) {
    33  	var tests = []struct {
    34  		s    string
    35  		expr expr.Expr
    36  		err  string
    37  	}{
    38  		// Primitives
    39  		{s: `100`, expr: &expr.NumberLiteral{Val: 100, Int: 100, Expr: "100", ExprType: expr.Unsigned}},
    40  		{s: `'foo bar'`, expr: &expr.StringLiteral{Val: "foo bar"}},
    41  		{s: `true`, expr: &expr.BooleanLiteral{Val: true}},
    42  		{s: `false`, expr: &expr.BooleanLiteral{Val: false}},
    43  		{s: `my_ident`, expr: &expr.VarRef{Val: "my_ident"}},
    44  		{s: `*`, expr: &expr.Wildcard{}},
    45  
    46  		// Simple binary expression
    47  		{
    48  			s: `1 + 2`,
    49  			expr: &expr.BinaryExpr{
    50  				Op:  expr.ADD,
    51  				LHS: &expr.NumberLiteral{Val: 1, Int: 1, Expr: "1", ExprType: expr.Unsigned},
    52  				RHS: &expr.NumberLiteral{Val: 2, Int: 2, Expr: "2", ExprType: expr.Unsigned},
    53  			},
    54  		},
    55  
    56  		// Binary expression with LHS precedence
    57  		{
    58  			s: `1 * 2 + 3`,
    59  			expr: &expr.BinaryExpr{
    60  				Op: expr.ADD,
    61  				LHS: &expr.BinaryExpr{
    62  					Op:  expr.MUL,
    63  					LHS: &expr.NumberLiteral{Val: 1, Int: 1, Expr: "1", ExprType: expr.Unsigned},
    64  					RHS: &expr.NumberLiteral{Val: 2, Int: 2, Expr: "2", ExprType: expr.Unsigned},
    65  				},
    66  				RHS: &expr.NumberLiteral{Val: 3, Int: 3, Expr: "3", ExprType: expr.Unsigned},
    67  			},
    68  		},
    69  
    70  		// Binary expression with RHS precedence
    71  		{
    72  			s: `1 + 2 * 3`,
    73  			expr: &expr.BinaryExpr{
    74  				Op:  expr.ADD,
    75  				LHS: &expr.NumberLiteral{Val: 1, Int: 1, Expr: "1", ExprType: expr.Unsigned},
    76  				RHS: &expr.BinaryExpr{
    77  					Op:  expr.MUL,
    78  					LHS: &expr.NumberLiteral{Val: 2, Int: 2, Expr: "2", ExprType: expr.Unsigned},
    79  					RHS: &expr.NumberLiteral{Val: 3, Int: 3, Expr: "3", ExprType: expr.Unsigned},
    80  				},
    81  			},
    82  		},
    83  
    84  		// Binary expression with LHS paren group.
    85  		{
    86  			s: `(1 + 2) * 3`,
    87  			expr: &expr.BinaryExpr{
    88  				Op: expr.MUL,
    89  				LHS: &expr.ParenExpr{
    90  					Expr: &expr.BinaryExpr{
    91  						Op:  expr.ADD,
    92  						LHS: &expr.NumberLiteral{Val: 1, Int: 1, Expr: "1", ExprType: expr.Unsigned},
    93  						RHS: &expr.NumberLiteral{Val: 2, Int: 2, Expr: "2", ExprType: expr.Unsigned},
    94  					},
    95  				},
    96  				RHS: &expr.NumberLiteral{Val: 3, Int: 3, Expr: "3", ExprType: expr.Unsigned},
    97  			},
    98  		},
    99  
   100  		// Binary expression with no precedence, tests left associativity.
   101  		{
   102  			s: `1 * 2 * 3`,
   103  			expr: &expr.BinaryExpr{
   104  				Op: expr.MUL,
   105  				LHS: &expr.BinaryExpr{
   106  					Op:  expr.MUL,
   107  					LHS: &expr.NumberLiteral{Val: 1, Int: 1, Expr: "1", ExprType: expr.Unsigned},
   108  					RHS: &expr.NumberLiteral{Val: 2, Int: 2, Expr: "2", ExprType: expr.Unsigned},
   109  				},
   110  				RHS: &expr.NumberLiteral{Val: 3, Int: 3, Expr: "3", ExprType: expr.Unsigned},
   111  			},
   112  		},
   113  
   114  		// Binary expression with IN.
   115  		{
   116  			s: "id IN (12, 18)",
   117  			expr: &expr.BinaryExpr{
   118  				Op:  expr.IN,
   119  				LHS: &expr.VarRef{Val: "id"},
   120  				RHS: &expr.Call{Name: "", Args: []expr.Expr{
   121  					&expr.NumberLiteral{Val: 12, Int: 12, Expr: "12", ExprType: expr.Unsigned},
   122  					&expr.NumberLiteral{Val: 18, Int: 18, Expr: "18", ExprType: expr.Unsigned},
   123  				}},
   124  			},
   125  		},
   126  		{
   127  			s: "id IN (12)",
   128  			expr: &expr.BinaryExpr{
   129  				Op:  expr.IN,
   130  				LHS: &expr.VarRef{Val: "id"},
   131  				RHS: &expr.Call{Name: "", Args: []expr.Expr{
   132  					&expr.NumberLiteral{Val: 12, Int: 12, Expr: "12", ExprType: expr.Unsigned},
   133  				}},
   134  			},
   135  		},
   136  		{
   137  			s: "id IN (12, 15)",
   138  			expr: &expr.BinaryExpr{
   139  				Op:  expr.IN,
   140  				LHS: &expr.VarRef{Val: "id"},
   141  				RHS: &expr.Call{Name: "", Args: []expr.Expr{
   142  					&expr.NumberLiteral{Val: 12, Int: 12, Expr: "12", ExprType: expr.Unsigned},
   143  					&expr.NumberLiteral{Val: 15, Int: 15, Expr: "15", ExprType: expr.Unsigned},
   144  				}},
   145  			},
   146  		},
   147  		// Binary expression with NOT IN.
   148  		{
   149  			s: "id NOT IN (12, 18)",
   150  			expr: &expr.BinaryExpr{
   151  				Op:  expr.NOT_IN,
   152  				LHS: &expr.VarRef{Val: "id"},
   153  				RHS: &expr.Call{Name: "", Args: []expr.Expr{
   154  					&expr.NumberLiteral{Val: 12, Int: 12, Expr: "12", ExprType: expr.Unsigned},
   155  					&expr.NumberLiteral{Val: 18, Int: 18, Expr: "18", ExprType: expr.Unsigned},
   156  				}},
   157  			},
   158  		},
   159  		{
   160  			s: "id NOT IN (12, 15)",
   161  			expr: &expr.BinaryExpr{
   162  				Op:  expr.NOT_IN,
   163  				LHS: &expr.VarRef{Val: "id"},
   164  				RHS: &expr.Call{Name: "", Args: []expr.Expr{
   165  					&expr.NumberLiteral{Val: 12, Int: 12, Expr: "12", ExprType: expr.Unsigned},
   166  					&expr.NumberLiteral{Val: 15, Int: 15, Expr: "15", ExprType: expr.Unsigned},
   167  				}},
   168  			},
   169  		},
   170  		// Unary expression.
   171  		{
   172  			s: "not now",
   173  			expr: &expr.UnaryExpr{
   174  				Op:   expr.NOT,
   175  				Expr: &expr.VarRef{Val: "now"},
   176  			},
   177  		},
   178  		{
   179  			s: "!today",
   180  			expr: &expr.UnaryExpr{
   181  				Op:   expr.EXCLAMATION,
   182  				Expr: &expr.VarRef{Val: "today"},
   183  			},
   184  		},
   185  		{
   186  			s: "-c",
   187  			expr: &expr.UnaryExpr{
   188  				Op:   expr.UNARY_MINUS,
   189  				Expr: &expr.VarRef{Val: "c"},
   190  			},
   191  		},
   192  		{
   193  			s: "not ! a + b and c",
   194  			expr: &expr.BinaryExpr{
   195  				Op: expr.AND,
   196  				LHS: &expr.UnaryExpr{
   197  					Op: expr.NOT,
   198  					Expr: &expr.BinaryExpr{
   199  						Op: expr.ADD,
   200  						LHS: &expr.UnaryExpr{
   201  							Op:   expr.EXCLAMATION,
   202  							Expr: &expr.VarRef{Val: "a"},
   203  						},
   204  						RHS: &expr.VarRef{Val: "b"},
   205  					},
   206  				},
   207  				RHS: &expr.VarRef{Val: "c"},
   208  			},
   209  		},
   210  		// Derived unary expression.
   211  		{
   212  			s: "a is null",
   213  			expr: &expr.UnaryExpr{
   214  				Op:   expr.IS_NULL,
   215  				Expr: &expr.VarRef{Val: "a"},
   216  			},
   217  		},
   218  		{
   219  			s: "a is not null",
   220  			expr: &expr.UnaryExpr{
   221  				Op:   expr.IS_NOT_NULL,
   222  				Expr: &expr.VarRef{Val: "a"},
   223  			},
   224  		},
   225  		{
   226  			s: "a is unknown",
   227  			expr: &expr.UnaryExpr{
   228  				Op:   expr.IS_NULL,
   229  				Expr: &expr.VarRef{Val: "a"},
   230  			},
   231  		},
   232  		{
   233  			s: "a is true",
   234  			expr: &expr.UnaryExpr{
   235  				Op:   expr.IS_TRUE,
   236  				Expr: &expr.VarRef{Val: "a"},
   237  			},
   238  		},
   239  		{
   240  			s: "a is not true",
   241  			expr: &expr.UnaryExpr{
   242  				Op:   expr.IS_FALSE,
   243  				Expr: &expr.VarRef{Val: "a"},
   244  			},
   245  		},
   246  		{
   247  			s: "not ! a is not true and c",
   248  			expr: &expr.BinaryExpr{
   249  				Op: expr.AND,
   250  				LHS: &expr.UnaryExpr{
   251  					Op: expr.NOT,
   252  					Expr: &expr.UnaryExpr{
   253  						Op: expr.IS_FALSE,
   254  						Expr: &expr.UnaryExpr{
   255  							Op:   expr.EXCLAMATION,
   256  							Expr: &expr.VarRef{Val: "a"},
   257  						},
   258  					},
   259  				},
   260  				RHS: &expr.VarRef{Val: "c"},
   261  			},
   262  		},
   263  
   264  		// Complex binary expression.
   265  		{
   266  			s: `value + 3 < 30 AND 1 + 2 OR true`,
   267  			expr: &expr.BinaryExpr{
   268  				Op: expr.OR,
   269  				LHS: &expr.BinaryExpr{
   270  					Op: expr.AND,
   271  					LHS: &expr.BinaryExpr{
   272  						Op: expr.LT,
   273  						LHS: &expr.BinaryExpr{
   274  							Op:  expr.ADD,
   275  							LHS: &expr.VarRef{Val: "value"},
   276  							RHS: &expr.NumberLiteral{Val: 3, Int: 3, Expr: "3", ExprType: expr.Unsigned},
   277  						},
   278  						RHS: &expr.NumberLiteral{Val: 30, Int: 30, Expr: "30", ExprType: expr.Unsigned},
   279  					},
   280  					RHS: &expr.BinaryExpr{
   281  						Op:  expr.ADD,
   282  						LHS: &expr.NumberLiteral{Val: 1, Int: 1, Expr: "1", ExprType: expr.Unsigned},
   283  						RHS: &expr.NumberLiteral{Val: 2, Int: 2, Expr: "2", ExprType: expr.Unsigned},
   284  					},
   285  				},
   286  				RHS: &expr.BooleanLiteral{Val: true},
   287  			},
   288  		},
   289  
   290  		// Case
   291  		{
   292  			s: "case when a then b end",
   293  			expr: &expr.Case{
   294  				WhenThens: []expr.WhenThen{
   295  					{
   296  						When: &expr.VarRef{Val: "a"},
   297  						Then: &expr.VarRef{Val: "b"},
   298  					},
   299  				},
   300  			},
   301  		},
   302  		{
   303  			s: "case when a then b else c end",
   304  			expr: &expr.Case{
   305  				WhenThens: []expr.WhenThen{
   306  					{
   307  						When: &expr.VarRef{Val: "a"},
   308  						Then: &expr.VarRef{Val: "b"},
   309  					},
   310  				},
   311  				Else: &expr.VarRef{Val: "c"},
   312  			},
   313  		},
   314  		{
   315  			s: "case when a then b when a2 then b2 else c end",
   316  			expr: &expr.Case{
   317  				WhenThens: []expr.WhenThen{
   318  					{
   319  						When: &expr.VarRef{Val: "a"},
   320  						Then: &expr.VarRef{Val: "b"},
   321  					},
   322  					{
   323  						When: &expr.VarRef{Val: "a2"},
   324  						Then: &expr.VarRef{Val: "b2"},
   325  					},
   326  				},
   327  				Else: &expr.VarRef{Val: "c"},
   328  			},
   329  		},
   330  		{
   331  			s:   "case end",
   332  			err: "found END, expected WHEN at line 1, char 6",
   333  		},
   334  		{
   335  			s:   "case else b end",
   336  			err: "found ELSE, expected WHEN at line 1, char 6",
   337  		},
   338  		{
   339  			s:   "case when a then b",
   340  			err: "found EOF, expected END at line 1, char 20",
   341  		},
   342  
   343  		// Function call (empty)
   344  		{
   345  			s: `my_func()`,
   346  			expr: &expr.Call{
   347  				Name: "my_func",
   348  			},
   349  		},
   350  
   351  		// Function call (multi-arg)
   352  		{
   353  			s: `my_func(1, -2 + 3)`,
   354  			expr: &expr.Call{
   355  				Name: "my_func",
   356  				Args: []expr.Expr{
   357  					&expr.NumberLiteral{Val: 1, Int: 1, Expr: "1", ExprType: expr.Unsigned},
   358  					&expr.BinaryExpr{
   359  						Op:  expr.ADD,
   360  						LHS: &expr.NumberLiteral{Val: -2, Int: -2, Expr: "-2", ExprType: expr.Signed},
   361  						RHS: &expr.NumberLiteral{Val: 3, Int: 3, Expr: "3", ExprType: expr.Unsigned},
   362  					},
   363  				},
   364  			},
   365  		},
   366  	}
   367  
   368  	for i, tt := range tests {
   369  		expr, err := expr.NewParser(strings.NewReader(tt.s)).ParseExpr(0)
   370  		if !reflect.DeepEqual(tt.err, errstring(err)) {
   371  			t.Errorf("%d. %q: error mismatch:\n  exp=%s\n  got=%s\n\n", i, tt.s, tt.err, err)
   372  		} else if tt.err == "" && !reflect.DeepEqual(tt.expr, expr) {
   373  			t.Errorf("%d. %q\n\nexpr mismatch:\n\nexp=%#v\n\ngot=%#v\n\n", i, tt.s, tt.expr, expr)
   374  		}
   375  	}
   376  }
   377  
   378  // Ensure a string can be quoted.
   379  func TestQuote(t *testing.T) {
   380  	for i, tt := range []struct {
   381  		in  string
   382  		out string
   383  	}{
   384  		{``, `''`},
   385  		{`foo`, `'foo'`},
   386  		{"foo\nbar", `'foo\nbar'`},
   387  		{`foo bar\\`, `'foo bar\\\\'`},
   388  		{`'foo'`, `'\'foo\''`},
   389  	} {
   390  		if out := expr.QuoteString(tt.in); tt.out != out {
   391  			t.Errorf("%d. %s: mismatch: %s != %s", i, tt.in, tt.out, out)
   392  		}
   393  	}
   394  }
   395  
   396  // Ensure an identifier's segments can be quoted.
   397  func TestQuoteIdent(t *testing.T) {
   398  	for i, tt := range []struct {
   399  		ident []string
   400  		s     string
   401  	}{
   402  		{[]string{``}, ``},
   403  		{[]string{`select`}, `"select"`},
   404  		{[]string{`in-bytes`}, `"in-bytes"`},
   405  		{[]string{`foo`, `bar`}, `"foo".bar`},
   406  		{[]string{`foo`, ``, `bar`}, `"foo"..bar`},
   407  		{[]string{`foo bar`, `baz`}, `"foo bar".baz`},
   408  		{[]string{`foo.bar`, `baz`}, `"foo.bar".baz`},
   409  		{[]string{`foo.bar`, `rp`, `baz`}, `"foo.bar"."rp".baz`},
   410  		{[]string{`foo.bar`, `rp`, `1baz`}, `"foo.bar"."rp"."1baz"`},
   411  	} {
   412  		if s := expr.QuoteIdent(tt.ident...); tt.s != s {
   413  			t.Errorf("%d. %s: mismatch: %s != %s", i, tt.ident, tt.s, s)
   414  		}
   415  	}
   416  }
   417  
   418  // errstring converts an error to its string representation.
   419  func errstring(err error) string {
   420  	if err != nil {
   421  		return err.Error()
   422  	}
   423  	return ""
   424  }