storj.io/minio@v0.0.0-20230509071714-0cbc90f649b1/pkg/s3select/select_test.go (about) 1 /* 2 * MinIO Cloud Storage, (C) 2019 MinIO, Inc. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package s3select 18 19 import ( 20 "bytes" 21 "encoding/xml" 22 "fmt" 23 "io" 24 "io/ioutil" 25 "net/http" 26 "os" 27 "reflect" 28 "strings" 29 "testing" 30 31 "github.com/klauspost/cpuid/v2" 32 "github.com/minio/minio-go/v7" 33 "github.com/minio/simdjson-go" 34 ) 35 36 type testResponseWriter struct { 37 statusCode int 38 response []byte 39 } 40 41 func (w *testResponseWriter) Header() http.Header { 42 return nil 43 } 44 45 func (w *testResponseWriter) Write(p []byte) (int, error) { 46 w.response = append(w.response, p...) 47 return len(p), nil 48 } 49 50 func (w *testResponseWriter) WriteHeader(statusCode int) { 51 w.statusCode = statusCode 52 } 53 54 func (w *testResponseWriter) Flush() { 55 } 56 57 func TestJSONQueries(t *testing.T) { 58 input := `{"id": 0,"title": "Test Record","desc": "Some text","synonyms": ["foo", "bar", "whatever"]} 59 {"id": 1,"title": "Second Record","desc": "another text","synonyms": ["some", "synonym", "value"]} 60 {"id": 2,"title": "Second Record","desc": "another text","numbers": [2, 3.0, 4]} 61 {"id": 3,"title": "Second Record","desc": "another text","nested": [[2, 3.0, 4], [7, 8.5, 9]]}` 62 63 var testTable = []struct { 64 name string 65 query string 66 requestXML []byte // override request XML 67 wantResult string 68 withJSON string // Override JSON input 69 }{ 70 { 71 name: "select-in-array-full", 72 query: `SELECT * from s3object s WHERE 'bar' IN s.synonyms[*]`, 73 wantResult: `{"id":0,"title":"Test Record","desc":"Some text","synonyms":["foo","bar","whatever"]}`, 74 }, 75 { 76 name: "simple-in-array", 77 query: `SELECT * from s3object s WHERE s.id IN (1,3)`, 78 wantResult: `{"id":1,"title":"Second Record","desc":"another text","synonyms":["some","synonym","value"]} 79 {"id":3,"title":"Second Record","desc":"another text","nested":[[2,3,4],[7,8.5,9]]}`, 80 }, 81 { 82 name: "select-in-array-single", 83 query: `SELECT synonyms from s3object s WHERE 'bar' IN s.synonyms[*] `, 84 wantResult: `{"synonyms":["foo","bar","whatever"]}`, 85 }, 86 { 87 name: "donatello-1", 88 query: `SELECT * from s3object s WHERE 'bar' in s.synonyms`, 89 wantResult: `{"id":0,"title":"Test Record","desc":"Some text","synonyms":["foo","bar","whatever"]}`, 90 }, 91 { 92 name: "donatello-2", 93 query: `SELECT * from s3object s WHERE 'bar' in s.synonyms[*]`, 94 wantResult: `{"id":0,"title":"Test Record","desc":"Some text","synonyms":["foo","bar","whatever"]}`, 95 }, 96 { 97 name: "bignum-1", 98 query: `SELECT id from s3object s WHERE s.id <= 9223372036854775807`, 99 wantResult: `{"id":0} 100 {"id":1} 101 {"id":2} 102 {"id":3}`}, 103 { 104 name: "bignum-2", 105 query: `SELECT id from s3object s WHERE s.id >= -9223372036854775808`, 106 wantResult: `{"id":0} 107 {"id":1} 108 {"id":2} 109 {"id":3}`}, 110 { 111 name: "donatello-3", 112 query: `SELECT * from s3object s WHERE 'value' IN s.synonyms[*]`, 113 wantResult: `{"id":1,"title":"Second Record","desc":"another text","synonyms":["some","synonym","value"]}`, 114 }, 115 { 116 name: "select-in-number", 117 query: `SELECT * from s3object s WHERE 4 in s.numbers[*]`, 118 wantResult: `{"id":2,"title":"Second Record","desc":"another text","numbers":[2,3,4]}`, 119 }, 120 { 121 name: "select-in-number-float", 122 query: `SELECT * from s3object s WHERE 3 in s.numbers[*]`, 123 wantResult: `{"id":2,"title":"Second Record","desc":"another text","numbers":[2,3,4]}`, 124 }, 125 { 126 name: "select-in-number-float-in-sql", 127 query: `SELECT * from s3object s WHERE 3.0 in s.numbers[*]`, 128 wantResult: `{"id":2,"title":"Second Record","desc":"another text","numbers":[2,3,4]}`, 129 }, 130 { 131 name: "select-in-list-match", 132 query: `SELECT * from s3object s WHERE (2,3,4) IN s.nested[*]`, 133 wantResult: `{"id":3,"title":"Second Record","desc":"another text","nested":[[2,3,4],[7,8.5,9]]}`, 134 }, 135 { 136 name: "select-in-nested-float", 137 query: `SELECT s.nested from s3object s WHERE 8.5 IN s.nested[*][*]`, 138 wantResult: `{"nested":[[2,3,4],[7,8.5,9]]}`, 139 }, 140 { 141 name: "select-in-combine-and", 142 query: `SELECT s.nested from s3object s WHERE (8.5 IN s.nested[*][*]) AND (s.id > 0)`, 143 wantResult: `{"nested":[[2,3,4],[7,8.5,9]]}`, 144 }, 145 { 146 name: "select-in-combine-and-no", 147 query: `SELECT s.nested from s3object s WHERE (8.5 IN s.nested[*][*]) AND (s.id = 0)`, 148 wantResult: ``, 149 }, 150 { 151 name: "select-in-nested-float-no-flat", 152 query: `SELECT s.nested from s3object s WHERE 8.5 IN s.nested[*]`, 153 wantResult: ``, 154 }, 155 { 156 name: "select-empty-field-result", 157 query: `SELECT * from s3object s WHERE s.nested[0][0] = 2`, 158 wantResult: `{"id":3,"title":"Second Record","desc":"another text","nested":[[2,3,4],[7,8.5,9]]}`, 159 }, 160 { 161 name: "select-arrays-specific", 162 query: `SELECT * from s3object s WHERE s.nested[1][0] = 7`, 163 wantResult: `{"id":3,"title":"Second Record","desc":"another text","nested":[[2,3,4],[7,8.5,9]]}`, 164 }, 165 { 166 name: "wrong-index-no-result", 167 query: `SELECT * from s3object s WHERE s.nested[0][0] = 7`, 168 wantResult: ``, 169 }, 170 { 171 name: "not-equal-result", 172 query: `SELECT * from s3object s WHERE s.nested[1][0] != 7`, 173 wantResult: `{"id":0,"title":"Test Record","desc":"Some text","synonyms":["foo","bar","whatever"]} 174 {"id":1,"title":"Second Record","desc":"another text","synonyms":["some","synonym","value"]} 175 {"id":2,"title":"Second Record","desc":"another text","numbers":[2,3,4]}`, 176 }, 177 { 178 name: "indexed-list-match", 179 query: `SELECT * from s3object s WHERE (7,8.5,9) IN s.nested[1]`, 180 wantResult: ``, 181 }, 182 { 183 name: "indexed-list-match-equals", 184 query: `SELECT * from s3object s WHERE (7,8.5,9) = s.nested[1]`, 185 wantResult: `{"id":3,"title":"Second Record","desc":"another text","nested":[[2,3,4],[7,8.5,9]]}`, 186 }, 187 { 188 name: "indexed-list-match-equals-s-star", 189 query: `SELECT s.* from s3object s WHERE (7,8.5,9) = s.nested[1]`, 190 wantResult: `{"id":3,"title":"Second Record","desc":"another text","nested":[[2,3,4],[7,8.5,9]]}`, 191 }, 192 { 193 name: "indexed-list-match-equals-s-index", 194 query: `SELECT s.nested[1], s.nested[0] from s3object s WHERE (7,8.5,9) = s.nested[1]`, 195 wantResult: `{"_1":[7,8.5,9],"_2":[2,3,4]}`, 196 }, 197 { 198 name: "indexed-list-match-not-equals", 199 query: `SELECT * from s3object s WHERE (7,8.5,9) != s.nested[1]`, 200 wantResult: `{"id":0,"title":"Test Record","desc":"Some text","synonyms":["foo","bar","whatever"]} 201 {"id":1,"title":"Second Record","desc":"another text","synonyms":["some","synonym","value"]} 202 {"id":2,"title":"Second Record","desc":"another text","numbers":[2,3,4]}`, 203 }, 204 { 205 name: "indexed-list-square-bracket", 206 query: `SELECT * from s3object s WHERE [7,8.5,9] = s.nested[1]`, 207 wantResult: `{"id":3,"title":"Second Record","desc":"another text","nested":[[2,3,4],[7,8.5,9]]}`, 208 }, 209 { 210 name: "indexed-list-square-bracket", 211 query: `SELECT * from s3object s WHERE [7,8.5,9] IN s.nested`, 212 wantResult: `{"id":3,"title":"Second Record","desc":"another text","nested":[[2,3,4],[7,8.5,9]]}`, 213 }, 214 { 215 name: "indexed-list-square-bracket", 216 query: `SELECT * from s3object s WHERE id IN [3,2]`, 217 wantResult: `{"id":2,"title":"Second Record","desc":"another text","numbers":[2,3,4]} 218 {"id":3,"title":"Second Record","desc":"another text","nested":[[2,3,4],[7,8.5,9]]}`, 219 }, 220 { 221 name: "index-wildcard-in", 222 query: `SELECT * from s3object s WHERE (8.5) IN s.nested[1][*]`, 223 wantResult: `{"id":3,"title":"Second Record","desc":"another text","nested":[[2,3,4],[7,8.5,9]]}`, 224 }, 225 { 226 name: "index-wildcard-in", 227 query: `SELECT * from s3object s WHERE (8.0+0.5) IN s.nested[1][*]`, 228 wantResult: `{"id":3,"title":"Second Record","desc":"another text","nested":[[2,3,4],[7,8.5,9]]}`, 229 }, 230 { 231 name: "compare-mixed", 232 query: `SELECT id from s3object s WHERE value = true`, 233 wantResult: `{"id":1}`, 234 withJSON: `{"id":0, "value": false} 235 {"id":1, "value": true} 236 {"id":2, "value": 42} 237 {"id":3, "value": "true"} 238 `, 239 }, 240 { 241 name: "compare-mixed-not", 242 query: `SELECT COUNT(id) as n from s3object s WHERE value != true`, 243 wantResult: `{"n":3}`, 244 withJSON: `{"id":0, "value": false} 245 {"id":1, "value": true} 246 {"id":2, "value": 42} 247 {"id":3, "value": "true"} 248 `, 249 }, 250 { 251 name: "index-wildcard-in", 252 query: `SELECT * from s3object s WHERE title = 'Test Record'`, 253 wantResult: `{"id":0,"title":"Test Record","desc":"Some text","synonyms":["foo","bar","whatever"]}`, 254 }, 255 { 256 name: "select-output-field-as-csv", 257 requestXML: []byte(`<?xml version="1.0" encoding="UTF-8"?> 258 <SelectObjectContentRequest> 259 <Expression>SELECT s.synonyms from s3object s WHERE 'whatever' IN s.synonyms</Expression> 260 <ExpressionType>SQL</ExpressionType> 261 <InputSerialization> 262 <CompressionType>NONE</CompressionType> 263 <JSON> 264 <Type>DOCUMENT</Type> 265 </JSON> 266 </InputSerialization> 267 <OutputSerialization> 268 <CSV> 269 <QuoteCharacter>"</QuoteCharacter> 270 </CSV> 271 </OutputSerialization> 272 <RequestProgress> 273 <Enabled>FALSE</Enabled> 274 </RequestProgress> 275 </SelectObjectContentRequest>`), 276 wantResult: `"[""foo"",""bar"",""whatever""]"`, 277 }, 278 { 279 name: "document", 280 query: "", 281 requestXML: []byte(` 282 <?xml version="1.0" encoding="UTF-8"?> 283 <SelectObjectContentRequest> 284 <Expression>select * from s3object[*].elements[*] s where s.element_type = '__elem__merfu'</Expression> 285 <ExpressionType>SQL</ExpressionType> 286 <InputSerialization> 287 <CompressionType>NONE</CompressionType> 288 <JSON> 289 <Type>DOCUMENT</Type> 290 </JSON> 291 </InputSerialization> 292 <OutputSerialization> 293 <JSON> 294 </JSON> 295 </OutputSerialization> 296 <RequestProgress> 297 <Enabled>FALSE</Enabled> 298 </RequestProgress> 299 </SelectObjectContentRequest>`), 300 withJSON: ` 301 { 302 "name": "small_pdf1.pdf", 303 "lume_id": "9507193e-572d-4f95-bcf1-e9226d96be65", 304 "elements": [ 305 { 306 "element_type": "__elem__image", 307 "element_id": "859d09c4-7cf1-4a37-9674-3a7de8b56abc", 308 "attributes": { 309 "__attr__image_dpi": 300, 310 "__attr__image_size": [ 311 2550, 312 3299 313 ], 314 "__attr__image_index": 1, 315 "__attr__image_format": "JPEG", 316 "__attr__file_extension": "jpg", 317 "__attr__data": null 318 } 319 }, 320 { 321 "element_type": "__elem__merfu", 322 "element_id": "d868aefe-ef9a-4be2-b9b2-c9fd89cc43eb", 323 "attributes": { 324 "__attr__image_dpi": 300, 325 "__attr__image_size": [ 326 2550, 327 3299 328 ], 329 "__attr__image_index": 2, 330 "__attr__image_format": "JPEG", 331 "__attr__file_extension": "jpg", 332 "__attr__data": null 333 } 334 } 335 ], 336 "data": "asdascasdc1234e123erdasdas" 337 }`, 338 wantResult: `{"element_type":"__elem__merfu","element_id":"d868aefe-ef9a-4be2-b9b2-c9fd89cc43eb","attributes":{"__attr__image_dpi":300,"__attr__image_size":[2550,3299],"__attr__image_index":2,"__attr__image_format":"JPEG","__attr__file_extension":"jpg","__attr__data":null}}`, 339 }, 340 { 341 name: "date_diff_month", 342 query: `SELECT date_diff(MONTH, '2019-10-20T', '2020-01-20T') FROM S3Object LIMIT 1`, 343 wantResult: `{"_1":3}`, 344 }, 345 { 346 name: "date_diff_month_neg", 347 query: `SELECT date_diff(MONTH, '2020-01-20T', '2019-10-20T') FROM S3Object LIMIT 1`, 348 wantResult: `{"_1":-3}`, 349 }, 350 // Examples from https://docs.aws.amazon.com/AmazonS3/latest/userguide/s3-glacier-select-sql-reference-date.html#s3-glacier-select-sql-reference-date-diff 351 { 352 name: "date_diff_year", 353 query: `SELECT date_diff(year, '2010-01-01T', '2011-01-01T') FROM S3Object LIMIT 1`, 354 wantResult: `{"_1":1}`, 355 }, 356 { 357 name: "date_diff_year", 358 query: `SELECT date_diff(month, '2010-01-01T', '2010-05T') FROM S3Object LIMIT 1`, 359 wantResult: `{"_1":4}`, 360 }, 361 { 362 name: "date_diff_month_oney", 363 query: `SELECT date_diff(month, '2010T', '2011T') FROM S3Object LIMIT 1`, 364 wantResult: `{"_1":12}`, 365 }, 366 { 367 name: "date_diff_month_neg", 368 query: `SELECT date_diff(month, '2011T', '2010T') FROM S3Object LIMIT 1`, 369 wantResult: `{"_1":-12}`, 370 }, 371 { 372 name: "date_diff_days", 373 query: `SELECT date_diff(day, '2010-01-01T23:00:00Z', '2010-01-02T01:00:00Z') FROM S3Object LIMIT 1`, 374 wantResult: `{"_1":0}`, 375 }, 376 { 377 name: "date_diff_days_one", 378 query: `SELECT date_diff(day, '2010-01-01T23:00:00Z', '2010-01-02T23:00:00Z') FROM S3Object LIMIT 1`, 379 wantResult: `{"_1":1}`, 380 }, 381 { 382 name: "cast_from_int_to_float", 383 query: `SELECT cast(1 as float) FROM S3Object LIMIT 1`, 384 wantResult: `{"_1":1}`, 385 }, 386 { 387 name: "cast_from_float_to_float", 388 query: `SELECT cast(1.0 as float) FROM S3Object LIMIT 1`, 389 wantResult: `{"_1":1}`, 390 }, 391 { 392 name: "arithmetic_integer_operand", 393 query: `SELECT 1 / 2 FROM S3Object LIMIT 1`, 394 wantResult: `{"_1":0}`, 395 }, 396 { 397 name: "arithmetic_float_operand", 398 query: `SELECT 1.0 / 2.0 * .3 FROM S3Object LIMIT 1`, 399 wantResult: `{"_1":0.15}`, 400 }, 401 { 402 name: "arithmetic_integer_float_operand", 403 query: `SELECT 3.0 / 2, 5 / 2.0 FROM S3Object LIMIT 1`, 404 wantResult: `{"_1":1.5,"_2":2.5}`, 405 }, 406 } 407 408 defRequest := `<?xml version="1.0" encoding="UTF-8"?> 409 <SelectObjectContentRequest> 410 <Expression>%s</Expression> 411 <ExpressionType>SQL</ExpressionType> 412 <InputSerialization> 413 <CompressionType>NONE</CompressionType> 414 <JSON> 415 <Type>LINES</Type> 416 </JSON> 417 </InputSerialization> 418 <OutputSerialization> 419 <JSON> 420 </JSON> 421 </OutputSerialization> 422 <RequestProgress> 423 <Enabled>FALSE</Enabled> 424 </RequestProgress> 425 </SelectObjectContentRequest>` 426 427 for _, testCase := range testTable { 428 t.Run(testCase.name, func(t *testing.T) { 429 // Hack cpuid to the CPU doesn't appear to support AVX2. 430 // Restore whatever happens. 431 if cpuid.CPU.Supports(cpuid.AVX2) { 432 cpuid.CPU.Disable(cpuid.AVX2) 433 defer cpuid.CPU.Enable(cpuid.AVX2) 434 } 435 if simdjson.SupportedCPU() { 436 t.Fatal("setup error: expected cpu to be unsupported") 437 } 438 testReq := testCase.requestXML 439 if len(testReq) == 0 { 440 var escaped bytes.Buffer 441 xml.EscapeText(&escaped, []byte(testCase.query)) 442 testReq = []byte(fmt.Sprintf(defRequest, escaped.String())) 443 } 444 s3Select, err := NewS3Select(bytes.NewReader(testReq)) 445 if err != nil { 446 t.Fatal(err) 447 } 448 449 if err = s3Select.Open(func(offset, length int64) (io.ReadCloser, error) { 450 in := input 451 if len(testCase.withJSON) > 0 { 452 in = testCase.withJSON 453 } 454 return ioutil.NopCloser(bytes.NewBufferString(in)), nil 455 }); err != nil { 456 t.Fatal(err) 457 } 458 459 w := &testResponseWriter{} 460 s3Select.Evaluate(w) 461 s3Select.Close() 462 resp := http.Response{ 463 StatusCode: http.StatusOK, 464 Body: ioutil.NopCloser(bytes.NewReader(w.response)), 465 ContentLength: int64(len(w.response)), 466 } 467 res, err := minio.NewSelectResults(&resp, "testbucket") 468 if err != nil { 469 t.Error(err) 470 return 471 } 472 got, err := ioutil.ReadAll(res) 473 if err != nil { 474 t.Error(err) 475 return 476 } 477 gotS := strings.TrimSpace(string(got)) 478 if !reflect.DeepEqual(gotS, testCase.wantResult) { 479 t.Errorf("received response does not match with expected reply. Query: %s\ngot: %s\nwant:%s", testCase.query, gotS, testCase.wantResult) 480 } 481 }) 482 t.Run("simd-"+testCase.name, func(t *testing.T) { 483 if !simdjson.SupportedCPU() { 484 t.Skip("No CPU support") 485 } 486 testReq := testCase.requestXML 487 if len(testReq) == 0 { 488 var escaped bytes.Buffer 489 xml.EscapeText(&escaped, []byte(testCase.query)) 490 testReq = []byte(fmt.Sprintf(defRequest, escaped.String())) 491 } 492 s3Select, err := NewS3Select(bytes.NewReader(testReq)) 493 if err != nil { 494 t.Fatal(err) 495 } 496 497 if err = s3Select.Open(func(offset, length int64) (io.ReadCloser, error) { 498 in := input 499 if len(testCase.withJSON) > 0 { 500 in = testCase.withJSON 501 } 502 return ioutil.NopCloser(bytes.NewBufferString(in)), nil 503 }); err != nil { 504 t.Fatal(err) 505 } 506 507 w := &testResponseWriter{} 508 s3Select.Evaluate(w) 509 s3Select.Close() 510 resp := http.Response{ 511 StatusCode: http.StatusOK, 512 Body: ioutil.NopCloser(bytes.NewReader(w.response)), 513 ContentLength: int64(len(w.response)), 514 } 515 res, err := minio.NewSelectResults(&resp, "testbucket") 516 if err != nil { 517 t.Error(err) 518 return 519 } 520 got, err := ioutil.ReadAll(res) 521 if err != nil { 522 t.Error(err) 523 return 524 } 525 gotS := strings.TrimSpace(string(got)) 526 if !reflect.DeepEqual(gotS, testCase.wantResult) { 527 t.Errorf("received response does not match with expected reply. Query: %s\ngot: %s\nwant:%s", testCase.query, gotS, testCase.wantResult) 528 } 529 }) 530 } 531 } 532 533 func TestCSVQueries(t *testing.T) { 534 input := `index,ID,CaseNumber,Date,Day,Month,Year,Block,IUCR,PrimaryType,Description,LocationDescription,Arrest,Domestic,Beat,District,Ward,CommunityArea,FBI Code,XCoordinate,YCoordinate,UpdatedOn,Latitude,Longitude,Location 535 2700763,7732229,,2010-05-26 00:00:00,26,May,2010,113XX S HALSTED ST,1150,,CREDIT CARD FRAUD,,False,False,2233,22.0,34.0,,11,,,,41.688043288,-87.6422444,"(41.688043288, -87.6422444)"` 536 537 var testTable = []struct { 538 name string 539 query string 540 requestXML []byte 541 wantResult string 542 }{ 543 { 544 name: "select-in-text-simple", 545 query: `SELECT index FROM s3Object s WHERE "Month"='May'`, 546 wantResult: `2700763`, 547 }, 548 } 549 550 defRequest := `<?xml version="1.0" encoding="UTF-8"?> 551 <SelectObjectContentRequest> 552 <Expression>%s</Expression> 553 <ExpressionType>SQL</ExpressionType> 554 <InputSerialization> 555 <CompressionType>NONE</CompressionType> 556 <CSV> 557 <FieldDelimiter>,</FieldDelimiter> 558 <FileHeaderInfo>USE</FileHeaderInfo> 559 <QuoteCharacter>"</QuoteCharacter> 560 <QuoteEscapeCharacter>"</QuoteEscapeCharacter> 561 <RecordDelimiter>\n</RecordDelimiter> 562 </CSV> 563 </InputSerialization> 564 <OutputSerialization> 565 <CSV> 566 </CSV> 567 </OutputSerialization> 568 <RequestProgress> 569 <Enabled>FALSE</Enabled> 570 </RequestProgress> 571 </SelectObjectContentRequest>` 572 573 for _, testCase := range testTable { 574 t.Run(testCase.name, func(t *testing.T) { 575 testReq := testCase.requestXML 576 if len(testReq) == 0 { 577 testReq = []byte(fmt.Sprintf(defRequest, testCase.query)) 578 } 579 s3Select, err := NewS3Select(bytes.NewReader(testReq)) 580 if err != nil { 581 t.Fatal(err) 582 } 583 584 if err = s3Select.Open(func(offset, length int64) (io.ReadCloser, error) { 585 return ioutil.NopCloser(bytes.NewBufferString(input)), nil 586 }); err != nil { 587 t.Fatal(err) 588 } 589 590 w := &testResponseWriter{} 591 s3Select.Evaluate(w) 592 s3Select.Close() 593 resp := http.Response{ 594 StatusCode: http.StatusOK, 595 Body: ioutil.NopCloser(bytes.NewReader(w.response)), 596 ContentLength: int64(len(w.response)), 597 } 598 res, err := minio.NewSelectResults(&resp, "testbucket") 599 if err != nil { 600 t.Error(err) 601 return 602 } 603 got, err := ioutil.ReadAll(res) 604 if err != nil { 605 t.Error(err) 606 return 607 } 608 gotS := strings.TrimSpace(string(got)) 609 if !reflect.DeepEqual(gotS, testCase.wantResult) { 610 t.Errorf("received response does not match with expected reply. Query: %s\ngot: %s\nwant:%s", testCase.query, gotS, testCase.wantResult) 611 } 612 }) 613 } 614 } 615 616 func TestCSVQueries2(t *testing.T) { 617 input := `id,time,num,num2,text 618 1,2010-01-01T,7867786,4565.908123,"a text, with comma" 619 2,2017-01-02T03:04Z,-5, 0.765111, 620 ` 621 var testTable = []struct { 622 name string 623 query string 624 requestXML []byte // override request XML 625 wantResult string 626 }{ 627 { 628 name: "select-all", 629 query: `SELECT * from s3object AS s WHERE id = '1'`, 630 wantResult: `{"id":"1","time":"2010-01-01T","num":"7867786","num2":"4565.908123","text":"a text, with comma"}`, 631 }, 632 { 633 name: "select-all-2", 634 query: `SELECT * from s3object s WHERE id = 2`, 635 wantResult: `{"id":"2","time":"2017-01-02T03:04Z","num":"-5","num2":" 0.765111","text":""}`, 636 }, 637 { 638 name: "select-text-convert", 639 query: `SELECT CAST(text AS STRING) AS text from s3object s WHERE id = 1`, 640 wantResult: `{"text":"a text, with comma"}`, 641 }, 642 { 643 name: "select-text-direct", 644 query: `SELECT text from s3object s WHERE id = 1`, 645 wantResult: `{"text":"a text, with comma"}`, 646 }, 647 { 648 name: "select-time-direct", 649 query: `SELECT time from s3object s WHERE id = 2`, 650 wantResult: `{"time":"2017-01-02T03:04Z"}`, 651 }, 652 { 653 name: "select-int-direct", 654 query: `SELECT num from s3object s WHERE id = 2`, 655 wantResult: `{"num":"-5"}`, 656 }, 657 { 658 name: "select-float-direct", 659 query: `SELECT num2 from s3object s WHERE id = 2`, 660 wantResult: `{"num2":" 0.765111"}`, 661 }, 662 { 663 name: "select-in-array", 664 query: `select id from S3Object s WHERE id in [1,3]`, 665 wantResult: `{"id":"1"}`, 666 }, 667 { 668 name: "select-in-array-matchnone", 669 query: `select id from S3Object s WHERE s.id in [4,3]`, 670 wantResult: ``, 671 }, 672 { 673 name: "select-float-by-val", 674 query: `SELECT num2 from s3object s WHERE num2 = 0.765111`, 675 wantResult: `{"num2":" 0.765111"}`, 676 }, 677 } 678 679 defRequest := `<?xml version="1.0" encoding="UTF-8"?> 680 <SelectObjectContentRequest> 681 <Expression>%s</Expression> 682 <ExpressionType>SQL</ExpressionType> 683 <InputSerialization> 684 <CompressionType>NONE</CompressionType> 685 <CSV> 686 <FileHeaderInfo>USE</FileHeaderInfo> 687 <QuoteCharacter>"</QuoteCharacter> 688 </CSV> 689 </InputSerialization> 690 <OutputSerialization> 691 <JSON> 692 </JSON> 693 </OutputSerialization> 694 <RequestProgress> 695 <Enabled>FALSE</Enabled> 696 </RequestProgress> 697 </SelectObjectContentRequest>` 698 699 for _, testCase := range testTable { 700 t.Run(testCase.name, func(t *testing.T) { 701 testReq := testCase.requestXML 702 if len(testReq) == 0 { 703 testReq = []byte(fmt.Sprintf(defRequest, testCase.query)) 704 } 705 s3Select, err := NewS3Select(bytes.NewReader(testReq)) 706 if err != nil { 707 t.Fatal(err) 708 } 709 710 if err = s3Select.Open(func(offset, length int64) (io.ReadCloser, error) { 711 return ioutil.NopCloser(bytes.NewBufferString(input)), nil 712 }); err != nil { 713 t.Fatal(err) 714 } 715 716 w := &testResponseWriter{} 717 s3Select.Evaluate(w) 718 s3Select.Close() 719 resp := http.Response{ 720 StatusCode: http.StatusOK, 721 Body: ioutil.NopCloser(bytes.NewReader(w.response)), 722 ContentLength: int64(len(w.response)), 723 } 724 res, err := minio.NewSelectResults(&resp, "testbucket") 725 if err != nil { 726 t.Error(err) 727 return 728 } 729 got, err := ioutil.ReadAll(res) 730 if err != nil { 731 t.Error(err) 732 return 733 } 734 gotS := strings.TrimSpace(string(got)) 735 if !reflect.DeepEqual(gotS, testCase.wantResult) { 736 t.Errorf("received response does not match with expected reply. Query: %s\ngot: %s\nwant:%s", testCase.query, gotS, testCase.wantResult) 737 } 738 }) 739 } 740 } 741 742 func TestCSVQueries3(t *testing.T) { 743 input := `na.me,qty,CAST 744 apple,1,true 745 mango,3,false 746 ` 747 var testTable = []struct { 748 name string 749 query string 750 requestXML []byte // override request XML 751 wantResult string 752 }{ 753 { 754 name: "Select a column containing dot", 755 query: `select "na.me" from S3Object s`, 756 wantResult: `apple 757 mango`, 758 }, 759 { 760 name: "Select column containing dot with table name prefix", 761 query: `select count(S3Object."na.me") from S3Object`, 762 wantResult: `2`, 763 }, 764 { 765 name: "Select column containing dot with table alias prefix", 766 query: `select s."na.me" from S3Object as s`, 767 wantResult: `apple 768 mango`, 769 }, 770 { 771 name: "Select column simplest", 772 query: `select qty from S3Object`, 773 wantResult: `1 774 3`, 775 }, 776 { 777 name: "Select column with table name prefix", 778 query: `select S3Object.qty from S3Object`, 779 wantResult: `1 780 3`, 781 }, 782 { 783 name: "Select column without table alias", 784 query: `select qty from S3Object s`, 785 wantResult: `1 786 3`, 787 }, 788 { 789 name: "Select column with table alias", 790 query: `select s.qty from S3Object s`, 791 wantResult: `1 792 3`, 793 }, 794 { 795 name: "Select reserved word column", 796 query: `select "CAST" from s3object`, 797 wantResult: `true 798 false`, 799 }, 800 { 801 name: "Select reserved word column with table alias", 802 query: `select S3Object."CAST" from s3object`, 803 wantResult: `true 804 false`, 805 }, 806 { 807 name: "Select reserved word column with unused table alias", 808 query: `select "CAST" from s3object s`, 809 wantResult: `true 810 false`, 811 }, 812 { 813 name: "Select reserved word column with table alias", 814 query: `select s."CAST" from s3object s`, 815 wantResult: `true 816 false`, 817 }, 818 { 819 name: "Select reserved word column with table alias", 820 query: `select NOT CAST(s."CAST" AS Bool) from s3object s`, 821 wantResult: `false 822 true`, 823 }, 824 } 825 826 defRequest := `<?xml version="1.0" encoding="UTF-8"?> 827 <SelectObjectContentRequest> 828 <Expression>%s</Expression> 829 <ExpressionType>SQL</ExpressionType> 830 <InputSerialization> 831 <CompressionType>NONE</CompressionType> 832 <CSV> 833 <FileHeaderInfo>USE</FileHeaderInfo> 834 <QuoteCharacter>"</QuoteCharacter> 835 </CSV> 836 </InputSerialization> 837 <OutputSerialization> 838 <CSV/> 839 </OutputSerialization> 840 <RequestProgress> 841 <Enabled>FALSE</Enabled> 842 </RequestProgress> 843 </SelectObjectContentRequest>` 844 845 for _, testCase := range testTable { 846 t.Run(testCase.name, func(t *testing.T) { 847 testReq := testCase.requestXML 848 if len(testReq) == 0 { 849 testReq = []byte(fmt.Sprintf(defRequest, testCase.query)) 850 } 851 s3Select, err := NewS3Select(bytes.NewReader(testReq)) 852 if err != nil { 853 t.Fatal(err) 854 } 855 856 if err = s3Select.Open(func(offset, length int64) (io.ReadCloser, error) { 857 return ioutil.NopCloser(bytes.NewBufferString(input)), nil 858 }); err != nil { 859 t.Fatal(err) 860 } 861 862 w := &testResponseWriter{} 863 s3Select.Evaluate(w) 864 s3Select.Close() 865 resp := http.Response{ 866 StatusCode: http.StatusOK, 867 Body: ioutil.NopCloser(bytes.NewReader(w.response)), 868 ContentLength: int64(len(w.response)), 869 } 870 res, err := minio.NewSelectResults(&resp, "testbucket") 871 if err != nil { 872 t.Error(err) 873 return 874 } 875 got, err := ioutil.ReadAll(res) 876 if err != nil { 877 t.Error(err) 878 return 879 } 880 gotS := strings.TrimSpace(string(got)) 881 if gotS != testCase.wantResult { 882 t.Errorf("received response does not match with expected reply.\nQuery: %s\n=====\ngot: %s\n=====\nwant: %s\n=====\n", testCase.query, gotS, testCase.wantResult) 883 } 884 }) 885 } 886 } 887 888 func TestCSVInput(t *testing.T) { 889 var testTable = []struct { 890 requestXML []byte 891 expectedResult []byte 892 }{ 893 { 894 []byte(` 895 <?xml version="1.0" encoding="UTF-8"?> 896 <SelectObjectContentRequest> 897 <Expression>SELECT one, two, three from S3Object</Expression> 898 <ExpressionType>SQL</ExpressionType> 899 <InputSerialization> 900 <CompressionType>NONE</CompressionType> 901 <CSV> 902 <FileHeaderInfo>USE</FileHeaderInfo> 903 </CSV> 904 </InputSerialization> 905 <OutputSerialization> 906 <CSV> 907 </CSV> 908 </OutputSerialization> 909 <RequestProgress> 910 <Enabled>FALSE</Enabled> 911 </RequestProgress> 912 </SelectObjectContentRequest> 913 `), []byte{ 914 0, 0, 0, 137, 0, 0, 0, 85, 194, 213, 168, 241, 13, 58, 109, 101, 115, 115, 97, 103, 101, 45, 116, 121, 112, 101, 7, 0, 5, 101, 118, 101, 110, 116, 13, 58, 99, 111, 110, 116, 101, 110, 116, 45, 116, 121, 112, 101, 7, 0, 24, 97, 112, 112, 108, 105, 99, 97, 116, 105, 111, 110, 47, 111, 99, 116, 101, 116, 45, 115, 116, 114, 101, 97, 109, 11, 58, 101, 118, 101, 110, 116, 45, 116, 121, 112, 101, 7, 0, 7, 82, 101, 99, 111, 114, 100, 115, 45, 49, 44, 102, 111, 111, 44, 116, 114, 117, 101, 10, 44, 98, 97, 114, 44, 102, 97, 108, 115, 101, 10, 50, 46, 53, 44, 98, 97, 122, 44, 116, 114, 117, 101, 10, 75, 182, 193, 80, 0, 0, 0, 235, 0, 0, 0, 67, 213, 243, 57, 141, 13, 58, 109, 101, 115, 115, 97, 103, 101, 45, 116, 121, 112, 101, 7, 0, 5, 101, 118, 101, 110, 116, 13, 58, 99, 111, 110, 116, 101, 110, 116, 45, 116, 121, 112, 101, 7, 0, 8, 116, 101, 120, 116, 47, 120, 109, 108, 11, 58, 101, 118, 101, 110, 116, 45, 116, 121, 112, 101, 7, 0, 5, 83, 116, 97, 116, 115, 60, 63, 120, 109, 108, 32, 118, 101, 114, 115, 105, 111, 110, 61, 34, 49, 46, 48, 34, 32, 101, 110, 99, 111, 100, 105, 110, 103, 61, 34, 85, 84, 70, 45, 56, 34, 63, 62, 60, 83, 116, 97, 116, 115, 62, 60, 66, 121, 116, 101, 115, 83, 99, 97, 110, 110, 101, 100, 62, 53, 48, 60, 47, 66, 121, 116, 101, 115, 83, 99, 97, 110, 110, 101, 100, 62, 60, 66, 121, 116, 101, 115, 80, 114, 111, 99, 101, 115, 115, 101, 100, 62, 53, 48, 60, 47, 66, 121, 116, 101, 115, 80, 114, 111, 99, 101, 115, 115, 101, 100, 62, 60, 66, 121, 116, 101, 115, 82, 101, 116, 117, 114, 110, 101, 100, 62, 51, 54, 60, 47, 66, 121, 116, 101, 115, 82, 101, 116, 117, 114, 110, 101, 100, 62, 60, 47, 83, 116, 97, 116, 115, 62, 253, 105, 8, 216, 0, 0, 0, 56, 0, 0, 0, 40, 193, 198, 132, 212, 13, 58, 109, 101, 115, 115, 97, 103, 101, 45, 116, 121, 112, 101, 7, 0, 5, 101, 118, 101, 110, 116, 11, 58, 101, 118, 101, 110, 116, 45, 116, 121, 112, 101, 7, 0, 3, 69, 110, 100, 207, 151, 211, 146, 915 }, 916 }, 917 { 918 []byte(` 919 <?xml version="1.0" encoding="UTF-8"?> 920 <SelectObjectContentRequest> 921 <Expression>SELECT COUNT(*) AS total_record_count from S3Object</Expression> 922 <ExpressionType>SQL</ExpressionType> 923 <InputSerialization> 924 <CompressionType>NONE</CompressionType> 925 <CSV> 926 <FileHeaderInfo>USE</FileHeaderInfo> 927 </CSV> 928 </InputSerialization> 929 <OutputSerialization> 930 <JSON> 931 </JSON> 932 </OutputSerialization> 933 <RequestProgress> 934 <Enabled>FALSE</Enabled> 935 </RequestProgress> 936 </SelectObjectContentRequest> 937 `), []byte{ 938 0, 0, 0, 126, 0, 0, 0, 85, 56, 193, 36, 188, 13, 58, 109, 101, 115, 115, 97, 103, 101, 45, 116, 121, 112, 101, 7, 0, 5, 101, 118, 101, 110, 116, 13, 58, 99, 111, 110, 116, 101, 110, 116, 45, 116, 121, 112, 101, 7, 0, 24, 97, 112, 112, 108, 105, 99, 97, 116, 105, 111, 110, 47, 111, 99, 116, 101, 116, 45, 115, 116, 114, 101, 97, 109, 11, 58, 101, 118, 101, 110, 116, 45, 116, 121, 112, 101, 7, 0, 7, 82, 101, 99, 111, 114, 100, 115, 123, 34, 116, 111, 116, 97, 108, 95, 114, 101, 99, 111, 114, 100, 95, 99, 111, 117, 110, 116, 34, 58, 51, 125, 10, 196, 183, 134, 242, 0, 0, 0, 235, 0, 0, 0, 67, 213, 243, 57, 141, 13, 58, 109, 101, 115, 115, 97, 103, 101, 45, 116, 121, 112, 101, 7, 0, 5, 101, 118, 101, 110, 116, 13, 58, 99, 111, 110, 116, 101, 110, 116, 45, 116, 121, 112, 101, 7, 0, 8, 116, 101, 120, 116, 47, 120, 109, 108, 11, 58, 101, 118, 101, 110, 116, 45, 116, 121, 112, 101, 7, 0, 5, 83, 116, 97, 116, 115, 60, 63, 120, 109, 108, 32, 118, 101, 114, 115, 105, 111, 110, 61, 34, 49, 46, 48, 34, 32, 101, 110, 99, 111, 100, 105, 110, 103, 61, 34, 85, 84, 70, 45, 56, 34, 63, 62, 60, 83, 116, 97, 116, 115, 62, 60, 66, 121, 116, 101, 115, 83, 99, 97, 110, 110, 101, 100, 62, 53, 48, 60, 47, 66, 121, 116, 101, 115, 83, 99, 97, 110, 110, 101, 100, 62, 60, 66, 121, 116, 101, 115, 80, 114, 111, 99, 101, 115, 115, 101, 100, 62, 53, 48, 60, 47, 66, 121, 116, 101, 115, 80, 114, 111, 99, 101, 115, 115, 101, 100, 62, 60, 66, 121, 116, 101, 115, 82, 101, 116, 117, 114, 110, 101, 100, 62, 50, 53, 60, 47, 66, 121, 116, 101, 115, 82, 101, 116, 117, 114, 110, 101, 100, 62, 60, 47, 83, 116, 97, 116, 115, 62, 47, 153, 24, 28, 0, 0, 0, 56, 0, 0, 0, 40, 193, 198, 132, 212, 13, 58, 109, 101, 115, 115, 97, 103, 101, 45, 116, 121, 112, 101, 7, 0, 5, 101, 118, 101, 110, 116, 11, 58, 101, 118, 101, 110, 116, 45, 116, 121, 112, 101, 7, 0, 3, 69, 110, 100, 207, 151, 211, 146, 939 }, 940 }, 941 { 942 []byte(` 943 <?xml version="1.0" encoding="UTF-8"?> 944 <SelectObjectContentRequest> 945 <Expression>SELECT * from S3Object</Expression> 946 <ExpressionType>SQL</ExpressionType> 947 <InputSerialization> 948 <CompressionType>NONE</CompressionType> 949 <CSV> 950 <FileHeaderInfo>USE</FileHeaderInfo> 951 </CSV> 952 </InputSerialization> 953 <OutputSerialization> 954 <JSON> 955 </JSON> 956 </OutputSerialization> 957 <RequestProgress> 958 <Enabled>FALSE</Enabled> 959 </RequestProgress> 960 </SelectObjectContentRequest> 961 `), []byte{0x0, 0x0, 0x0, 0xdd, 0x0, 0x0, 0x0, 0x55, 0xf, 0x46, 0xc1, 0xfa, 0xd, 0x3a, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x2d, 0x74, 0x79, 0x70, 0x65, 0x7, 0x0, 0x5, 0x65, 0x76, 0x65, 0x6e, 0x74, 0xd, 0x3a, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x2d, 0x74, 0x79, 0x70, 0x65, 0x7, 0x0, 0x18, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x6f, 0x63, 0x74, 0x65, 0x74, 0x2d, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0xb, 0x3a, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x2d, 0x74, 0x79, 0x70, 0x65, 0x7, 0x0, 0x7, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x7b, 0x22, 0x6f, 0x6e, 0x65, 0x22, 0x3a, 0x22, 0x2d, 0x31, 0x22, 0x2c, 0x22, 0x74, 0x77, 0x6f, 0x22, 0x3a, 0x22, 0x66, 0x6f, 0x6f, 0x22, 0x2c, 0x22, 0x74, 0x68, 0x72, 0x65, 0x65, 0x22, 0x3a, 0x22, 0x74, 0x72, 0x75, 0x65, 0x22, 0x7d, 0xa, 0x7b, 0x22, 0x6f, 0x6e, 0x65, 0x22, 0x3a, 0x22, 0x22, 0x2c, 0x22, 0x74, 0x77, 0x6f, 0x22, 0x3a, 0x22, 0x62, 0x61, 0x72, 0x22, 0x2c, 0x22, 0x74, 0x68, 0x72, 0x65, 0x65, 0x22, 0x3a, 0x22, 0x66, 0x61, 0x6c, 0x73, 0x65, 0x22, 0x7d, 0xa, 0x7b, 0x22, 0x6f, 0x6e, 0x65, 0x22, 0x3a, 0x22, 0x32, 0x2e, 0x35, 0x22, 0x2c, 0x22, 0x74, 0x77, 0x6f, 0x22, 0x3a, 0x22, 0x62, 0x61, 0x7a, 0x22, 0x2c, 0x22, 0x74, 0x68, 0x72, 0x65, 0x65, 0x22, 0x3a, 0x22, 0x74, 0x72, 0x75, 0x65, 0x22, 0x7d, 0xa, 0x7e, 0xb5, 0x99, 0xfb, 0x0, 0x0, 0x0, 0xec, 0x0, 0x0, 0x0, 0x43, 0x67, 0xd3, 0xe5, 0x9d, 0xd, 0x3a, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x2d, 0x74, 0x79, 0x70, 0x65, 0x7, 0x0, 0x5, 0x65, 0x76, 0x65, 0x6e, 0x74, 0xd, 0x3a, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x2d, 0x74, 0x79, 0x70, 0x65, 0x7, 0x0, 0x8, 0x74, 0x65, 0x78, 0x74, 0x2f, 0x78, 0x6d, 0x6c, 0xb, 0x3a, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x2d, 0x74, 0x79, 0x70, 0x65, 0x7, 0x0, 0x5, 0x53, 0x74, 0x61, 0x74, 0x73, 0x3c, 0x3f, 0x78, 0x6d, 0x6c, 0x20, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x3d, 0x22, 0x31, 0x2e, 0x30, 0x22, 0x20, 0x65, 0x6e, 0x63, 0x6f, 0x64, 0x69, 0x6e, 0x67, 0x3d, 0x22, 0x55, 0x54, 0x46, 0x2d, 0x38, 0x22, 0x3f, 0x3e, 0x3c, 0x53, 0x74, 0x61, 0x74, 0x73, 0x3e, 0x3c, 0x42, 0x79, 0x74, 0x65, 0x73, 0x53, 0x63, 0x61, 0x6e, 0x6e, 0x65, 0x64, 0x3e, 0x35, 0x30, 0x3c, 0x2f, 0x42, 0x79, 0x74, 0x65, 0x73, 0x53, 0x63, 0x61, 0x6e, 0x6e, 0x65, 0x64, 0x3e, 0x3c, 0x42, 0x79, 0x74, 0x65, 0x73, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x65, 0x64, 0x3e, 0x35, 0x30, 0x3c, 0x2f, 0x42, 0x79, 0x74, 0x65, 0x73, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x65, 0x64, 0x3e, 0x3c, 0x42, 0x79, 0x74, 0x65, 0x73, 0x52, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x65, 0x64, 0x3e, 0x31, 0x32, 0x30, 0x3c, 0x2f, 0x42, 0x79, 0x74, 0x65, 0x73, 0x52, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x65, 0x64, 0x3e, 0x3c, 0x2f, 0x53, 0x74, 0x61, 0x74, 0x73, 0x3e, 0x5a, 0xe5, 0xd, 0x84, 0x0, 0x0, 0x0, 0x38, 0x0, 0x0, 0x0, 0x28, 0xc1, 0xc6, 0x84, 0xd4, 0xd, 0x3a, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x2d, 0x74, 0x79, 0x70, 0x65, 0x7, 0x0, 0x5, 0x65, 0x76, 0x65, 0x6e, 0x74, 0xb, 0x3a, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x2d, 0x74, 0x79, 0x70, 0x65, 0x7, 0x0, 0x3, 0x45, 0x6e, 0x64, 0xcf, 0x97, 0xd3, 0x92}, 962 }, 963 { 964 []byte(` 965 <?xml version="1.0" encoding="UTF-8"?> 966 <SelectObjectContentRequest> 967 <Expression>SELECT one from S3Object limit 1</Expression> 968 <ExpressionType>SQL</ExpressionType> 969 <InputSerialization> 970 <CompressionType>NONE</CompressionType> 971 <CSV> 972 <FileHeaderInfo>USE</FileHeaderInfo> 973 </CSV> 974 </InputSerialization> 975 <OutputSerialization> 976 <CSV> 977 </CSV> 978 </OutputSerialization> 979 <RequestProgress> 980 <Enabled>FALSE</Enabled> 981 </RequestProgress> 982 </SelectObjectContentRequest> 983 `), []byte{ 984 0x0, 0x0, 0x0, 0x68, 0x0, 0x0, 0x0, 0x55, 0xd7, 0x61, 0x46, 0x9e, 0xd, 0x3a, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x2d, 0x74, 0x79, 0x70, 0x65, 0x7, 0x0, 0x5, 0x65, 0x76, 0x65, 0x6e, 0x74, 0xd, 0x3a, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x2d, 0x74, 0x79, 0x70, 0x65, 0x7, 0x0, 0x18, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x6f, 0x63, 0x74, 0x65, 0x74, 0x2d, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0xb, 0x3a, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x2d, 0x74, 0x79, 0x70, 0x65, 0x7, 0x0, 0x7, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x2d, 0x31, 0xa, 0x17, 0xfb, 0x1, 0x90, 0x0, 0x0, 0x0, 0xea, 0x0, 0x0, 0x0, 0x43, 0xe8, 0x93, 0x10, 0x3d, 0xd, 0x3a, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x2d, 0x74, 0x79, 0x70, 0x65, 0x7, 0x0, 0x5, 0x65, 0x76, 0x65, 0x6e, 0x74, 0xd, 0x3a, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x2d, 0x74, 0x79, 0x70, 0x65, 0x7, 0x0, 0x8, 0x74, 0x65, 0x78, 0x74, 0x2f, 0x78, 0x6d, 0x6c, 0xb, 0x3a, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x2d, 0x74, 0x79, 0x70, 0x65, 0x7, 0x0, 0x5, 0x53, 0x74, 0x61, 0x74, 0x73, 0x3c, 0x3f, 0x78, 0x6d, 0x6c, 0x20, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x3d, 0x22, 0x31, 0x2e, 0x30, 0x22, 0x20, 0x65, 0x6e, 0x63, 0x6f, 0x64, 0x69, 0x6e, 0x67, 0x3d, 0x22, 0x55, 0x54, 0x46, 0x2d, 0x38, 0x22, 0x3f, 0x3e, 0x3c, 0x53, 0x74, 0x61, 0x74, 0x73, 0x3e, 0x3c, 0x42, 0x79, 0x74, 0x65, 0x73, 0x53, 0x63, 0x61, 0x6e, 0x6e, 0x65, 0x64, 0x3e, 0x35, 0x30, 0x3c, 0x2f, 0x42, 0x79, 0x74, 0x65, 0x73, 0x53, 0x63, 0x61, 0x6e, 0x6e, 0x65, 0x64, 0x3e, 0x3c, 0x42, 0x79, 0x74, 0x65, 0x73, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x65, 0x64, 0x3e, 0x35, 0x30, 0x3c, 0x2f, 0x42, 0x79, 0x74, 0x65, 0x73, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x65, 0x64, 0x3e, 0x3c, 0x42, 0x79, 0x74, 0x65, 0x73, 0x52, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x65, 0x64, 0x3e, 0x33, 0x3c, 0x2f, 0x42, 0x79, 0x74, 0x65, 0x73, 0x52, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x65, 0x64, 0x3e, 0x3c, 0x2f, 0x53, 0x74, 0x61, 0x74, 0x73, 0x3e, 0x15, 0x72, 0x19, 0x94, 0x0, 0x0, 0x0, 0x38, 0x0, 0x0, 0x0, 0x28, 0xc1, 0xc6, 0x84, 0xd4, 0xd, 0x3a, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x2d, 0x74, 0x79, 0x70, 0x65, 0x7, 0x0, 0x5, 0x65, 0x76, 0x65, 0x6e, 0x74, 0xb, 0x3a, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x2d, 0x74, 0x79, 0x70, 0x65, 0x7, 0x0, 0x3, 0x45, 0x6e, 0x64, 0xcf, 0x97, 0xd3, 0x92, 985 }, 986 }, 987 } 988 989 var csvData = []byte(`one,two,three 990 -1,foo,true 991 ,bar,false 992 2.5,baz,true 993 `) 994 995 for i, testCase := range testTable { 996 t.Run(fmt.Sprint(i), func(t *testing.T) { 997 s3Select, err := NewS3Select(bytes.NewReader(testCase.requestXML)) 998 if err != nil { 999 t.Fatal(err) 1000 } 1001 1002 if err = s3Select.Open(func(offset, length int64) (io.ReadCloser, error) { 1003 return ioutil.NopCloser(bytes.NewReader(csvData)), nil 1004 }); err != nil { 1005 t.Fatal(err) 1006 } 1007 1008 w := &testResponseWriter{} 1009 s3Select.Evaluate(w) 1010 s3Select.Close() 1011 1012 if !reflect.DeepEqual(w.response, testCase.expectedResult) { 1013 resp := http.Response{ 1014 StatusCode: http.StatusOK, 1015 Body: ioutil.NopCloser(bytes.NewReader(w.response)), 1016 ContentLength: int64(len(w.response)), 1017 } 1018 res, err := minio.NewSelectResults(&resp, "testbucket") 1019 if err != nil { 1020 t.Error(err) 1021 return 1022 } 1023 got, err := ioutil.ReadAll(res) 1024 if err != nil { 1025 t.Error(err) 1026 return 1027 } 1028 1029 t.Errorf("received response does not match with expected reply\ngot: %#v\nwant:%#v\ndecoded:%s", w.response, testCase.expectedResult, string(got)) 1030 } 1031 }) 1032 } 1033 1034 } 1035 1036 func TestJSONInput(t *testing.T) { 1037 1038 var testTable = []struct { 1039 requestXML []byte 1040 expectedResult []byte 1041 }{ 1042 { 1043 []byte(` 1044 <?xml version="1.0" encoding="UTF-8"?> 1045 <SelectObjectContentRequest> 1046 <Expression>SELECT one, two, three from S3Object</Expression> 1047 <ExpressionType>SQL</ExpressionType> 1048 <InputSerialization> 1049 <CompressionType>NONE</CompressionType> 1050 <JSON> 1051 <Type>DOCUMENT</Type> 1052 </JSON> 1053 </InputSerialization> 1054 <OutputSerialization> 1055 <CSV> 1056 </CSV> 1057 </OutputSerialization> 1058 <RequestProgress> 1059 <Enabled>FALSE</Enabled> 1060 </RequestProgress> 1061 </SelectObjectContentRequest> 1062 `), []byte{ 1063 0, 0, 0, 137, 0, 0, 0, 85, 194, 213, 168, 241, 13, 58, 109, 101, 115, 115, 97, 103, 101, 45, 116, 121, 112, 101, 7, 0, 5, 101, 118, 101, 110, 116, 13, 58, 99, 111, 110, 116, 101, 110, 116, 45, 116, 121, 112, 101, 7, 0, 24, 97, 112, 112, 108, 105, 99, 97, 116, 105, 111, 110, 47, 111, 99, 116, 101, 116, 45, 115, 116, 114, 101, 97, 109, 11, 58, 101, 118, 101, 110, 116, 45, 116, 121, 112, 101, 7, 0, 7, 82, 101, 99, 111, 114, 100, 115, 45, 49, 44, 102, 111, 111, 44, 116, 114, 117, 101, 10, 44, 98, 97, 114, 44, 102, 97, 108, 115, 101, 10, 50, 46, 53, 44, 98, 97, 122, 44, 116, 114, 117, 101, 10, 75, 182, 193, 80, 0, 0, 0, 237, 0, 0, 0, 67, 90, 179, 204, 45, 13, 58, 109, 101, 115, 115, 97, 103, 101, 45, 116, 121, 112, 101, 7, 0, 5, 101, 118, 101, 110, 116, 13, 58, 99, 111, 110, 116, 101, 110, 116, 45, 116, 121, 112, 101, 7, 0, 8, 116, 101, 120, 116, 47, 120, 109, 108, 11, 58, 101, 118, 101, 110, 116, 45, 116, 121, 112, 101, 7, 0, 5, 83, 116, 97, 116, 115, 60, 63, 120, 109, 108, 32, 118, 101, 114, 115, 105, 111, 110, 61, 34, 49, 46, 48, 34, 32, 101, 110, 99, 111, 100, 105, 110, 103, 61, 34, 85, 84, 70, 45, 56, 34, 63, 62, 60, 83, 116, 97, 116, 115, 62, 60, 66, 121, 116, 101, 115, 83, 99, 97, 110, 110, 101, 100, 62, 49, 49, 50, 60, 47, 66, 121, 116, 101, 115, 83, 99, 97, 110, 110, 101, 100, 62, 60, 66, 121, 116, 101, 115, 80, 114, 111, 99, 101, 115, 115, 101, 100, 62, 49, 49, 50, 60, 47, 66, 121, 116, 101, 115, 80, 114, 111, 99, 101, 115, 115, 101, 100, 62, 60, 66, 121, 116, 101, 115, 82, 101, 116, 117, 114, 110, 101, 100, 62, 51, 54, 60, 47, 66, 121, 116, 101, 115, 82, 101, 116, 117, 114, 110, 101, 100, 62, 60, 47, 83, 116, 97, 116, 115, 62, 181, 40, 50, 250, 0, 0, 0, 56, 0, 0, 0, 40, 193, 198, 132, 212, 13, 58, 109, 101, 115, 115, 97, 103, 101, 45, 116, 121, 112, 101, 7, 0, 5, 101, 118, 101, 110, 116, 11, 58, 101, 118, 101, 110, 116, 45, 116, 121, 112, 101, 7, 0, 3, 69, 110, 100, 207, 151, 211, 146, 1064 }, 1065 }, 1066 { 1067 []byte(` 1068 <?xml version="1.0" encoding="UTF-8"?> 1069 <SelectObjectContentRequest> 1070 <Expression>SELECT COUNT(*) AS total_record_count from S3Object</Expression> 1071 <ExpressionType>SQL</ExpressionType> 1072 <InputSerialization> 1073 <CompressionType>NONE</CompressionType> 1074 <JSON> 1075 <Type>DOCUMENT</Type> 1076 </JSON> 1077 </InputSerialization> 1078 <OutputSerialization> 1079 <CSV> 1080 </CSV> 1081 </OutputSerialization> 1082 <RequestProgress> 1083 <Enabled>FALSE</Enabled> 1084 </RequestProgress> 1085 </SelectObjectContentRequest> 1086 `), []byte{ 1087 0, 0, 0, 103, 0, 0, 0, 85, 85, 49, 209, 79, 13, 58, 109, 101, 115, 115, 97, 103, 101, 45, 116, 121, 112, 101, 7, 0, 5, 101, 118, 101, 110, 116, 13, 58, 99, 111, 110, 116, 101, 110, 116, 45, 116, 121, 112, 101, 7, 0, 24, 97, 112, 112, 108, 105, 99, 97, 116, 105, 111, 110, 47, 111, 99, 116, 101, 116, 45, 115, 116, 114, 101, 97, 109, 11, 58, 101, 118, 101, 110, 116, 45, 116, 121, 112, 101, 7, 0, 7, 82, 101, 99, 111, 114, 100, 115, 51, 10, 175, 58, 213, 152, 0, 0, 0, 236, 0, 0, 0, 67, 103, 211, 229, 157, 13, 58, 109, 101, 115, 115, 97, 103, 101, 45, 116, 121, 112, 101, 7, 0, 5, 101, 118, 101, 110, 116, 13, 58, 99, 111, 110, 116, 101, 110, 116, 45, 116, 121, 112, 101, 7, 0, 8, 116, 101, 120, 116, 47, 120, 109, 108, 11, 58, 101, 118, 101, 110, 116, 45, 116, 121, 112, 101, 7, 0, 5, 83, 116, 97, 116, 115, 60, 63, 120, 109, 108, 32, 118, 101, 114, 115, 105, 111, 110, 61, 34, 49, 46, 48, 34, 32, 101, 110, 99, 111, 100, 105, 110, 103, 61, 34, 85, 84, 70, 45, 56, 34, 63, 62, 60, 83, 116, 97, 116, 115, 62, 60, 66, 121, 116, 101, 115, 83, 99, 97, 110, 110, 101, 100, 62, 49, 49, 50, 60, 47, 66, 121, 116, 101, 115, 83, 99, 97, 110, 110, 101, 100, 62, 60, 66, 121, 116, 101, 115, 80, 114, 111, 99, 101, 115, 115, 101, 100, 62, 49, 49, 50, 60, 47, 66, 121, 116, 101, 115, 80, 114, 111, 99, 101, 115, 115, 101, 100, 62, 60, 66, 121, 116, 101, 115, 82, 101, 116, 117, 114, 110, 101, 100, 62, 50, 60, 47, 66, 121, 116, 101, 115, 82, 101, 116, 117, 114, 110, 101, 100, 62, 60, 47, 83, 116, 97, 116, 115, 62, 52, 192, 77, 114, 0, 0, 0, 56, 0, 0, 0, 40, 193, 198, 132, 212, 13, 58, 109, 101, 115, 115, 97, 103, 101, 45, 116, 121, 112, 101, 7, 0, 5, 101, 118, 101, 110, 116, 11, 58, 101, 118, 101, 110, 116, 45, 116, 121, 112, 101, 7, 0, 3, 69, 110, 100, 207, 151, 211, 146, 1088 }, 1089 }, 1090 { 1091 []byte(` 1092 <?xml version="1.0" encoding="UTF-8"?> 1093 <SelectObjectContentRequest> 1094 <Expression>SELECT * from S3Object</Expression> 1095 <ExpressionType>SQL</ExpressionType> 1096 <InputSerialization> 1097 <CompressionType>NONE</CompressionType> 1098 <JSON> 1099 <Type>DOCUMENT</Type> 1100 </JSON> 1101 </InputSerialization> 1102 <OutputSerialization> 1103 <CSV> 1104 </CSV> 1105 </OutputSerialization> 1106 <RequestProgress> 1107 <Enabled>FALSE</Enabled> 1108 </RequestProgress> 1109 </SelectObjectContentRequest> 1110 `), []byte{0x0, 0x0, 0x0, 0x89, 0x0, 0x0, 0x0, 0x55, 0xc2, 0xd5, 0xa8, 0xf1, 0xd, 0x3a, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x2d, 0x74, 0x79, 0x70, 0x65, 0x7, 0x0, 0x5, 0x65, 0x76, 0x65, 0x6e, 0x74, 0xd, 0x3a, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x2d, 0x74, 0x79, 0x70, 0x65, 0x7, 0x0, 0x18, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x6f, 0x63, 0x74, 0x65, 0x74, 0x2d, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0xb, 0x3a, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x2d, 0x74, 0x79, 0x70, 0x65, 0x7, 0x0, 0x7, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x74, 0x72, 0x75, 0x65, 0x2c, 0x66, 0x6f, 0x6f, 0x2c, 0x2d, 0x31, 0xa, 0x66, 0x61, 0x6c, 0x73, 0x65, 0x2c, 0x62, 0x61, 0x72, 0x2c, 0xa, 0x74, 0x72, 0x75, 0x65, 0x2c, 0x62, 0x61, 0x7a, 0x2c, 0x32, 0x2e, 0x35, 0xa, 0xef, 0x22, 0x13, 0xa3, 0x0, 0x0, 0x0, 0xed, 0x0, 0x0, 0x0, 0x43, 0x5a, 0xb3, 0xcc, 0x2d, 0xd, 0x3a, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x2d, 0x74, 0x79, 0x70, 0x65, 0x7, 0x0, 0x5, 0x65, 0x76, 0x65, 0x6e, 0x74, 0xd, 0x3a, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x2d, 0x74, 0x79, 0x70, 0x65, 0x7, 0x0, 0x8, 0x74, 0x65, 0x78, 0x74, 0x2f, 0x78, 0x6d, 0x6c, 0xb, 0x3a, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x2d, 0x74, 0x79, 0x70, 0x65, 0x7, 0x0, 0x5, 0x53, 0x74, 0x61, 0x74, 0x73, 0x3c, 0x3f, 0x78, 0x6d, 0x6c, 0x20, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x3d, 0x22, 0x31, 0x2e, 0x30, 0x22, 0x20, 0x65, 0x6e, 0x63, 0x6f, 0x64, 0x69, 0x6e, 0x67, 0x3d, 0x22, 0x55, 0x54, 0x46, 0x2d, 0x38, 0x22, 0x3f, 0x3e, 0x3c, 0x53, 0x74, 0x61, 0x74, 0x73, 0x3e, 0x3c, 0x42, 0x79, 0x74, 0x65, 0x73, 0x53, 0x63, 0x61, 0x6e, 0x6e, 0x65, 0x64, 0x3e, 0x31, 0x31, 0x32, 0x3c, 0x2f, 0x42, 0x79, 0x74, 0x65, 0x73, 0x53, 0x63, 0x61, 0x6e, 0x6e, 0x65, 0x64, 0x3e, 0x3c, 0x42, 0x79, 0x74, 0x65, 0x73, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x65, 0x64, 0x3e, 0x31, 0x31, 0x32, 0x3c, 0x2f, 0x42, 0x79, 0x74, 0x65, 0x73, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x65, 0x64, 0x3e, 0x3c, 0x42, 0x79, 0x74, 0x65, 0x73, 0x52, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x65, 0x64, 0x3e, 0x33, 0x36, 0x3c, 0x2f, 0x42, 0x79, 0x74, 0x65, 0x73, 0x52, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x65, 0x64, 0x3e, 0x3c, 0x2f, 0x53, 0x74, 0x61, 0x74, 0x73, 0x3e, 0xb5, 0x28, 0x32, 0xfa, 0x0, 0x0, 0x0, 0x38, 0x0, 0x0, 0x0, 0x28, 0xc1, 0xc6, 0x84, 0xd4, 0xd, 0x3a, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x2d, 0x74, 0x79, 0x70, 0x65, 0x7, 0x0, 0x5, 0x65, 0x76, 0x65, 0x6e, 0x74, 0xb, 0x3a, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x2d, 0x74, 0x79, 0x70, 0x65, 0x7, 0x0, 0x3, 0x45, 0x6e, 0x64, 0xcf, 0x97, 0xd3, 0x92}, 1111 }, 1112 } 1113 1114 var jsonData = []byte(`{"three":true,"two":"foo","one":-1} 1115 {"three":false,"two":"bar","one":null} 1116 {"three":true,"two":"baz","one":2.5} 1117 `) 1118 1119 for i, testCase := range testTable { 1120 t.Run(fmt.Sprint(i), func(t *testing.T) { 1121 s3Select, err := NewS3Select(bytes.NewReader(testCase.requestXML)) 1122 if err != nil { 1123 t.Fatal(err) 1124 } 1125 1126 if err = s3Select.Open(func(offset, length int64) (io.ReadCloser, error) { 1127 return ioutil.NopCloser(bytes.NewReader(jsonData)), nil 1128 }); err != nil { 1129 t.Fatal(err) 1130 } 1131 1132 w := &testResponseWriter{} 1133 s3Select.Evaluate(w) 1134 s3Select.Close() 1135 1136 if !reflect.DeepEqual(w.response, testCase.expectedResult) { 1137 resp := http.Response{ 1138 StatusCode: http.StatusOK, 1139 Body: ioutil.NopCloser(bytes.NewReader(w.response)), 1140 ContentLength: int64(len(w.response)), 1141 } 1142 res, err := minio.NewSelectResults(&resp, "testbucket") 1143 if err != nil { 1144 t.Error(err) 1145 return 1146 } 1147 got, err := ioutil.ReadAll(res) 1148 if err != nil { 1149 t.Error(err) 1150 return 1151 } 1152 1153 t.Errorf("received response does not match with expected reply\ngot: %#v\nwant:%#v\ndecoded:%s", w.response, testCase.expectedResult, string(got)) 1154 } 1155 }) 1156 } 1157 } 1158 1159 func TestParquetInput(t *testing.T) { 1160 os.Setenv("MINIO_API_SELECT_PARQUET", "on") 1161 defer os.Setenv("MINIO_API_SELECT_PARQUET", "off") 1162 1163 var testTable = []struct { 1164 requestXML []byte 1165 expectedResult []byte 1166 }{ 1167 { 1168 []byte(` 1169 <?xml version="1.0" encoding="UTF-8"?> 1170 <SelectObjectContentRequest> 1171 <Expression>SELECT one, two, three from S3Object</Expression> 1172 <ExpressionType>SQL</ExpressionType> 1173 <InputSerialization> 1174 <CompressionType>NONE</CompressionType> 1175 <Parquet> 1176 </Parquet> 1177 </InputSerialization> 1178 <OutputSerialization> 1179 <CSV> 1180 </CSV> 1181 </OutputSerialization> 1182 <RequestProgress> 1183 <Enabled>FALSE</Enabled> 1184 </RequestProgress> 1185 </SelectObjectContentRequest> 1186 `), []byte{ 1187 0, 0, 0, 137, 0, 0, 0, 85, 194, 213, 168, 241, 13, 58, 109, 101, 115, 115, 97, 103, 101, 45, 116, 121, 112, 101, 7, 0, 5, 101, 118, 101, 110, 116, 13, 58, 99, 111, 110, 116, 101, 110, 116, 45, 116, 121, 112, 101, 7, 0, 24, 97, 112, 112, 108, 105, 99, 97, 116, 105, 111, 110, 47, 111, 99, 116, 101, 116, 45, 115, 116, 114, 101, 97, 109, 11, 58, 101, 118, 101, 110, 116, 45, 116, 121, 112, 101, 7, 0, 7, 82, 101, 99, 111, 114, 100, 115, 45, 49, 44, 102, 111, 111, 44, 116, 114, 117, 101, 10, 44, 98, 97, 114, 44, 102, 97, 108, 115, 101, 10, 50, 46, 53, 44, 98, 97, 122, 44, 116, 114, 117, 101, 10, 75, 182, 193, 80, 0, 0, 0, 235, 0, 0, 0, 67, 213, 243, 57, 141, 13, 58, 109, 101, 115, 115, 97, 103, 101, 45, 116, 121, 112, 101, 7, 0, 5, 101, 118, 101, 110, 116, 13, 58, 99, 111, 110, 116, 101, 110, 116, 45, 116, 121, 112, 101, 7, 0, 8, 116, 101, 120, 116, 47, 120, 109, 108, 11, 58, 101, 118, 101, 110, 116, 45, 116, 121, 112, 101, 7, 0, 5, 83, 116, 97, 116, 115, 60, 63, 120, 109, 108, 32, 118, 101, 114, 115, 105, 111, 110, 61, 34, 49, 46, 48, 34, 32, 101, 110, 99, 111, 100, 105, 110, 103, 61, 34, 85, 84, 70, 45, 56, 34, 63, 62, 60, 83, 116, 97, 116, 115, 62, 60, 66, 121, 116, 101, 115, 83, 99, 97, 110, 110, 101, 100, 62, 45, 49, 60, 47, 66, 121, 116, 101, 115, 83, 99, 97, 110, 110, 101, 100, 62, 60, 66, 121, 116, 101, 115, 80, 114, 111, 99, 101, 115, 115, 101, 100, 62, 45, 49, 60, 47, 66, 121, 116, 101, 115, 80, 114, 111, 99, 101, 115, 115, 101, 100, 62, 60, 66, 121, 116, 101, 115, 82, 101, 116, 117, 114, 110, 101, 100, 62, 51, 54, 60, 47, 66, 121, 116, 101, 115, 82, 101, 116, 117, 114, 110, 101, 100, 62, 60, 47, 83, 116, 97, 116, 115, 62, 128, 96, 253, 66, 0, 0, 0, 56, 0, 0, 0, 40, 193, 198, 132, 212, 13, 58, 109, 101, 115, 115, 97, 103, 101, 45, 116, 121, 112, 101, 7, 0, 5, 101, 118, 101, 110, 116, 11, 58, 101, 118, 101, 110, 116, 45, 116, 121, 112, 101, 7, 0, 3, 69, 110, 100, 207, 151, 211, 146, 1188 }, 1189 }, 1190 { 1191 []byte(` 1192 <?xml version="1.0" encoding="UTF-8"?> 1193 <SelectObjectContentRequest> 1194 <Expression>SELECT COUNT(*) AS total_record_count from S3Object</Expression> 1195 <ExpressionType>SQL</ExpressionType> 1196 <InputSerialization> 1197 <CompressionType>NONE</CompressionType> 1198 <Parquet> 1199 </Parquet> 1200 </InputSerialization> 1201 <OutputSerialization> 1202 <CSV> 1203 </CSV> 1204 </OutputSerialization> 1205 <RequestProgress> 1206 <Enabled>FALSE</Enabled> 1207 </RequestProgress> 1208 </SelectObjectContentRequest> 1209 `), []byte{ 1210 0, 0, 0, 103, 0, 0, 0, 85, 85, 49, 209, 79, 13, 58, 109, 101, 115, 115, 97, 103, 101, 45, 116, 121, 112, 101, 7, 0, 5, 101, 118, 101, 110, 116, 13, 58, 99, 111, 110, 116, 101, 110, 116, 45, 116, 121, 112, 101, 7, 0, 24, 97, 112, 112, 108, 105, 99, 97, 116, 105, 111, 110, 47, 111, 99, 116, 101, 116, 45, 115, 116, 114, 101, 97, 109, 11, 58, 101, 118, 101, 110, 116, 45, 116, 121, 112, 101, 7, 0, 7, 82, 101, 99, 111, 114, 100, 115, 51, 10, 175, 58, 213, 152, 0, 0, 0, 234, 0, 0, 0, 67, 232, 147, 16, 61, 13, 58, 109, 101, 115, 115, 97, 103, 101, 45, 116, 121, 112, 101, 7, 0, 5, 101, 118, 101, 110, 116, 13, 58, 99, 111, 110, 116, 101, 110, 116, 45, 116, 121, 112, 101, 7, 0, 8, 116, 101, 120, 116, 47, 120, 109, 108, 11, 58, 101, 118, 101, 110, 116, 45, 116, 121, 112, 101, 7, 0, 5, 83, 116, 97, 116, 115, 60, 63, 120, 109, 108, 32, 118, 101, 114, 115, 105, 111, 110, 61, 34, 49, 46, 48, 34, 32, 101, 110, 99, 111, 100, 105, 110, 103, 61, 34, 85, 84, 70, 45, 56, 34, 63, 62, 60, 83, 116, 97, 116, 115, 62, 60, 66, 121, 116, 101, 115, 83, 99, 97, 110, 110, 101, 100, 62, 45, 49, 60, 47, 66, 121, 116, 101, 115, 83, 99, 97, 110, 110, 101, 100, 62, 60, 66, 121, 116, 101, 115, 80, 114, 111, 99, 101, 115, 115, 101, 100, 62, 45, 49, 60, 47, 66, 121, 116, 101, 115, 80, 114, 111, 99, 101, 115, 115, 101, 100, 62, 60, 66, 121, 116, 101, 115, 82, 101, 116, 117, 114, 110, 101, 100, 62, 50, 60, 47, 66, 121, 116, 101, 115, 82, 101, 116, 117, 114, 110, 101, 100, 62, 60, 47, 83, 116, 97, 116, 115, 62, 190, 146, 162, 21, 0, 0, 0, 56, 0, 0, 0, 40, 193, 198, 132, 212, 13, 58, 109, 101, 115, 115, 97, 103, 101, 45, 116, 121, 112, 101, 7, 0, 5, 101, 118, 101, 110, 116, 11, 58, 101, 118, 101, 110, 116, 45, 116, 121, 112, 101, 7, 0, 3, 69, 110, 100, 207, 151, 211, 146, 1211 }, 1212 }, 1213 } 1214 1215 for i, testCase := range testTable { 1216 t.Run(fmt.Sprint(i), func(t *testing.T) { 1217 getReader := func(offset int64, length int64) (io.ReadCloser, error) { 1218 testdataFile := "testdata/testdata.parquet" 1219 file, err := os.Open(testdataFile) 1220 if err != nil { 1221 return nil, err 1222 } 1223 1224 fi, err := file.Stat() 1225 if err != nil { 1226 return nil, err 1227 } 1228 1229 if offset < 0 { 1230 offset = fi.Size() + offset 1231 } 1232 1233 if _, err = file.Seek(offset, io.SeekStart); err != nil { 1234 return nil, err 1235 } 1236 1237 return file, nil 1238 } 1239 1240 s3Select, err := NewS3Select(bytes.NewReader(testCase.requestXML)) 1241 if err != nil { 1242 t.Fatal(err) 1243 } 1244 1245 if err = s3Select.Open(getReader); err != nil { 1246 t.Fatal(err) 1247 } 1248 1249 w := &testResponseWriter{} 1250 s3Select.Evaluate(w) 1251 s3Select.Close() 1252 1253 if !reflect.DeepEqual(w.response, testCase.expectedResult) { 1254 resp := http.Response{ 1255 StatusCode: http.StatusOK, 1256 Body: ioutil.NopCloser(bytes.NewReader(w.response)), 1257 ContentLength: int64(len(w.response)), 1258 } 1259 res, err := minio.NewSelectResults(&resp, "testbucket") 1260 if err != nil { 1261 t.Error(err) 1262 return 1263 } 1264 got, err := ioutil.ReadAll(res) 1265 if err != nil { 1266 t.Error(err) 1267 return 1268 } 1269 1270 t.Errorf("received response does not match with expected reply\ngot: %#v\nwant:%#v\ndecoded:%s", w.response, testCase.expectedResult, string(got)) 1271 } 1272 }) 1273 } 1274 } 1275 1276 func TestParquetInputSchema(t *testing.T) { 1277 os.Setenv("MINIO_API_SELECT_PARQUET", "on") 1278 defer os.Setenv("MINIO_API_SELECT_PARQUET", "off") 1279 1280 var testTable = []struct { 1281 requestXML []byte 1282 wantResult string 1283 }{ 1284 { 1285 requestXML: []byte(` 1286 <?xml version="1.0" encoding="UTF-8"?> 1287 <SelectObjectContentRequest> 1288 <Expression>SELECT * FROM S3Object LIMIT 5</Expression> 1289 <ExpressionType>SQL</ExpressionType> 1290 <InputSerialization> 1291 <CompressionType>NONE</CompressionType> 1292 <Parquet> 1293 </Parquet> 1294 </InputSerialization> 1295 <OutputSerialization> 1296 <JSON> 1297 </JSON> 1298 </OutputSerialization> 1299 <RequestProgress> 1300 <Enabled>FALSE</Enabled> 1301 </RequestProgress> 1302 </SelectObjectContentRequest> 1303 `), wantResult: `{"shipdate":"1996-03-13T"} 1304 {"shipdate":"1996-04-12T"} 1305 {"shipdate":"1996-01-29T"} 1306 {"shipdate":"1996-04-21T"} 1307 {"shipdate":"1996-03-30T"}`, 1308 }, 1309 { 1310 requestXML: []byte(` 1311 <?xml version="1.0" encoding="UTF-8"?> 1312 <SelectObjectContentRequest> 1313 <Expression>SELECT DATE_ADD(day, 2, shipdate) as shipdate FROM S3Object LIMIT 5</Expression> 1314 <ExpressionType>SQL</ExpressionType> 1315 <InputSerialization> 1316 <CompressionType>NONE</CompressionType> 1317 <Parquet> 1318 </Parquet> 1319 </InputSerialization> 1320 <OutputSerialization> 1321 <JSON> 1322 </JSON> 1323 </OutputSerialization> 1324 <RequestProgress> 1325 <Enabled>FALSE</Enabled> 1326 </RequestProgress> 1327 </SelectObjectContentRequest> 1328 `), wantResult: `{"shipdate":"1996-03-15T"} 1329 {"shipdate":"1996-04-14T"} 1330 {"shipdate":"1996-01-31T"} 1331 {"shipdate":"1996-04-23T"} 1332 {"shipdate":"1996-04T"}`, 1333 }, 1334 } 1335 1336 for i, testCase := range testTable { 1337 t.Run(fmt.Sprint(i), func(t *testing.T) { 1338 getReader := func(offset int64, length int64) (io.ReadCloser, error) { 1339 testdataFile := "testdata/lineitem_shipdate.parquet" 1340 file, err := os.Open(testdataFile) 1341 if err != nil { 1342 return nil, err 1343 } 1344 1345 fi, err := file.Stat() 1346 if err != nil { 1347 return nil, err 1348 } 1349 1350 if offset < 0 { 1351 offset = fi.Size() + offset 1352 } 1353 1354 if _, err = file.Seek(offset, io.SeekStart); err != nil { 1355 return nil, err 1356 } 1357 1358 return file, nil 1359 } 1360 1361 s3Select, err := NewS3Select(bytes.NewReader(testCase.requestXML)) 1362 if err != nil { 1363 t.Fatal(err) 1364 } 1365 1366 if err = s3Select.Open(getReader); err != nil { 1367 t.Fatal(err) 1368 } 1369 1370 w := &testResponseWriter{} 1371 s3Select.Evaluate(w) 1372 s3Select.Close() 1373 resp := http.Response{ 1374 StatusCode: http.StatusOK, 1375 Body: ioutil.NopCloser(bytes.NewReader(w.response)), 1376 ContentLength: int64(len(w.response)), 1377 } 1378 res, err := minio.NewSelectResults(&resp, "testbucket") 1379 if err != nil { 1380 t.Error(err) 1381 return 1382 } 1383 got, err := ioutil.ReadAll(res) 1384 if err != nil { 1385 t.Error(err) 1386 return 1387 } 1388 gotS := strings.TrimSpace(string(got)) 1389 if !reflect.DeepEqual(gotS, testCase.wantResult) { 1390 t.Errorf("received response does not match with expected reply. Query: %s\ngot: %s\nwant:%s", testCase.requestXML, gotS, testCase.wantResult) 1391 } 1392 1393 }) 1394 } 1395 } 1396 1397 func TestParquetInputSchemaCSV(t *testing.T) { 1398 os.Setenv("MINIO_API_SELECT_PARQUET", "on") 1399 defer os.Setenv("MINIO_API_SELECT_PARQUET", "off") 1400 1401 var testTable = []struct { 1402 requestXML []byte 1403 wantResult string 1404 }{ 1405 { 1406 requestXML: []byte(` 1407 <?xml version="1.0" encoding="UTF-8"?> 1408 <SelectObjectContentRequest> 1409 <Expression>SELECT * FROM S3Object LIMIT 5</Expression> 1410 <ExpressionType>SQL</ExpressionType> 1411 <InputSerialization> 1412 <CompressionType>NONE</CompressionType> 1413 <Parquet> 1414 </Parquet> 1415 </InputSerialization> 1416 <OutputSerialization> 1417 <CSV/> 1418 </OutputSerialization> 1419 <RequestProgress> 1420 <Enabled>FALSE</Enabled> 1421 </RequestProgress> 1422 </SelectObjectContentRequest> 1423 `), wantResult: `1996-03-13T 1424 1996-04-12T 1425 1996-01-29T 1426 1996-04-21T 1427 1996-03-30T`, 1428 }, 1429 { 1430 requestXML: []byte(` 1431 <?xml version="1.0" encoding="UTF-8"?> 1432 <SelectObjectContentRequest> 1433 <Expression>SELECT DATE_ADD(day, 2, shipdate) as shipdate FROM S3Object LIMIT 5</Expression> 1434 <ExpressionType>SQL</ExpressionType> 1435 <InputSerialization> 1436 <CompressionType>NONE</CompressionType> 1437 <Parquet> 1438 </Parquet> 1439 </InputSerialization> 1440 <OutputSerialization> 1441 <CSV/> 1442 </OutputSerialization> 1443 <RequestProgress> 1444 <Enabled>FALSE</Enabled> 1445 </RequestProgress> 1446 </SelectObjectContentRequest> 1447 `), wantResult: `1996-03-15T 1448 1996-04-14T 1449 1996-01-31T 1450 1996-04-23T 1451 1996-04T`, 1452 }, 1453 } 1454 1455 for i, testCase := range testTable { 1456 t.Run(fmt.Sprint(i), func(t *testing.T) { 1457 getReader := func(offset int64, length int64) (io.ReadCloser, error) { 1458 testdataFile := "testdata/lineitem_shipdate.parquet" 1459 file, err := os.Open(testdataFile) 1460 if err != nil { 1461 return nil, err 1462 } 1463 1464 fi, err := file.Stat() 1465 if err != nil { 1466 return nil, err 1467 } 1468 1469 if offset < 0 { 1470 offset = fi.Size() + offset 1471 } 1472 1473 if _, err = file.Seek(offset, io.SeekStart); err != nil { 1474 return nil, err 1475 } 1476 1477 return file, nil 1478 } 1479 1480 s3Select, err := NewS3Select(bytes.NewReader(testCase.requestXML)) 1481 if err != nil { 1482 t.Fatal(err) 1483 } 1484 1485 if err = s3Select.Open(getReader); err != nil { 1486 t.Fatal(err) 1487 } 1488 1489 w := &testResponseWriter{} 1490 s3Select.Evaluate(w) 1491 s3Select.Close() 1492 resp := http.Response{ 1493 StatusCode: http.StatusOK, 1494 Body: ioutil.NopCloser(bytes.NewReader(w.response)), 1495 ContentLength: int64(len(w.response)), 1496 } 1497 res, err := minio.NewSelectResults(&resp, "testbucket") 1498 if err != nil { 1499 t.Error(err) 1500 return 1501 } 1502 got, err := ioutil.ReadAll(res) 1503 if err != nil { 1504 t.Error(err) 1505 return 1506 } 1507 gotS := strings.TrimSpace(string(got)) 1508 if !reflect.DeepEqual(gotS, testCase.wantResult) { 1509 t.Errorf("received response does not match with expected reply. Query: %s\ngot: %s\nwant:%s", testCase.requestXML, gotS, testCase.wantResult) 1510 } 1511 1512 }) 1513 } 1514 }