github.com/influxdata/influxql@v1.1.0/scanner_test.go (about)

     1  package influxql_test
     2  
     3  import (
     4  	"reflect"
     5  	"strings"
     6  	"testing"
     7  
     8  	"github.com/influxdata/influxql"
     9  )
    10  
    11  // Ensure the scanner can scan tokens correctly.
    12  func TestScanner_Scan(t *testing.T) {
    13  	var tests = []struct {
    14  		s   string
    15  		tok influxql.Token
    16  		lit string
    17  		pos influxql.Pos
    18  	}{
    19  		// Special tokens (EOF, ILLEGAL, WS)
    20  		{s: ``, tok: influxql.EOF},
    21  		{s: `#`, tok: influxql.ILLEGAL, lit: `#`},
    22  		{s: ` `, tok: influxql.WS, lit: " "},
    23  		{s: "\t", tok: influxql.WS, lit: "\t"},
    24  		{s: "\n", tok: influxql.WS, lit: "\n"},
    25  		{s: "\r", tok: influxql.WS, lit: "\n"},
    26  		{s: "\r\n", tok: influxql.WS, lit: "\n"},
    27  		{s: "\rX", tok: influxql.WS, lit: "\n"},
    28  		{s: "\n\r", tok: influxql.WS, lit: "\n\n"},
    29  		{s: " \n\t \r\n\t", tok: influxql.WS, lit: " \n\t \n\t"},
    30  		{s: " foo", tok: influxql.WS, lit: " "},
    31  
    32  		// Numeric operators
    33  		{s: `+`, tok: influxql.ADD},
    34  		{s: `-`, tok: influxql.SUB},
    35  		{s: `*`, tok: influxql.MUL},
    36  		{s: `/`, tok: influxql.DIV},
    37  		{s: `%`, tok: influxql.MOD},
    38  
    39  		// Logical operators
    40  		{s: `AND`, tok: influxql.AND},
    41  		{s: `and`, tok: influxql.AND},
    42  		{s: `OR`, tok: influxql.OR},
    43  		{s: `or`, tok: influxql.OR},
    44  
    45  		{s: `=`, tok: influxql.EQ},
    46  		{s: `<>`, tok: influxql.NEQ},
    47  		{s: `! `, tok: influxql.ILLEGAL, lit: "!"},
    48  		{s: `<`, tok: influxql.LT},
    49  		{s: `<=`, tok: influxql.LTE},
    50  		{s: `>`, tok: influxql.GT},
    51  		{s: `>=`, tok: influxql.GTE},
    52  
    53  		// Misc tokens
    54  		{s: `(`, tok: influxql.LPAREN},
    55  		{s: `)`, tok: influxql.RPAREN},
    56  		{s: `,`, tok: influxql.COMMA},
    57  		{s: `;`, tok: influxql.SEMICOLON},
    58  		{s: `.`, tok: influxql.DOT},
    59  		{s: `=~`, tok: influxql.EQREGEX},
    60  		{s: `!~`, tok: influxql.NEQREGEX},
    61  		{s: `:`, tok: influxql.COLON},
    62  		{s: `::`, tok: influxql.DOUBLECOLON},
    63  
    64  		// Identifiers
    65  		{s: `foo`, tok: influxql.IDENT, lit: `foo`},
    66  		{s: `_foo`, tok: influxql.IDENT, lit: `_foo`},
    67  		{s: `Zx12_3U_-`, tok: influxql.IDENT, lit: `Zx12_3U_`},
    68  		{s: `"foo"`, tok: influxql.IDENT, lit: `foo`},
    69  		{s: `"foo\\bar"`, tok: influxql.IDENT, lit: `foo\bar`},
    70  		{s: `"foo\bar"`, tok: influxql.BADESCAPE, lit: `\b`, pos: influxql.Pos{Line: 0, Char: 5}},
    71  		{s: `"foo\"bar\""`, tok: influxql.IDENT, lit: `foo"bar"`},
    72  		{s: `test"`, tok: influxql.BADSTRING, lit: "", pos: influxql.Pos{Line: 0, Char: 3}},
    73  		{s: `"test`, tok: influxql.BADSTRING, lit: `test`},
    74  		{s: `$host`, tok: influxql.BOUNDPARAM, lit: `$host`},
    75  		{s: `$"host param"`, tok: influxql.BOUNDPARAM, lit: `$host param`},
    76  
    77  		{s: `true`, tok: influxql.TRUE},
    78  		{s: `false`, tok: influxql.FALSE},
    79  
    80  		// Strings
    81  		{s: `'testing 123!'`, tok: influxql.STRING, lit: `testing 123!`},
    82  		{s: `'foo\nbar'`, tok: influxql.STRING, lit: "foo\nbar"},
    83  		{s: `'foo\\bar'`, tok: influxql.STRING, lit: "foo\\bar"},
    84  		{s: `'test`, tok: influxql.BADSTRING, lit: `test`},
    85  		{s: "'test\nfoo", tok: influxql.BADSTRING, lit: `test`},
    86  		{s: `'test\g'`, tok: influxql.BADESCAPE, lit: `\g`, pos: influxql.Pos{Line: 0, Char: 6}},
    87  
    88  		// Numbers
    89  		{s: `100`, tok: influxql.INTEGER, lit: `100`},
    90  		{s: `100.23`, tok: influxql.NUMBER, lit: `100.23`},
    91  		{s: `.23`, tok: influxql.NUMBER, lit: `.23`},
    92  		//{s: `.`, tok: influxql.ILLEGAL, lit: `.`},
    93  		{s: `10.3s`, tok: influxql.NUMBER, lit: `10.3`},
    94  
    95  		// Durations
    96  		{s: `10u`, tok: influxql.DURATIONVAL, lit: `10u`},
    97  		{s: `10µ`, tok: influxql.DURATIONVAL, lit: `10µ`},
    98  		{s: `10ms`, tok: influxql.DURATIONVAL, lit: `10ms`},
    99  		{s: `1s`, tok: influxql.DURATIONVAL, lit: `1s`},
   100  		{s: `10m`, tok: influxql.DURATIONVAL, lit: `10m`},
   101  		{s: `10h`, tok: influxql.DURATIONVAL, lit: `10h`},
   102  		{s: `10d`, tok: influxql.DURATIONVAL, lit: `10d`},
   103  		{s: `10w`, tok: influxql.DURATIONVAL, lit: `10w`},
   104  		{s: `10x`, tok: influxql.DURATIONVAL, lit: `10x`}, // non-duration unit, but scanned as a duration value
   105  
   106  		// Keywords
   107  		{s: `ALL`, tok: influxql.ALL},
   108  		{s: `ALTER`, tok: influxql.ALTER},
   109  		{s: `AS`, tok: influxql.AS},
   110  		{s: `ASC`, tok: influxql.ASC},
   111  		{s: `BEGIN`, tok: influxql.BEGIN},
   112  		{s: `BY`, tok: influxql.BY},
   113  		{s: `CREATE`, tok: influxql.CREATE},
   114  		{s: `CONTINUOUS`, tok: influxql.CONTINUOUS},
   115  		{s: `DATABASE`, tok: influxql.DATABASE},
   116  		{s: `DATABASES`, tok: influxql.DATABASES},
   117  		{s: `DEFAULT`, tok: influxql.DEFAULT},
   118  		{s: `DELETE`, tok: influxql.DELETE},
   119  		{s: `DESC`, tok: influxql.DESC},
   120  		{s: `DROP`, tok: influxql.DROP},
   121  		{s: `DURATION`, tok: influxql.DURATION},
   122  		{s: `END`, tok: influxql.END},
   123  		{s: `EVERY`, tok: influxql.EVERY},
   124  		{s: `EXPLAIN`, tok: influxql.EXPLAIN},
   125  		{s: `FIELD`, tok: influxql.FIELD},
   126  		{s: `FROM`, tok: influxql.FROM},
   127  		{s: `GRANT`, tok: influxql.GRANT},
   128  		{s: `GROUP`, tok: influxql.GROUP},
   129  		{s: `GROUPS`, tok: influxql.GROUPS},
   130  		{s: `INSERT`, tok: influxql.INSERT},
   131  		{s: `INTO`, tok: influxql.INTO},
   132  		{s: `KEY`, tok: influxql.KEY},
   133  		{s: `KEYS`, tok: influxql.KEYS},
   134  		{s: `KILL`, tok: influxql.KILL},
   135  		{s: `LIMIT`, tok: influxql.LIMIT},
   136  		{s: `SHOW`, tok: influxql.SHOW},
   137  		{s: `SHARD`, tok: influxql.SHARD},
   138  		{s: `SHARDS`, tok: influxql.SHARDS},
   139  		{s: `MEASUREMENT`, tok: influxql.MEASUREMENT},
   140  		{s: `MEASUREMENTS`, tok: influxql.MEASUREMENTS},
   141  		{s: `OFFSET`, tok: influxql.OFFSET},
   142  		{s: `ON`, tok: influxql.ON},
   143  		{s: `ORDER`, tok: influxql.ORDER},
   144  		{s: `PASSWORD`, tok: influxql.PASSWORD},
   145  		{s: `POLICY`, tok: influxql.POLICY},
   146  		{s: `POLICIES`, tok: influxql.POLICIES},
   147  		{s: `PRIVILEGES`, tok: influxql.PRIVILEGES},
   148  		{s: `QUERIES`, tok: influxql.QUERIES},
   149  		{s: `QUERY`, tok: influxql.QUERY},
   150  		{s: `READ`, tok: influxql.READ},
   151  		{s: `REPLICATION`, tok: influxql.REPLICATION},
   152  		{s: `RESAMPLE`, tok: influxql.RESAMPLE},
   153  		{s: `RETENTION`, tok: influxql.RETENTION},
   154  		{s: `REVOKE`, tok: influxql.REVOKE},
   155  		{s: `SELECT`, tok: influxql.SELECT},
   156  		{s: `SERIES`, tok: influxql.SERIES},
   157  		{s: `TAG`, tok: influxql.TAG},
   158  		{s: `TO`, tok: influxql.TO},
   159  		{s: `USER`, tok: influxql.USER},
   160  		{s: `USERS`, tok: influxql.USERS},
   161  		{s: `VALUES`, tok: influxql.VALUES},
   162  		{s: `WHERE`, tok: influxql.WHERE},
   163  		{s: `WITH`, tok: influxql.WITH},
   164  		{s: `WRITE`, tok: influxql.WRITE},
   165  		{s: `explain`, tok: influxql.EXPLAIN}, // case insensitive
   166  		{s: `seLECT`, tok: influxql.SELECT},   // case insensitive
   167  	}
   168  
   169  	for i, tt := range tests {
   170  		s := influxql.NewScanner(strings.NewReader(tt.s))
   171  		tok, pos, lit := s.Scan()
   172  		if tt.tok != tok {
   173  			t.Errorf("%d. %q token mismatch: exp=%q got=%q <%q>", i, tt.s, tt.tok, tok, lit)
   174  		} else if tt.pos.Line != pos.Line || tt.pos.Char != pos.Char {
   175  			t.Errorf("%d. %q pos mismatch: exp=%#v got=%#v", i, tt.s, tt.pos, pos)
   176  		} else if tt.lit != lit {
   177  			t.Errorf("%d. %q literal mismatch: exp=%q got=%q", i, tt.s, tt.lit, lit)
   178  		}
   179  	}
   180  }
   181  
   182  // Ensure the scanner can scan a series of tokens correctly.
   183  func TestScanner_Scan_Multi(t *testing.T) {
   184  	type result struct {
   185  		tok influxql.Token
   186  		pos influxql.Pos
   187  		lit string
   188  	}
   189  	exp := []result{
   190  		{tok: influxql.SELECT, pos: influxql.Pos{Line: 0, Char: 0}, lit: ""},
   191  		{tok: influxql.WS, pos: influxql.Pos{Line: 0, Char: 6}, lit: " "},
   192  		{tok: influxql.IDENT, pos: influxql.Pos{Line: 0, Char: 7}, lit: "value"},
   193  		{tok: influxql.WS, pos: influxql.Pos{Line: 0, Char: 12}, lit: " "},
   194  		{tok: influxql.FROM, pos: influxql.Pos{Line: 0, Char: 13}, lit: ""},
   195  		{tok: influxql.WS, pos: influxql.Pos{Line: 0, Char: 17}, lit: " "},
   196  		{tok: influxql.IDENT, pos: influxql.Pos{Line: 0, Char: 18}, lit: "myseries"},
   197  		{tok: influxql.WS, pos: influxql.Pos{Line: 0, Char: 26}, lit: " "},
   198  		{tok: influxql.WHERE, pos: influxql.Pos{Line: 0, Char: 27}, lit: ""},
   199  		{tok: influxql.WS, pos: influxql.Pos{Line: 0, Char: 32}, lit: " "},
   200  		{tok: influxql.IDENT, pos: influxql.Pos{Line: 0, Char: 33}, lit: "a"},
   201  		{tok: influxql.WS, pos: influxql.Pos{Line: 0, Char: 34}, lit: " "},
   202  		{tok: influxql.EQ, pos: influxql.Pos{Line: 0, Char: 35}, lit: ""},
   203  		{tok: influxql.WS, pos: influxql.Pos{Line: 0, Char: 36}, lit: " "},
   204  		{tok: influxql.STRING, pos: influxql.Pos{Line: 0, Char: 36}, lit: "b"},
   205  		{tok: influxql.EOF, pos: influxql.Pos{Line: 0, Char: 40}, lit: ""},
   206  	}
   207  
   208  	// Create a scanner.
   209  	v := `SELECT value from myseries WHERE a = 'b'`
   210  	s := influxql.NewScanner(strings.NewReader(v))
   211  
   212  	// Continually scan until we reach the end.
   213  	var act []result
   214  	for {
   215  		tok, pos, lit := s.Scan()
   216  		act = append(act, result{tok, pos, lit})
   217  		if tok == influxql.EOF {
   218  			break
   219  		}
   220  	}
   221  
   222  	// Verify the token counts match.
   223  	if len(exp) != len(act) {
   224  		t.Fatalf("token count mismatch: exp=%d, got=%d", len(exp), len(act))
   225  	}
   226  
   227  	// Verify each token matches.
   228  	for i := range exp {
   229  		if !reflect.DeepEqual(exp[i], act[i]) {
   230  			t.Fatalf("%d. token mismatch:\n\nexp=%#v\n\ngot=%#v", i, exp[i], act[i])
   231  		}
   232  	}
   233  }
   234  
   235  // Ensure the library can correctly scan strings.
   236  func TestScanString(t *testing.T) {
   237  	var tests = []struct {
   238  		in  string
   239  		out string
   240  		err string
   241  	}{
   242  		{in: `""`, out: ``},
   243  		{in: `"foo bar"`, out: `foo bar`},
   244  		{in: `'foo bar'`, out: `foo bar`},
   245  		{in: `"foo\nbar"`, out: "foo\nbar"},
   246  		{in: `"foo\\bar"`, out: `foo\bar`},
   247  		{in: `"foo\"bar"`, out: `foo"bar`},
   248  		{in: `'foo\'bar'`, out: `foo'bar`},
   249  
   250  		{in: `"foo` + "\n", out: `foo`, err: "bad string"}, // newline in string
   251  		{in: `"foo`, out: `foo`, err: "bad string"},        // unclosed quotes
   252  		{in: `"foo\xbar"`, out: `\x`, err: "bad escape"},   // invalid escape
   253  	}
   254  
   255  	for i, tt := range tests {
   256  		out, err := influxql.ScanString(strings.NewReader(tt.in))
   257  		if tt.err != errstring(err) {
   258  			t.Errorf("%d. %s: error: exp=%s, got=%s", i, tt.in, tt.err, err)
   259  		} else if tt.out != out {
   260  			t.Errorf("%d. %s: out: exp=%s, got=%s", i, tt.in, tt.out, out)
   261  		}
   262  	}
   263  }
   264  
   265  // Test scanning regex
   266  func TestScanRegex(t *testing.T) {
   267  	var tests = []struct {
   268  		in  string
   269  		tok influxql.Token
   270  		lit string
   271  		err string
   272  	}{
   273  		{in: `/^payments\./`, tok: influxql.REGEX, lit: `^payments\.`},
   274  		{in: `/foo\/bar/`, tok: influxql.REGEX, lit: `foo/bar`},
   275  		{in: `/foo\\/bar/`, tok: influxql.REGEX, lit: `foo\/bar`},
   276  		{in: `/foo\\bar/`, tok: influxql.REGEX, lit: `foo\\bar`},
   277  		{in: `/http\:\/\/www\.example\.com/`, tok: influxql.REGEX, lit: `http\://www\.example\.com`},
   278  	}
   279  
   280  	for i, tt := range tests {
   281  		s := influxql.NewScanner(strings.NewReader(tt.in))
   282  		tok, _, lit := s.ScanRegex()
   283  		if tok != tt.tok {
   284  			t.Errorf("%d. %s: error:\n\texp=%s\n\tgot=%s\n", i, tt.in, tt.tok.String(), tok.String())
   285  		}
   286  		if lit != tt.lit {
   287  			t.Errorf("%d. %s: error:\n\texp=%s\n\tgot=%s\n", i, tt.in, tt.lit, lit)
   288  		}
   289  	}
   290  }