github.com/minio/minio@v0.0.0-20240328213742-3f72439b8a27/internal/s3select/sql/parser_test.go (about) 1 // Copyright (c) 2015-2021 MinIO, Inc. 2 // 3 // This file is part of MinIO Object Storage stack 4 // 5 // This program is free software: you can redistribute it and/or modify 6 // it under the terms of the GNU Affero General Public License as published by 7 // the Free Software Foundation, either version 3 of the License, or 8 // (at your option) any later version. 9 // 10 // This program is distributed in the hope that it will be useful 11 // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 // GNU Affero General Public License for more details. 14 // 15 // You should have received a copy of the GNU Affero General Public License 16 // along with this program. If not, see <http://www.gnu.org/licenses/>. 17 18 package sql 19 20 import ( 21 "bytes" 22 "testing" 23 24 "github.com/alecthomas/participle" 25 "github.com/alecthomas/participle/lexer" 26 ) 27 28 func TestJSONPathElement(t *testing.T) { 29 p := participle.MustBuild( 30 &JSONPathElement{}, 31 participle.Lexer(sqlLexer), 32 participle.CaseInsensitive("Keyword"), 33 participle.CaseInsensitive("Timeword"), 34 ) 35 36 j := JSONPathElement{} 37 cases := []string{ 38 // Key 39 "['name']", ".name", `."name"`, 40 41 // Index 42 "[2]", "[0]", "[100]", 43 44 // Object wildcard 45 ".*", 46 47 // array wildcard 48 "[*]", 49 } 50 for i, tc := range cases { 51 err := p.ParseString(tc, &j) 52 if err != nil { 53 t.Fatalf("%d: %v", i, err) 54 } 55 // repr.Println(j, repr.Indent(" "), repr.OmitEmpty(true)) 56 } 57 } 58 59 func TestJSONPath(t *testing.T) { 60 p := participle.MustBuild( 61 &JSONPath{}, 62 participle.Lexer(sqlLexer), 63 participle.CaseInsensitive("Keyword"), 64 participle.CaseInsensitive("Timeword"), 65 ) 66 67 j := JSONPath{} 68 cases := []string{ 69 "S3Object", 70 "S3Object.id", 71 "S3Object.book.title", 72 "S3Object.id[1]", 73 "S3Object.id['abc']", 74 "S3Object.id['ab']", 75 "S3Object.words.*.id", 76 "S3Object.words.name[*].val", 77 "S3Object.words.name[*].val[*]", 78 "S3Object.words.name[*].val.*", 79 } 80 for i, tc := range cases { 81 err := p.ParseString(tc, &j) 82 if err != nil { 83 t.Fatalf("%d: %v", i, err) 84 } 85 // repr.Println(j, repr.Indent(" "), repr.OmitEmpty(true)) 86 } 87 } 88 89 func TestIdentifierParsing(t *testing.T) { 90 p := participle.MustBuild( 91 &Identifier{}, 92 participle.Lexer(sqlLexer), 93 participle.CaseInsensitive("Keyword"), 94 ) 95 96 id := Identifier{} 97 validCases := []string{ 98 "a", 99 "_a", 100 "abc_a", 101 "a2", 102 `"abc"`, 103 `"abc\a""ac"`, 104 } 105 for i, tc := range validCases { 106 err := p.ParseString(tc, &id) 107 if err != nil { 108 t.Fatalf("%d: %v", i, err) 109 } 110 // repr.Println(id, repr.Indent(" "), repr.OmitEmpty(true)) 111 } 112 113 invalidCases := []string{ 114 "+a", 115 "-a", 116 "1a", 117 `"ab`, 118 `abc"`, 119 `aa""a`, 120 `"a"a"`, 121 } 122 for i, tc := range invalidCases { 123 err := p.ParseString(tc, &id) 124 if err == nil { 125 t.Fatalf("%d: %v", i, err) 126 } 127 // fmt.Println(tc, err) 128 } 129 } 130 131 func TestLiteralStringParsing(t *testing.T) { 132 var k ObjectKey 133 p := participle.MustBuild( 134 &ObjectKey{}, 135 participle.Lexer(sqlLexer), 136 participle.CaseInsensitive("Keyword"), 137 ) 138 139 validCases := []string{ 140 "['abc']", 141 "['ab''c']", 142 "['a''b''c']", 143 "['abc-x_1##@(*&(#*))/\\']", 144 } 145 for i, tc := range validCases { 146 err := p.ParseString(tc, &k) 147 if err != nil { 148 t.Fatalf("%d: %v", i, err) 149 } 150 if string(*k.Lit) == "" { 151 t.Fatalf("Incorrect parse %#v", k) 152 } 153 // repr.Println(k, repr.Indent(" "), repr.OmitEmpty(true)) 154 } 155 156 invalidCases := []string{ 157 "['abc'']", 158 "['-abc'sc']", 159 "[abc']", 160 "['ac]", 161 } 162 for i, tc := range invalidCases { 163 err := p.ParseString(tc, &k) 164 if err == nil { 165 t.Fatalf("%d: %v", i, err) 166 } 167 // fmt.Println(tc, err) 168 } 169 } 170 171 func TestFunctionParsing(t *testing.T) { 172 var fex FuncExpr 173 p := participle.MustBuild( 174 &FuncExpr{}, 175 participle.Lexer(sqlLexer), 176 participle.CaseInsensitive("Keyword"), 177 participle.CaseInsensitive("Timeword"), 178 ) 179 180 validCases := []string{ 181 "count(*)", 182 "sum(2 + s.id)", 183 "sum(t)", 184 "avg(s.id[1])", 185 "coalesce(s.id[1], 2, 2 + 3)", 186 187 "cast(s as string)", 188 "cast(s AS INT)", 189 "cast(s as DECIMAL)", 190 "extract(YEAR from '2018-01-09')", 191 "extract(month from '2018-01-09')", 192 193 "extract(hour from '2018-01-09')", 194 "extract(day from '2018-01-09')", 195 "substring('abcd' from 2 for 2)", 196 "substring('abcd' from 2)", 197 "substring('abcd' , 2 , 2)", 198 199 "substring('abcd' , 22 )", 200 "trim(' aab ')", 201 "trim(leading from ' aab ')", 202 "trim(trailing from ' aab ')", 203 "trim(both from ' aab ')", 204 205 "trim(both '12' from ' aab ')", 206 "trim(leading '12' from ' aab ')", 207 "trim(trailing '12' from ' aab ')", 208 "count(23)", 209 } 210 for i, tc := range validCases { 211 err := p.ParseString(tc, &fex) 212 if err != nil { 213 t.Fatalf("%d: %v", i, err) 214 } 215 // repr.Println(fex, repr.Indent(" "), repr.OmitEmpty(true)) 216 } 217 } 218 219 func TestSqlLexer(t *testing.T) { 220 // s := bytes.NewBuffer([]byte("s.['name'].*.[*].abc.[\"abc\"]")) 221 s := bytes.NewBuffer([]byte("S3Object.words.*.id")) 222 // s := bytes.NewBuffer([]byte("COUNT(Id)")) 223 lex, err := sqlLexer.Lex(s) 224 if err != nil { 225 t.Fatal(err) 226 } 227 tokens, err := lexer.ConsumeAll(lex) 228 if err != nil { 229 t.Fatal(err) 230 } 231 // for i, t := range tokens { 232 // fmt.Printf("%d: %#v\n", i, t) 233 // } 234 if len(tokens) != 7 { 235 t.Fatalf("Expected 7 got %d", len(tokens)) 236 } 237 } 238 239 func TestSelectWhere(t *testing.T) { 240 p := participle.MustBuild( 241 &Select{}, 242 participle.Lexer(sqlLexer), 243 participle.CaseInsensitive("Keyword"), 244 ) 245 246 s := Select{} 247 cases := []string{ 248 "select * from s3object", 249 "select a, b from s3object s", 250 "select a, b from s3object as s", 251 "select a, b from s3object as s where a = 1", 252 "select a, b from s3object s where a = 1", 253 "select a, b from s3object where a = 1", 254 } 255 for i, tc := range cases { 256 err := p.ParseString(tc, &s) 257 if err != nil { 258 t.Fatalf("%d: %v", i, err) 259 } 260 261 // repr.Println(s, repr.Indent(" "), repr.OmitEmpty(true)) 262 } 263 } 264 265 func TestLikeClause(t *testing.T) { 266 p := participle.MustBuild( 267 &Select{}, 268 participle.Lexer(sqlLexer), 269 participle.CaseInsensitive("Keyword"), 270 ) 271 272 s := Select{} 273 cases := []string{ 274 `select * from s3object where Name like 'abcd'`, 275 `select Name like 'abc' from s3object`, 276 `select * from s3object where Name not like 'abc'`, 277 `select * from s3object where Name like 'abc' escape 't'`, 278 `select * from s3object where Name like 'a\%' escape '?'`, 279 `select * from s3object where Name not like 'abc\' escape '?'`, 280 `select * from s3object where Name like 'a\%' escape LOWER('?')`, 281 `select * from s3object where Name not like LOWER('Bc\') escape '?'`, 282 } 283 for i, tc := range cases { 284 err := p.ParseString(tc, &s) 285 if err != nil { 286 t.Errorf("%d: %v", i, err) 287 } 288 } 289 } 290 291 func TestBetweenClause(t *testing.T) { 292 p := participle.MustBuild( 293 &Select{}, 294 participle.Lexer(sqlLexer), 295 participle.CaseInsensitive("Keyword"), 296 ) 297 298 s := Select{} 299 cases := []string{ 300 `select * from s3object where Id between 1 and 2`, 301 `select * from s3object where Id between 1 and 2 and name = 'Ab'`, 302 `select * from s3object where Id not between 1 and 2`, 303 `select * from s3object where Id not between 1 and 2 and name = 'Bc'`, 304 } 305 for i, tc := range cases { 306 err := p.ParseString(tc, &s) 307 if err != nil { 308 t.Errorf("%d: %v", i, err) 309 } 310 } 311 } 312 313 func TestFromClauseJSONPath(t *testing.T) { 314 p := participle.MustBuild( 315 &Select{}, 316 participle.Lexer(sqlLexer), 317 participle.CaseInsensitive("Keyword"), 318 ) 319 320 s := Select{} 321 cases := []string{ 322 "select * from s3object", 323 "select * from s3object[*].name", 324 "select * from s3object[*].books[*]", 325 "select * from s3object[*].books[*].name", 326 "select * from s3object where name > 2", 327 "select * from s3object[*].name where name > 2", 328 "select * from s3object[*].books[*] where name > 2", 329 "select * from s3object[*].books[*].name where name > 2", 330 "select * from s3object[*].books[*] s", 331 "select * from s3object[*].books[*].name as s", 332 "select * from s3object s where name > 2", 333 "select * from s3object[*].name as s where name > 2", 334 "select * from s3object[*].books[*] limit 1", 335 } 336 for i, tc := range cases { 337 err := p.ParseString(tc, &s) 338 if err != nil { 339 t.Fatalf("%d: %v", i, err) 340 } 341 342 // repr.Println(s, repr.Indent(" "), repr.OmitEmpty(true)) 343 } 344 } 345 346 func TestSelectParsing(t *testing.T) { 347 p := participle.MustBuild( 348 &Select{}, 349 participle.Lexer(sqlLexer), 350 participle.CaseInsensitive("Keyword"), 351 ) 352 353 s := Select{} 354 cases := []string{ 355 "select * from s3object where name > 2 or value > 1 or word > 2", 356 "select s.word.id + 2 from s3object s", 357 "select 1-2-3 from s3object s limit 1", 358 } 359 for i, tc := range cases { 360 err := p.ParseString(tc, &s) 361 if err != nil { 362 t.Fatalf("%d: %v", i, err) 363 } 364 365 // repr.Println(s, repr.Indent(" "), repr.OmitEmpty(true)) 366 } 367 } 368 369 func TestSqlLexerArithOps(t *testing.T) { 370 s := bytes.NewBuffer([]byte("year from select month hour distinct")) 371 lex, err := sqlLexer.Lex(s) 372 if err != nil { 373 t.Fatal(err) 374 } 375 tokens, err := lexer.ConsumeAll(lex) 376 if err != nil { 377 t.Fatal(err) 378 } 379 if len(tokens) != 7 { 380 t.Errorf("Expected 7 got %d", len(tokens)) 381 } 382 // for i, t := range tokens { 383 // fmt.Printf("%d: %#v\n", i, t) 384 // } 385 } 386 387 func TestParseSelectStatement(t *testing.T) { 388 exp, err := ParseSelectStatement("select _3,_1,_2 as 'mytest' from S3object") 389 if err != nil { 390 t.Fatalf("parse alias sql error: %v", err) 391 } 392 if exp.selectAST.Expression.Expressions[2].As != "mytest" { 393 t.Fatalf("parse alias sql error: %s not equal %s", exp.selectAST.Expression.Expressions[2].As, err) 394 } 395 }