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 }