github.com/rohankumardubey/aresdb@v0.0.2-0.20190517170215-e54e3ca06b9c/query/expr/scanner_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 scanner can scan tokens correctly.
    32  func TestScanner_Scan(t *testing.T) {
    33  	var tests = []struct {
    34  		s   string
    35  		tok expr.Token
    36  		lit string
    37  		pos expr.Pos
    38  	}{
    39  		// Special tokens (EOF, ILLEGAL, WS)
    40  		{s: ``, tok: expr.EOF},
    41  		{s: `#`, tok: expr.ILLEGAL, lit: `#`},
    42  		{s: ` `, tok: expr.WS, lit: " "},
    43  		{s: "\t", tok: expr.WS, lit: "\t"},
    44  		{s: "\n", tok: expr.WS, lit: "\n"},
    45  		{s: "\r", tok: expr.WS, lit: "\n"},
    46  		{s: "\r\n", tok: expr.WS, lit: "\n"},
    47  		{s: "\rX", tok: expr.WS, lit: "\n"},
    48  		{s: "\n\r", tok: expr.WS, lit: "\n\n"},
    49  		{s: " \n\t \r\n\t", tok: expr.WS, lit: " \n\t \n\t"},
    50  		{s: " foo", tok: expr.WS, lit: " "},
    51  
    52  		// Numeric operators
    53  		{s: `*`, tok: expr.MUL},
    54  		{s: `/`, tok: expr.DIV},
    55  
    56  		// Logical operators
    57  		{s: `AND`, tok: expr.AND},
    58  		{s: `and`, tok: expr.AND},
    59  		{s: `OR`, tok: expr.OR},
    60  		{s: `or`, tok: expr.OR},
    61  
    62  		{s: `=`, tok: expr.EQ},
    63  		{s: `<>`, tok: expr.NEQ},
    64  		{s: `! `, tok: expr.EXCLAMATION},
    65  		{s: `<`, tok: expr.LT},
    66  		{s: `<=`, tok: expr.LTE},
    67  		{s: `>`, tok: expr.GT},
    68  		{s: `>=`, tok: expr.GTE},
    69  
    70  		// Misc tokens
    71  		{s: `(`, tok: expr.LPAREN},
    72  		{s: `)`, tok: expr.RPAREN},
    73  		{s: `,`, tok: expr.COMMA},
    74  		{s: `.`, tok: expr.DOT},
    75  
    76  		// Identifiers
    77  		{s: `foo`, tok: expr.IDENT, lit: `foo`},
    78  		{s: `_foo`, tok: expr.IDENT, lit: `_foo`},
    79  		{s: `Zx12_3U_-`, tok: expr.IDENT, lit: `Zx12_3U_`},
    80  		{s: "`foo`", tok: expr.IDENT, lit: `foo`},
    81  		{s: "`foo\\\\bar`", tok: expr.IDENT, lit: `foo\bar`},
    82  		{s: "`foo\\bar`", tok: expr.BADESCAPE, lit: `\b`, pos: expr.Pos{Line: 0, Char: 5}},
    83  		{s: "`foo\"bar\"`", tok: expr.IDENT, lit: `foo"bar"`},
    84  		{s: "test`", tok: expr.BADSTRING, lit: "", pos: expr.Pos{Line: 0, Char: 3}},
    85  		{s: "`test", tok: expr.BADSTRING, lit: `test`},
    86  
    87  		{s: `true`, tok: expr.TRUE},
    88  		{s: `false`, tok: expr.FALSE},
    89  
    90  		// Strings
    91  		{s: `'testing 123!'`, tok: expr.STRING, lit: `testing 123!`},
    92  		{s: `"testing 123!"`, tok: expr.STRING, lit: `testing 123!`},
    93  		{s: `'foo\nbar'`, tok: expr.STRING, lit: "foo\nbar"},
    94  		{s: `'foo\\bar'`, tok: expr.STRING, lit: "foo\\bar"},
    95  		{s: `'test`, tok: expr.BADSTRING, lit: `test`},
    96  		{s: "'test\nfoo", tok: expr.BADSTRING, lit: `test`},
    97  		{s: `'test\g'`, tok: expr.BADESCAPE, lit: `\g`, pos: expr.Pos{Line: 0, Char: 6}},
    98  
    99  		// Numbers
   100  		{s: `100`, tok: expr.NUMBER, lit: `100`},
   101  		{s: `100.23`, tok: expr.NUMBER, lit: `100.23`},
   102  		{s: `+100.23`, tok: expr.NUMBER, lit: `+100.23`},
   103  		{s: `-100.23`, tok: expr.NUMBER, lit: `-100.23`},
   104  		{s: `10.3s`, tok: expr.NUMBER, lit: `10.3`},
   105  
   106  		// Keywords
   107  		{s: `ALL`, tok: expr.ALL},
   108  		{s: `AS`, tok: expr.AS},
   109  		{s: `ASC`, tok: expr.ASC},
   110  		{s: `BEGIN`, tok: expr.BEGIN},
   111  		{s: `BY`, tok: expr.BY},
   112  		{s: `DEFAULT`, tok: expr.DEFAULT},
   113  		{s: `DELETE`, tok: expr.DELETE},
   114  		{s: `DESC`, tok: expr.DESC},
   115  		{s: `DROP`, tok: expr.DROP},
   116  		{s: `END`, tok: expr.END},
   117  		{s: `EXISTS`, tok: expr.EXISTS},
   118  		{s: `FIELD`, tok: expr.FIELD},
   119  		{s: `FROM`, tok: expr.FROM},
   120  		{s: `GROUP`, tok: expr.GROUP},
   121  		{s: `IF`, tok: expr.IF},
   122  		{s: `INNER`, tok: expr.INNER},
   123  		{s: `INSERT`, tok: expr.INSERT},
   124  		{s: `KEY`, tok: expr.KEY},
   125  		{s: `KEYS`, tok: expr.KEYS},
   126  		{s: `LIMIT`, tok: expr.LIMIT},
   127  		{s: `NOT`, tok: expr.NOT},
   128  		{s: `OFFSET`, tok: expr.OFFSET},
   129  		{s: `ON`, tok: expr.ON},
   130  		{s: `ORDER`, tok: expr.ORDER},
   131  		{s: `SELECT`, tok: expr.SELECT},
   132  		{s: `TO`, tok: expr.TO},
   133  		{s: `VALUES`, tok: expr.VALUES},
   134  		{s: `WHERE`, tok: expr.WHERE},
   135  		{s: `WITH`, tok: expr.WITH},
   136  		{s: `seLECT`, tok: expr.SELECT}, // case insensitive
   137  	}
   138  
   139  	for i, tt := range tests {
   140  		s := expr.NewScanner(strings.NewReader(tt.s))
   141  		tok, pos, lit := s.Scan()
   142  		if tt.tok != tok {
   143  			t.Errorf("%d. %q token mismatch: exp=%q got=%q <%q>", i, tt.s, tt.tok, tok, lit)
   144  		} else if tt.pos.Line != pos.Line || tt.pos.Char != pos.Char {
   145  			t.Errorf("%d. %q pos mismatch: exp=%#v got=%#v", i, tt.s, tt.pos, pos)
   146  		} else if tt.lit != lit {
   147  			t.Errorf("%d. %q literal mismatch: exp=%q got=%q", i, tt.s, tt.lit, lit)
   148  		}
   149  	}
   150  }
   151  
   152  // Ensure the scanner can scan a series of tokens correctly.
   153  func TestScanner_Scan_Multi(t *testing.T) {
   154  	type result struct {
   155  		tok expr.Token
   156  		pos expr.Pos
   157  		lit string
   158  	}
   159  	exp := []result{
   160  		{tok: expr.SELECT, pos: expr.Pos{Line: 0, Char: 0}, lit: ""},
   161  		{tok: expr.WS, pos: expr.Pos{Line: 0, Char: 6}, lit: " "},
   162  		{tok: expr.IDENT, pos: expr.Pos{Line: 0, Char: 7}, lit: "value"},
   163  		{tok: expr.WS, pos: expr.Pos{Line: 0, Char: 12}, lit: " "},
   164  		{tok: expr.FROM, pos: expr.Pos{Line: 0, Char: 13}, lit: ""},
   165  		{tok: expr.WS, pos: expr.Pos{Line: 0, Char: 17}, lit: " "},
   166  		{tok: expr.IDENT, pos: expr.Pos{Line: 0, Char: 18}, lit: "myseries"},
   167  		{tok: expr.WS, pos: expr.Pos{Line: 0, Char: 26}, lit: " "},
   168  		{tok: expr.WHERE, pos: expr.Pos{Line: 0, Char: 27}, lit: ""},
   169  		{tok: expr.WS, pos: expr.Pos{Line: 0, Char: 32}, lit: " "},
   170  		{tok: expr.IDENT, pos: expr.Pos{Line: 0, Char: 33}, lit: "a"},
   171  		{tok: expr.WS, pos: expr.Pos{Line: 0, Char: 34}, lit: " "},
   172  		{tok: expr.EQ, pos: expr.Pos{Line: 0, Char: 35}, lit: ""},
   173  		{tok: expr.WS, pos: expr.Pos{Line: 0, Char: 36}, lit: " "},
   174  		{tok: expr.STRING, pos: expr.Pos{Line: 0, Char: 36}, lit: "b"},
   175  		{tok: expr.EOF, pos: expr.Pos{Line: 0, Char: 40}, lit: ""},
   176  	}
   177  
   178  	// Create a scanner.
   179  	v := `SELECT value from myseries WHERE a = 'b'`
   180  	s := expr.NewScanner(strings.NewReader(v))
   181  
   182  	// Continually scan until we reach the end.
   183  	var act []result
   184  	for {
   185  		tok, pos, lit := s.Scan()
   186  		act = append(act, result{tok, pos, lit})
   187  		if tok == expr.EOF {
   188  			break
   189  		}
   190  	}
   191  
   192  	// Verify the token counts match.
   193  	if len(exp) != len(act) {
   194  		t.Fatalf("token count mismatch: exp=%d, got=%d", len(exp), len(act))
   195  	}
   196  
   197  	// Verify each token matches.
   198  	for i := range exp {
   199  		if !reflect.DeepEqual(exp[i], act[i]) {
   200  			t.Fatalf("%d. token mismatch:\n\nexp=%#v\n\ngot=%#v", i, exp[i], act[i])
   201  		}
   202  	}
   203  }
   204  
   205  // Ensure the library can correctly scan strings.
   206  func TestScanString(t *testing.T) {
   207  	var tests = []struct {
   208  		in  string
   209  		out string
   210  		err string
   211  	}{
   212  		{in: `""`, out: ``},
   213  		{in: `"foo bar"`, out: `foo bar`},
   214  		{in: `'foo bar'`, out: `foo bar`},
   215  		{in: `"foo\nbar"`, out: "foo\nbar"},
   216  		{in: `"foo\\bar"`, out: `foo\bar`},
   217  		{in: `"foo\"bar"`, out: `foo"bar`},
   218  		{in: `'foo\'bar'`, out: `foo'bar`},
   219  
   220  		{in: `"foo` + "\n", out: `foo`, err: "bad string"}, // newline in string
   221  		{in: `"foo`, out: `foo`, err: "bad string"},        // unclosed quotes
   222  		{in: `"foo\xbar"`, out: `\x`, err: "bad escape"},   // invalid escape
   223  	}
   224  
   225  	for i, tt := range tests {
   226  		out, err := expr.ScanString(strings.NewReader(tt.in))
   227  		if tt.err != errstring(err) {
   228  			t.Errorf("%d. %s: error: exp=%s, got=%s", i, tt.in, tt.err, err)
   229  		} else if tt.out != out {
   230  			t.Errorf("%d. %s: out: exp=%s, got=%s", i, tt.in, tt.out, out)
   231  		}
   232  	}
   233  }