github.com/minio/minio@v0.0.0-20240328213742-3f72439b8a27/internal/s3select/select_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 s3select 19 20 import ( 21 "bytes" 22 "encoding/xml" 23 "fmt" 24 "io" 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 func newStringRSC(s string) io.ReadSeekCloser { 37 return newBytesRSC([]byte(s)) 38 } 39 40 func newBytesRSC(b []byte) io.ReadSeekCloser { 41 r := bytes.NewReader(b) 42 segmentReader := func(offset int64) (io.ReadCloser, error) { 43 _, err := r.Seek(offset, io.SeekStart) 44 if err != nil { 45 return nil, err 46 } 47 return io.NopCloser(r), nil 48 } 49 return NewObjectReadSeekCloser(segmentReader, int64(len(b))) 50 } 51 52 type testResponseWriter struct { 53 statusCode int 54 response []byte 55 } 56 57 func (w *testResponseWriter) Header() http.Header { 58 return nil 59 } 60 61 func (w *testResponseWriter) Write(p []byte) (int, error) { 62 w.response = append(w.response, p...) 63 return len(p), nil 64 } 65 66 func (w *testResponseWriter) WriteHeader(statusCode int) { 67 w.statusCode = statusCode 68 } 69 70 func (w *testResponseWriter) Flush() { 71 } 72 73 func TestJSONQueries(t *testing.T) { 74 input := `{"id": 0,"title": "Test Record","desc": "Some text","synonyms": ["foo", "bar", "whatever"]} 75 {"id": 1,"title": "Second Record","desc": "another text","synonyms": ["some", "synonym", "value"]} 76 {"id": 2,"title": "Second Record","desc": "another text","numbers": [2, 3.0, 4]} 77 {"id": 3,"title": "Second Record","desc": "another text","nested": [[2, 3.0, 4], [7, 8.5, 9]]}` 78 79 testTable := []struct { 80 name string 81 query string 82 requestXML []byte // override request XML 83 wantResult string 84 withJSON string // Override JSON input 85 }{ 86 { 87 name: "select-in-array-full", 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: "simple-in-array", 93 query: `SELECT * from s3object s WHERE s.id IN (1,3)`, 94 wantResult: `{"id":1,"title":"Second Record","desc":"another text","synonyms":["some","synonym","value"]} 95 {"id":3,"title":"Second Record","desc":"another text","nested":[[2,3,4],[7,8.5,9]]}`, 96 }, 97 { 98 name: "select-in-array-single", 99 query: `SELECT synonyms from s3object s WHERE 'bar' IN s.synonyms[*] `, 100 wantResult: `{"synonyms":["foo","bar","whatever"]}`, 101 }, 102 { 103 name: "donatello-1", 104 query: `SELECT * from s3object s WHERE 'bar' in s.synonyms`, 105 wantResult: `{"id":0,"title":"Test Record","desc":"Some text","synonyms":["foo","bar","whatever"]}`, 106 }, 107 { 108 name: "donatello-2", 109 query: `SELECT * from s3object s WHERE 'bar' in s.synonyms[*]`, 110 wantResult: `{"id":0,"title":"Test Record","desc":"Some text","synonyms":["foo","bar","whatever"]}`, 111 }, 112 { 113 name: "in-expression-precedence-1", 114 query: "select * from s3object s where 'bar' in s.synonyms and s.id = 0", 115 wantResult: `{"id":0,"title":"Test Record","desc":"Some text","synonyms":["foo","bar","whatever"]}`, 116 }, 117 { 118 name: "in-expression-precedence-2", 119 query: "select * from s3object s where 'some' in ('somex', s.synonyms[0]) and s.id = 1", 120 wantResult: `{"id":1,"title":"Second Record","desc":"another text","synonyms":["some","synonym","value"]}`, 121 }, 122 { 123 name: "in-expression-with-aggregation", 124 query: "select SUM(s.id) from s3object s Where 2 in s.numbers[*] or 'some' in s.synonyms[*]", 125 wantResult: `{"_1":3}`, 126 }, 127 { 128 name: "bignum-1", 129 query: `SELECT id from s3object s WHERE s.id <= 9223372036854775807`, 130 wantResult: `{"id":0} 131 {"id":1} 132 {"id":2} 133 {"id":3}`, 134 }, 135 { 136 name: "bignum-2", 137 query: `SELECT id from s3object s WHERE s.id >= -9223372036854775808`, 138 wantResult: `{"id":0} 139 {"id":1} 140 {"id":2} 141 {"id":3}`, 142 }, 143 { 144 name: "donatello-3", 145 query: `SELECT * from s3object s WHERE 'value' IN s.synonyms[*]`, 146 wantResult: `{"id":1,"title":"Second Record","desc":"another text","synonyms":["some","synonym","value"]}`, 147 }, 148 { 149 name: "select-in-number", 150 query: `SELECT * from s3object s WHERE 4 in s.numbers[*]`, 151 wantResult: `{"id":2,"title":"Second Record","desc":"another text","numbers":[2,3,4]}`, 152 }, 153 { 154 name: "select-in-number-float", 155 query: `SELECT * from s3object s WHERE 3 in s.numbers[*]`, 156 wantResult: `{"id":2,"title":"Second Record","desc":"another text","numbers":[2,3,4]}`, 157 }, 158 { 159 name: "select-in-number-float-in-sql", 160 query: `SELECT * from s3object s WHERE 3.0 in s.numbers[*]`, 161 wantResult: `{"id":2,"title":"Second Record","desc":"another text","numbers":[2,3,4]}`, 162 }, 163 { 164 name: "select-in-list-match", 165 query: `SELECT * from s3object s WHERE (2,3,4) IN s.nested[*]`, 166 wantResult: `{"id":3,"title":"Second Record","desc":"another text","nested":[[2,3,4],[7,8.5,9]]}`, 167 }, 168 { 169 name: "select-in-nested-float", 170 query: `SELECT s.nested from s3object s WHERE 8.5 IN s.nested[*][*]`, 171 wantResult: `{"nested":[[2,3,4],[7,8.5,9]]}`, 172 }, 173 { 174 name: "select-in-combine-and", 175 query: `SELECT s.nested from s3object s WHERE (8.5 IN s.nested[*][*]) AND (s.id > 0)`, 176 wantResult: `{"nested":[[2,3,4],[7,8.5,9]]}`, 177 }, 178 { 179 name: "select-in-combine-and-no", 180 query: `SELECT s.nested from s3object s WHERE (8.5 IN s.nested[*][*]) AND (s.id = 0)`, 181 wantResult: ``, 182 }, 183 { 184 name: "select-in-nested-float-no-flat", 185 query: `SELECT s.nested from s3object s WHERE 8.5 IN s.nested[*]`, 186 wantResult: ``, 187 }, 188 { 189 name: "select-empty-field-result", 190 query: `SELECT * from s3object s WHERE s.nested[0][0] = 2`, 191 wantResult: `{"id":3,"title":"Second Record","desc":"another text","nested":[[2,3,4],[7,8.5,9]]}`, 192 }, 193 { 194 name: "select-arrays-specific", 195 query: `SELECT * from s3object s WHERE s.nested[1][0] = 7`, 196 wantResult: `{"id":3,"title":"Second Record","desc":"another text","nested":[[2,3,4],[7,8.5,9]]}`, 197 }, 198 { 199 name: "wrong-index-no-result", 200 query: `SELECT * from s3object s WHERE s.nested[0][0] = 7`, 201 wantResult: ``, 202 }, 203 { 204 name: "not-equal-result", 205 query: `SELECT * from s3object s WHERE s.nested[1][0] != 7`, 206 wantResult: `{"id":0,"title":"Test Record","desc":"Some text","synonyms":["foo","bar","whatever"]} 207 {"id":1,"title":"Second Record","desc":"another text","synonyms":["some","synonym","value"]} 208 {"id":2,"title":"Second Record","desc":"another text","numbers":[2,3,4]}`, 209 }, 210 { 211 name: "indexed-list-match", 212 query: `SELECT * from s3object s WHERE (7,8.5,9) IN s.nested[1]`, 213 wantResult: ``, 214 }, 215 { 216 name: "indexed-list-match-equals", 217 query: `SELECT * from s3object s WHERE (7,8.5,9) = s.nested[1]`, 218 wantResult: `{"id":3,"title":"Second Record","desc":"another text","nested":[[2,3,4],[7,8.5,9]]}`, 219 }, 220 { 221 name: "indexed-list-match-equals-s-star", 222 query: `SELECT s.* from s3object s WHERE (7,8.5,9) = 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: "indexed-list-match-equals-s-index", 227 query: `SELECT s.nested[1], s.nested[0] from s3object s WHERE (7,8.5,9) = s.nested[1]`, 228 wantResult: `{"_1":[7,8.5,9],"_2":[2,3,4]}`, 229 }, 230 { 231 name: "indexed-list-match-not-equals", 232 query: `SELECT * from s3object s WHERE (7,8.5,9) != s.nested[1]`, 233 wantResult: `{"id":0,"title":"Test Record","desc":"Some text","synonyms":["foo","bar","whatever"]} 234 {"id":1,"title":"Second Record","desc":"another text","synonyms":["some","synonym","value"]} 235 {"id":2,"title":"Second Record","desc":"another text","numbers":[2,3,4]}`, 236 }, 237 { 238 name: "indexed-list-square-bracket", 239 query: `SELECT * from s3object s WHERE [7,8.5,9] = s.nested[1]`, 240 wantResult: `{"id":3,"title":"Second Record","desc":"another text","nested":[[2,3,4],[7,8.5,9]]}`, 241 }, 242 { 243 name: "indexed-list-square-bracket", 244 query: `SELECT * from s3object s WHERE [7,8.5,9] IN s.nested`, 245 wantResult: `{"id":3,"title":"Second Record","desc":"another text","nested":[[2,3,4],[7,8.5,9]]}`, 246 }, 247 { 248 name: "indexed-list-square-bracket", 249 query: `SELECT * from s3object s WHERE id IN [3,2]`, 250 wantResult: `{"id":2,"title":"Second Record","desc":"another text","numbers":[2,3,4]} 251 {"id":3,"title":"Second Record","desc":"another text","nested":[[2,3,4],[7,8.5,9]]}`, 252 }, 253 { 254 name: "index-wildcard-in", 255 query: `SELECT * from s3object s WHERE (8.5) IN s.nested[1][*]`, 256 wantResult: `{"id":3,"title":"Second Record","desc":"another text","nested":[[2,3,4],[7,8.5,9]]}`, 257 }, 258 { 259 name: "index-wildcard-in", 260 query: `SELECT * from s3object s WHERE (8.0+0.5) IN s.nested[1][*]`, 261 wantResult: `{"id":3,"title":"Second Record","desc":"another text","nested":[[2,3,4],[7,8.5,9]]}`, 262 }, 263 { 264 name: "compare-mixed", 265 query: `SELECT id from s3object s WHERE value = true`, 266 wantResult: `{"id":1}`, 267 withJSON: `{"id":0, "value": false} 268 {"id":1, "value": true} 269 {"id":2, "value": 42} 270 {"id":3, "value": "true"} 271 `, 272 }, 273 { 274 name: "compare-mixed-not", 275 query: `SELECT COUNT(id) as n from s3object s WHERE value != true`, 276 wantResult: `{"n":3}`, 277 withJSON: `{"id":0, "value": false} 278 {"id":1, "value": true} 279 {"id":2, "value": 42} 280 {"id":3, "value": "true"} 281 `, 282 }, 283 { 284 name: "index-wildcard-in", 285 query: `SELECT * from s3object s WHERE title = 'Test Record'`, 286 wantResult: `{"id":0,"title":"Test Record","desc":"Some text","synonyms":["foo","bar","whatever"]}`, 287 }, 288 { 289 name: "select-output-field-as-csv", 290 requestXML: []byte(`<?xml version="1.0" encoding="UTF-8"?> 291 <SelectObjectContentRequest> 292 <Expression>SELECT s.synonyms from s3object s WHERE 'whatever' IN s.synonyms</Expression> 293 <ExpressionType>SQL</ExpressionType> 294 <InputSerialization> 295 <CompressionType>NONE</CompressionType> 296 <JSON> 297 <Type>DOCUMENT</Type> 298 </JSON> 299 </InputSerialization> 300 <OutputSerialization> 301 <CSV> 302 <QuoteCharacter>"</QuoteCharacter> 303 </CSV> 304 </OutputSerialization> 305 <RequestProgress> 306 <Enabled>FALSE</Enabled> 307 </RequestProgress> 308 </SelectObjectContentRequest>`), 309 wantResult: `"[""foo"",""bar"",""whatever""]"`, 310 }, 311 { 312 name: "document", 313 query: "", 314 requestXML: []byte(` 315 <?xml version="1.0" encoding="UTF-8"?> 316 <SelectObjectContentRequest> 317 <Expression>select * from s3object[*].elements[*] s where s.element_type = '__elem__merfu'</Expression> 318 <ExpressionType>SQL</ExpressionType> 319 <InputSerialization> 320 <CompressionType>NONE</CompressionType> 321 <JSON> 322 <Type>DOCUMENT</Type> 323 </JSON> 324 </InputSerialization> 325 <OutputSerialization> 326 <JSON> 327 </JSON> 328 </OutputSerialization> 329 <RequestProgress> 330 <Enabled>FALSE</Enabled> 331 </RequestProgress> 332 </SelectObjectContentRequest>`), 333 withJSON: ` 334 { 335 "name": "small_pdf1.pdf", 336 "lume_id": "9507193e-572d-4f95-bcf1-e9226d96be65", 337 "elements": [ 338 { 339 "element_type": "__elem__image", 340 "element_id": "859d09c4-7cf1-4a37-9674-3a7de8b56abc", 341 "attributes": { 342 "__attr__image_dpi": 300, 343 "__attr__image_size": [ 344 2550, 345 3299 346 ], 347 "__attr__image_index": 1, 348 "__attr__image_format": "JPEG", 349 "__attr__file_extension": "jpg", 350 "__attr__data": null 351 } 352 }, 353 { 354 "element_type": "__elem__merfu", 355 "element_id": "d868aefe-ef9a-4be2-b9b2-c9fd89cc43eb", 356 "attributes": { 357 "__attr__image_dpi": 300, 358 "__attr__image_size": [ 359 2550, 360 3299 361 ], 362 "__attr__image_index": 2, 363 "__attr__image_format": "JPEG", 364 "__attr__file_extension": "jpg", 365 "__attr__data": null 366 } 367 } 368 ], 369 "data": "asdascasdc1234e123erdasdas" 370 }`, 371 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}}`, 372 }, 373 { 374 name: "date_diff_month", 375 query: `SELECT date_diff(MONTH, '2019-10-20T', '2020-01-20T') FROM S3Object LIMIT 1`, 376 wantResult: `{"_1":3}`, 377 }, 378 { 379 name: "date_diff_month_neg", 380 query: `SELECT date_diff(MONTH, '2020-01-20T', '2019-10-20T') FROM S3Object LIMIT 1`, 381 wantResult: `{"_1":-3}`, 382 }, 383 // Examples from https://docs.aws.amazon.com/AmazonS3/latest/userguide/s3-glacier-select-sql-reference-date.html#s3-glacier-select-sql-reference-date-diff 384 { 385 name: "date_diff_year", 386 query: `SELECT date_diff(year, '2010-01-01T', '2011-01-01T') FROM S3Object LIMIT 1`, 387 wantResult: `{"_1":1}`, 388 }, 389 { 390 name: "date_diff_year", 391 query: `SELECT date_diff(month, '2010-01-01T', '2010-05T') FROM S3Object LIMIT 1`, 392 wantResult: `{"_1":4}`, 393 }, 394 { 395 name: "date_diff_month_oney", 396 query: `SELECT date_diff(month, '2010T', '2011T') FROM S3Object LIMIT 1`, 397 wantResult: `{"_1":12}`, 398 }, 399 { 400 name: "date_diff_month_neg", 401 query: `SELECT date_diff(month, '2011T', '2010T') FROM S3Object LIMIT 1`, 402 wantResult: `{"_1":-12}`, 403 }, 404 { 405 name: "date_diff_days", 406 query: `SELECT date_diff(day, '2010-01-01T23:00:00Z', '2010-01-02T01:00:00Z') FROM S3Object LIMIT 1`, 407 wantResult: `{"_1":0}`, 408 }, 409 { 410 name: "date_diff_days_one", 411 query: `SELECT date_diff(day, '2010-01-01T23:00:00Z', '2010-01-02T23:00:00Z') FROM S3Object LIMIT 1`, 412 wantResult: `{"_1":1}`, 413 }, 414 { 415 name: "cast_from_int_to_float", 416 query: `SELECT cast(1 as float) FROM S3Object LIMIT 1`, 417 wantResult: `{"_1":1}`, 418 }, 419 { 420 name: "cast_from_float_to_float", 421 query: `SELECT cast(1.0 as float) FROM S3Object LIMIT 1`, 422 wantResult: `{"_1":1}`, 423 }, 424 { 425 name: "arithmetic_integer_operand", 426 query: `SELECT 1 / 2 FROM S3Object LIMIT 1`, 427 wantResult: `{"_1":0}`, 428 }, 429 { 430 name: "arithmetic_float_operand", 431 query: `SELECT 1.0 / 2.0 * .3 FROM S3Object LIMIT 1`, 432 wantResult: `{"_1":0.15}`, 433 }, 434 { 435 name: "arithmetic_integer_float_operand", 436 query: `SELECT 3.0 / 2, 5 / 2.0 FROM S3Object LIMIT 1`, 437 wantResult: `{"_1":1.5,"_2":2.5}`, 438 }, 439 { 440 name: "limit-1", 441 query: `SELECT * FROM S3Object[*].elements[*] LIMIT 1`, 442 requestXML: []byte(` 443 <?xml version="1.0" encoding="UTF-8"?> 444 <SelectObjectContentRequest> 445 <Expression>select * from s3object[*].elements[*] s where s.element_type = '__elem__merfu'</Expression> 446 <ExpressionType>SQL</ExpressionType> 447 <InputSerialization> 448 <CompressionType>NONE</CompressionType> 449 <JSON> 450 <Type>DOCUMENT</Type> 451 </JSON> 452 </InputSerialization> 453 <OutputSerialization> 454 <JSON> 455 </JSON> 456 </OutputSerialization> 457 <RequestProgress> 458 <Enabled>FALSE</Enabled> 459 </RequestProgress> 460 </SelectObjectContentRequest>`), 461 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}}`, 462 withJSON: ` 463 { 464 "name": "small_pdf1.pdf", 465 "lume_id": "9507193e-572d-4f95-bcf1-e9226d96be65", 466 "elements": [ 467 { 468 "element_type": "__elem__image", 469 "element_id": "859d09c4-7cf1-4a37-9674-3a7de8b56abc", 470 "attributes": { 471 "__attr__image_dpi": 300, 472 "__attr__image_size": [ 473 2550, 474 3299 475 ], 476 "__attr__image_index": 1, 477 "__attr__image_format": "JPEG", 478 "__attr__file_extension": "jpg", 479 "__attr__data": null 480 } 481 }, 482 { 483 "element_type": "__elem__merfu", 484 "element_id": "d868aefe-ef9a-4be2-b9b2-c9fd89cc43eb", 485 "attributes": { 486 "__attr__image_dpi": 300, 487 "__attr__image_size": [ 488 2550, 489 3299 490 ], 491 "__attr__image_index": 2, 492 "__attr__image_format": "JPEG", 493 "__attr__file_extension": "jpg", 494 "__attr__data": null 495 } 496 } 497 ], 498 "data": "asdascasdc1234e123erdasdas" 499 }`, 500 }, 501 { 502 name: "limit-2", 503 query: `select * from s3object[*].person[*] limit 1`, 504 wantResult: `{"Id":1,"Name":"Anshu","Address":"Templestowe","Car":"Jeep"}`, 505 withJSON: `{ "person": [ { "Id": 1, "Name": "Anshu", "Address": "Templestowe", "Car": "Jeep" }, { "Id": 2, "Name": "Ben Mostafa", "Address": "Las Vegas", "Car": "Mustang" }, { "Id": 3, "Name": "Rohan Wood", "Address": "Wooddon", "Car": "VW" } ] }`, 506 }, 507 { 508 name: "lower-case-is", 509 query: `select * from s3object[*] as s where s.request.header['User-Agent'] is not null`, 510 wantResult: `{"request":{"uri":"/1","header":{"User-Agent":"test"}}}`, 511 withJSON: `{"request":{"uri":"/1","header":{"User-Agent":"test"}}} 512 {"request":{"uri":"/2","header":{}}}`, 513 }, 514 { 515 name: "is-not-missing", 516 query: `select * from s3object[*] as s where s.request.header['User-Agent'] is not missing`, 517 wantResult: `{"request":{"uri":"/1","header":{"User-Agent":"test"}}}`, 518 withJSON: `{"request":{"uri":"/1","header":{"User-Agent":"test"}}} 519 {"request":{"uri":"/2","header":{}}}`, 520 }, 521 { 522 name: "is-not-missing-2", 523 query: `select * from s3object[*] as s where s.request.header is not missing`, 524 wantResult: `{"request":{"uri":"/1","header":{"User-Agent":"test"}}} 525 {"request":{"uri":"/2","header":{}}}`, 526 withJSON: `{"request":{"uri":"/1","header":{"User-Agent":"test"}}} 527 {"request":{"uri":"/2","header":{}}}`, 528 }, 529 { 530 name: "is-not-missing-null", 531 query: `select * from s3object[*] as s where s.request.header is not missing`, 532 wantResult: `{"request":{"uri":"/1","header":{"User-Agent":"test"}}} 533 {"request":{"uri":"/2","header":null}}`, 534 withJSON: `{"request":{"uri":"/1","header":{"User-Agent":"test"}}} 535 {"request":{"uri":"/2","header":null}}`, 536 }, 537 { 538 name: "is-not-missing", 539 query: `select * from s3object[*] as s where s.request.header['User-Agent'] is not missing`, 540 wantResult: `{"request":{"uri":"/1","header":{"User-Agent":"test"}}}`, 541 withJSON: `{"request":{"uri":"/1","header":{"User-Agent":"test"}}} 542 {"request":{"uri":"/2","header":{}}}`, 543 }, 544 { 545 name: "is-not-missing-2", 546 query: `select * from s3object[*] as s where s.request.header is not missing`, 547 wantResult: `{"request":{"uri":"/1","header":{"User-Agent":"test"}}} 548 {"request":{"uri":"/2","header":{}}}`, 549 withJSON: `{"request":{"uri":"/1","header":{"User-Agent":"test"}}} 550 {"request":{"uri":"/2","header":{}}}`, 551 }, 552 { 553 name: "is-not-missing-null", 554 query: `select * from s3object[*] as s where s.request.header is not missing`, 555 wantResult: `{"request":{"uri":"/1","header":{"User-Agent":"test"}}} 556 {"request":{"uri":"/2","header":null}}`, 557 withJSON: `{"request":{"uri":"/1","header":{"User-Agent":"test"}}} 558 {"request":{"uri":"/2","header":null}}`, 559 }, 560 561 { 562 name: "is-missing", 563 query: `select * from s3object[*] as s where s.request.header['User-Agent'] is missing`, 564 wantResult: `{"request":{"uri":"/2","header":{}}}`, 565 withJSON: `{"request":{"uri":"/1","header":{"User-Agent":"test"}}} 566 {"request":{"uri":"/2","header":{}}}`, 567 }, 568 { 569 name: "is-missing-2", 570 query: `select * from s3object[*] as s where s.request.header is missing`, 571 wantResult: ``, 572 withJSON: `{"request":{"uri":"/1","header":{"User-Agent":"test"}}} 573 {"request":{"uri":"/2","header":{}}}`, 574 }, 575 { 576 name: "is-missing", 577 query: `select * from s3object[*] as s where s.request.header['User-Agent'] = missing`, 578 wantResult: `{"request":{"uri":"/2","header":{}}}`, 579 withJSON: `{"request":{"uri":"/1","header":{"User-Agent":"test"}}} 580 {"request":{"uri":"/2","header":{}}}`, 581 }, 582 { 583 name: "is-missing-null", 584 query: `select * from s3object[*] as s where s.request.header is missing`, 585 wantResult: ``, 586 withJSON: `{"request":{"uri":"/1","header":{"User-Agent":"test"}}} 587 {"request":{"uri":"/2","header":null}}`, 588 }, 589 { 590 name: "is-missing-select", 591 query: `select s.request.header['User-Agent'] as h from s3object[*] as s`, 592 wantResult: `{"h":"test"} 593 {}`, 594 withJSON: `{"request":{"uri":"/1","header":{"User-Agent":"test"}}} 595 {"request":{"uri":"/2","header":{}}}`, 596 }, 597 } 598 599 defRequest := `<?xml version="1.0" encoding="UTF-8"?> 600 <SelectObjectContentRequest> 601 <Expression>%s</Expression> 602 <ExpressionType>SQL</ExpressionType> 603 <InputSerialization> 604 <CompressionType>NONE</CompressionType> 605 <JSON> 606 <Type>LINES</Type> 607 </JSON> 608 </InputSerialization> 609 <OutputSerialization> 610 <JSON> 611 </JSON> 612 </OutputSerialization> 613 <RequestProgress> 614 <Enabled>FALSE</Enabled> 615 </RequestProgress> 616 </SelectObjectContentRequest>` 617 618 for _, testCase := range testTable { 619 t.Run(testCase.name, func(t *testing.T) { 620 // Hack cpuid to the CPU doesn't appear to support AVX2. 621 // Restore whatever happens. 622 if cpuid.CPU.Supports(cpuid.AVX2) { 623 cpuid.CPU.Disable(cpuid.AVX2) 624 defer cpuid.CPU.Enable(cpuid.AVX2) 625 } 626 if simdjson.SupportedCPU() { 627 t.Fatal("setup error: expected cpu to be unsupported") 628 } 629 testReq := testCase.requestXML 630 if len(testReq) == 0 { 631 var escaped bytes.Buffer 632 xml.EscapeText(&escaped, []byte(testCase.query)) 633 testReq = []byte(fmt.Sprintf(defRequest, escaped.String())) 634 } 635 s3Select, err := NewS3Select(bytes.NewReader(testReq)) 636 if err != nil { 637 t.Fatal(err) 638 } 639 640 in := input 641 if len(testCase.withJSON) > 0 { 642 in = testCase.withJSON 643 } 644 if err = s3Select.Open(newStringRSC(in)); err != nil { 645 t.Fatal(err) 646 } 647 648 w := &testResponseWriter{} 649 s3Select.Evaluate(w) 650 s3Select.Close() 651 resp := http.Response{ 652 StatusCode: http.StatusOK, 653 Body: io.NopCloser(bytes.NewReader(w.response)), 654 ContentLength: int64(len(w.response)), 655 } 656 res, err := minio.NewSelectResults(&resp, "testbucket") 657 if err != nil { 658 t.Error(err) 659 return 660 } 661 got, err := io.ReadAll(res) 662 if err != nil { 663 t.Error(err) 664 return 665 } 666 gotS := strings.TrimSpace(string(got)) 667 if !reflect.DeepEqual(gotS, testCase.wantResult) { 668 t.Errorf("received response does not match with expected reply. Query: %s\ngot: %s\nwant:%s", testCase.query, gotS, testCase.wantResult) 669 } 670 }) 671 t.Run("simd-"+testCase.name, func(t *testing.T) { 672 if !simdjson.SupportedCPU() { 673 t.Skip("No CPU support") 674 } 675 testReq := testCase.requestXML 676 if len(testReq) == 0 { 677 var escaped bytes.Buffer 678 xml.EscapeText(&escaped, []byte(testCase.query)) 679 testReq = []byte(fmt.Sprintf(defRequest, escaped.String())) 680 } 681 s3Select, err := NewS3Select(bytes.NewReader(testReq)) 682 if err != nil { 683 t.Fatal(err) 684 } 685 686 in := input 687 if len(testCase.withJSON) > 0 { 688 in = testCase.withJSON 689 } 690 if err = s3Select.Open(newStringRSC(in)); err != nil { 691 t.Fatal(err) 692 } 693 694 w := &testResponseWriter{} 695 s3Select.Evaluate(w) 696 s3Select.Close() 697 resp := http.Response{ 698 StatusCode: http.StatusOK, 699 Body: io.NopCloser(bytes.NewReader(w.response)), 700 ContentLength: int64(len(w.response)), 701 } 702 res, err := minio.NewSelectResults(&resp, "testbucket") 703 if err != nil { 704 t.Error(err) 705 return 706 } 707 got, err := io.ReadAll(res) 708 if err != nil { 709 t.Error(err) 710 return 711 } 712 gotS := strings.TrimSpace(string(got)) 713 if !reflect.DeepEqual(gotS, testCase.wantResult) { 714 t.Errorf("received response does not match with expected reply. Query: %s\ngot: %s\nwant:%s", testCase.query, gotS, testCase.wantResult) 715 } 716 }) 717 } 718 } 719 720 func TestCSVQueries(t *testing.T) { 721 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 722 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)"` 723 724 testTable := []struct { 725 name string 726 query string 727 requestXML []byte 728 wantResult string 729 }{ 730 { 731 name: "select-in-text-simple", 732 query: `SELECT index FROM s3Object s WHERE "Month"='May'`, 733 wantResult: `2700763`, 734 }, 735 } 736 737 defRequest := `<?xml version="1.0" encoding="UTF-8"?> 738 <SelectObjectContentRequest> 739 <Expression>%s</Expression> 740 <ExpressionType>SQL</ExpressionType> 741 <InputSerialization> 742 <CompressionType>NONE</CompressionType> 743 <CSV> 744 <FieldDelimiter>,</FieldDelimiter> 745 <FileHeaderInfo>USE</FileHeaderInfo> 746 <QuoteCharacter>"</QuoteCharacter> 747 <QuoteEscapeCharacter>"</QuoteEscapeCharacter> 748 <RecordDelimiter>\n</RecordDelimiter> 749 </CSV> 750 </InputSerialization> 751 <OutputSerialization> 752 <CSV> 753 </CSV> 754 </OutputSerialization> 755 <RequestProgress> 756 <Enabled>FALSE</Enabled> 757 </RequestProgress> 758 </SelectObjectContentRequest>` 759 760 for _, testCase := range testTable { 761 t.Run(testCase.name, func(t *testing.T) { 762 testReq := testCase.requestXML 763 if len(testReq) == 0 { 764 testReq = []byte(fmt.Sprintf(defRequest, testCase.query)) 765 } 766 s3Select, err := NewS3Select(bytes.NewReader(testReq)) 767 if err != nil { 768 t.Fatal(err) 769 } 770 771 if err = s3Select.Open(newStringRSC(input)); err != nil { 772 t.Fatal(err) 773 } 774 775 w := &testResponseWriter{} 776 s3Select.Evaluate(w) 777 s3Select.Close() 778 resp := http.Response{ 779 StatusCode: http.StatusOK, 780 Body: io.NopCloser(bytes.NewReader(w.response)), 781 ContentLength: int64(len(w.response)), 782 } 783 res, err := minio.NewSelectResults(&resp, "testbucket") 784 if err != nil { 785 t.Error(err) 786 return 787 } 788 got, err := io.ReadAll(res) 789 if err != nil { 790 t.Error(err) 791 return 792 } 793 gotS := strings.TrimSpace(string(got)) 794 if !reflect.DeepEqual(gotS, testCase.wantResult) { 795 t.Errorf("received response does not match with expected reply. Query: %s\ngot: %s\nwant:%s", testCase.query, gotS, testCase.wantResult) 796 } 797 }) 798 } 799 } 800 801 func TestCSVQueries2(t *testing.T) { 802 testInput := []byte(`id,time,num,num2,text 803 1,2010-01-01T,7867786,4565.908123,"a text, with comma" 804 2,2017-01-02T03:04Z,-5, 0.765111, 805 `) 806 testTable := []struct { 807 name string 808 query string 809 input []byte 810 requestXML []byte // override request XML 811 wantResult string 812 }{ 813 { 814 name: "select-all", 815 input: testInput, 816 query: `SELECT * from s3object AS s WHERE id = '1'`, 817 wantResult: `{"id":"1","time":"2010-01-01T","num":"7867786","num2":"4565.908123","text":"a text, with comma"}`, 818 }, 819 { 820 name: "select-all-2", 821 input: testInput, 822 query: `SELECT * from s3object s WHERE id = 2`, 823 wantResult: `{"id":"2","time":"2017-01-02T03:04Z","num":"-5","num2":" 0.765111","text":""}`, 824 }, 825 { 826 name: "select-text-convert", 827 input: testInput, 828 query: `SELECT CAST(text AS STRING) AS text from s3object s WHERE id = 1`, 829 wantResult: `{"text":"a text, with comma"}`, 830 }, 831 { 832 name: "select-text-direct", 833 input: testInput, 834 query: `SELECT text from s3object s WHERE id = 1`, 835 wantResult: `{"text":"a text, with comma"}`, 836 }, 837 { 838 name: "select-time-direct", 839 input: testInput, 840 query: `SELECT time from s3object s WHERE id = 2`, 841 wantResult: `{"time":"2017-01-02T03:04Z"}`, 842 }, 843 { 844 name: "select-int-direct", 845 input: testInput, 846 query: `SELECT num from s3object s WHERE id = 2`, 847 wantResult: `{"num":"-5"}`, 848 }, 849 { 850 name: "select-float-direct", 851 input: testInput, 852 query: `SELECT num2 from s3object s WHERE id = 2`, 853 wantResult: `{"num2":" 0.765111"}`, 854 }, 855 { 856 name: "select-in-array", 857 input: testInput, 858 query: `select id from S3Object s WHERE id in [1,3]`, 859 wantResult: `{"id":"1"}`, 860 }, 861 { 862 name: "select-in-array-matchnone", 863 input: testInput, 864 query: `select id from S3Object s WHERE s.id in [4,3]`, 865 wantResult: ``, 866 }, 867 { 868 name: "select-float-by-val", 869 input: testInput, 870 query: `SELECT num2 from s3object s WHERE num2 = 0.765111`, 871 wantResult: `{"num2":" 0.765111"}`, 872 }, 873 { 874 name: "select-non_exiting_values", 875 input: testInput, 876 query: `SELECT _1 as first, s._100 from s3object s LIMIT 1`, 877 wantResult: `{"first":"1","_100":null}`, 878 }, 879 { 880 name: "select-is_null_noresults", 881 input: testInput, 882 query: `select _2 from S3object where _2 IS NULL`, 883 wantResult: ``, 884 }, 885 { 886 name: "select-is_null_results", 887 input: testInput, 888 query: `select _2 from S3object WHERE _100 IS NULL`, 889 wantResult: `{"_2":"2010-01-01T"} 890 {"_2":"2017-01-02T03:04Z"}`, 891 }, 892 { 893 name: "select-is_not_null_results", 894 input: testInput, 895 query: `select _2 from S3object where _2 IS NOT NULL`, 896 wantResult: `{"_2":"2010-01-01T"} 897 {"_2":"2017-01-02T03:04Z"}`, 898 }, 899 { 900 name: "select-is_not_null_noresults", 901 input: testInput, 902 query: `select _2 from S3object WHERE _100 IS NOT NULL`, 903 wantResult: ``, 904 }, 905 { 906 name: "select-is_not_string", 907 input: []byte(`c1,c2,c3 908 1,2,3 909 1,,3`), 910 query: `select * from S3object where _2 IS NOT ''`, 911 wantResult: `{"c1":"1","c2":"2","c3":"3"}`, 912 }, 913 { 914 name: "select-is_not_string", 915 input: []byte(`c1,c2,c3 916 1,2,3 917 1,,3`), 918 query: `select * from S3object where _2 != '' AND _2 > 1`, 919 wantResult: `{"c1":"1","c2":"2","c3":"3"}`, 920 }, 921 } 922 923 defRequest := `<?xml version="1.0" encoding="UTF-8"?> 924 <SelectObjectContentRequest> 925 <Expression>%s</Expression> 926 <ExpressionType>SQL</ExpressionType> 927 <InputSerialization> 928 <CompressionType>NONE</CompressionType> 929 <CSV> 930 <FileHeaderInfo>USE</FileHeaderInfo> 931 <QuoteCharacter>"</QuoteCharacter> 932 </CSV> 933 </InputSerialization> 934 <OutputSerialization> 935 <JSON> 936 </JSON> 937 </OutputSerialization> 938 <RequestProgress> 939 <Enabled>FALSE</Enabled> 940 </RequestProgress> 941 </SelectObjectContentRequest>` 942 943 for _, testCase := range testTable { 944 t.Run(testCase.name, func(t *testing.T) { 945 testReq := testCase.requestXML 946 if len(testReq) == 0 { 947 testReq = []byte(fmt.Sprintf(defRequest, testCase.query)) 948 } 949 s3Select, err := NewS3Select(bytes.NewReader(testReq)) 950 if err != nil { 951 t.Fatal(err) 952 } 953 954 if err = s3Select.Open(newBytesRSC(testCase.input)); err != nil { 955 t.Fatal(err) 956 } 957 958 w := &testResponseWriter{} 959 s3Select.Evaluate(w) 960 s3Select.Close() 961 resp := http.Response{ 962 StatusCode: http.StatusOK, 963 Body: io.NopCloser(bytes.NewReader(w.response)), 964 ContentLength: int64(len(w.response)), 965 } 966 res, err := minio.NewSelectResults(&resp, "testbucket") 967 if err != nil { 968 t.Error(err) 969 return 970 } 971 got, err := io.ReadAll(res) 972 if err != nil { 973 t.Error(err) 974 return 975 } 976 gotS := strings.TrimSpace(string(got)) 977 if !reflect.DeepEqual(gotS, testCase.wantResult) { 978 t.Errorf("received response does not match with expected reply. Query: %s\ngot: %s\nwant:%s", testCase.query, gotS, testCase.wantResult) 979 } 980 }) 981 } 982 } 983 984 func TestCSVQueries3(t *testing.T) { 985 input := `na.me,qty,CAST 986 apple,1,true 987 mango,3,false 988 ` 989 testTable := []struct { 990 name string 991 query string 992 requestXML []byte // override request XML 993 wantResult string 994 }{ 995 { 996 name: "Select a column containing dot", 997 query: `select "na.me" from S3Object s`, 998 wantResult: `apple 999 mango`, 1000 }, 1001 { 1002 name: "Select column containing dot with table name prefix", 1003 query: `select count(S3Object."na.me") from S3Object`, 1004 wantResult: `2`, 1005 }, 1006 { 1007 name: "Select column containing dot with table alias prefix", 1008 query: `select s."na.me" from S3Object as s`, 1009 wantResult: `apple 1010 mango`, 1011 }, 1012 { 1013 name: "Select column simplest", 1014 query: `select qty from S3Object`, 1015 wantResult: `1 1016 3`, 1017 }, 1018 { 1019 name: "Select column with table name prefix", 1020 query: `select S3Object.qty from S3Object`, 1021 wantResult: `1 1022 3`, 1023 }, 1024 { 1025 name: "Select column without table alias", 1026 query: `select qty from S3Object s`, 1027 wantResult: `1 1028 3`, 1029 }, 1030 { 1031 name: "Select column with table alias", 1032 query: `select s.qty from S3Object s`, 1033 wantResult: `1 1034 3`, 1035 }, 1036 { 1037 name: "Select reserved word column", 1038 query: `select "CAST" from s3object`, 1039 wantResult: `true 1040 false`, 1041 }, 1042 { 1043 name: "Select reserved word column with table alias", 1044 query: `select S3Object."CAST" from s3object`, 1045 wantResult: `true 1046 false`, 1047 }, 1048 { 1049 name: "Select reserved word column with unused table alias", 1050 query: `select "CAST" from s3object s`, 1051 wantResult: `true 1052 false`, 1053 }, 1054 { 1055 name: "Select reserved word column with table alias", 1056 query: `select s."CAST" from s3object s`, 1057 wantResult: `true 1058 false`, 1059 }, 1060 { 1061 name: "Select reserved word column with table alias", 1062 query: `select NOT CAST(s."CAST" AS Bool) from s3object s`, 1063 wantResult: `false 1064 true`, 1065 }, 1066 } 1067 1068 defRequest := `<?xml version="1.0" encoding="UTF-8"?> 1069 <SelectObjectContentRequest> 1070 <Expression>%s</Expression> 1071 <ExpressionType>SQL</ExpressionType> 1072 <InputSerialization> 1073 <CompressionType>NONE</CompressionType> 1074 <CSV> 1075 <FileHeaderInfo>USE</FileHeaderInfo> 1076 <QuoteCharacter>"</QuoteCharacter> 1077 </CSV> 1078 </InputSerialization> 1079 <OutputSerialization> 1080 <CSV/> 1081 </OutputSerialization> 1082 <RequestProgress> 1083 <Enabled>FALSE</Enabled> 1084 </RequestProgress> 1085 </SelectObjectContentRequest>` 1086 1087 for _, testCase := range testTable { 1088 t.Run(testCase.name, func(t *testing.T) { 1089 testReq := testCase.requestXML 1090 if len(testReq) == 0 { 1091 testReq = []byte(fmt.Sprintf(defRequest, testCase.query)) 1092 } 1093 s3Select, err := NewS3Select(bytes.NewReader(testReq)) 1094 if err != nil { 1095 t.Fatal(err) 1096 } 1097 1098 if err = s3Select.Open(newStringRSC(input)); err != nil { 1099 t.Fatal(err) 1100 } 1101 1102 w := &testResponseWriter{} 1103 s3Select.Evaluate(w) 1104 s3Select.Close() 1105 resp := http.Response{ 1106 StatusCode: http.StatusOK, 1107 Body: io.NopCloser(bytes.NewReader(w.response)), 1108 ContentLength: int64(len(w.response)), 1109 } 1110 res, err := minio.NewSelectResults(&resp, "testbucket") 1111 if err != nil { 1112 t.Error(err) 1113 return 1114 } 1115 got, err := io.ReadAll(res) 1116 if err != nil { 1117 t.Error(err) 1118 return 1119 } 1120 gotS := strings.TrimSpace(string(got)) 1121 if gotS != testCase.wantResult { 1122 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) 1123 } 1124 }) 1125 } 1126 } 1127 1128 func TestCSVInput(t *testing.T) { 1129 testTable := []struct { 1130 requestXML []byte 1131 expectedResult []byte 1132 }{ 1133 { 1134 []byte(` 1135 <?xml version="1.0" encoding="UTF-8"?> 1136 <SelectObjectContentRequest> 1137 <Expression>SELECT one, two, three from S3Object</Expression> 1138 <ExpressionType>SQL</ExpressionType> 1139 <InputSerialization> 1140 <CompressionType>NONE</CompressionType> 1141 <CSV> 1142 <FileHeaderInfo>USE</FileHeaderInfo> 1143 </CSV> 1144 </InputSerialization> 1145 <OutputSerialization> 1146 <CSV> 1147 </CSV> 1148 </OutputSerialization> 1149 <RequestProgress> 1150 <Enabled>FALSE</Enabled> 1151 </RequestProgress> 1152 </SelectObjectContentRequest> 1153 `), []byte{ 1154 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, 1155 }, 1156 }, 1157 { 1158 []byte(` 1159 <?xml version="1.0" encoding="UTF-8"?> 1160 <SelectObjectContentRequest> 1161 <Expression>SELECT COUNT(*) AS total_record_count from S3Object</Expression> 1162 <ExpressionType>SQL</ExpressionType> 1163 <InputSerialization> 1164 <CompressionType>NONE</CompressionType> 1165 <CSV> 1166 <FileHeaderInfo>USE</FileHeaderInfo> 1167 </CSV> 1168 </InputSerialization> 1169 <OutputSerialization> 1170 <JSON> 1171 </JSON> 1172 </OutputSerialization> 1173 <RequestProgress> 1174 <Enabled>FALSE</Enabled> 1175 </RequestProgress> 1176 </SelectObjectContentRequest> 1177 `), []byte{ 1178 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, 1179 }, 1180 }, 1181 { 1182 []byte(` 1183 <?xml version="1.0" encoding="UTF-8"?> 1184 <SelectObjectContentRequest> 1185 <Expression>SELECT * from S3Object</Expression> 1186 <ExpressionType>SQL</ExpressionType> 1187 <InputSerialization> 1188 <CompressionType>NONE</CompressionType> 1189 <CSV> 1190 <FileHeaderInfo>USE</FileHeaderInfo> 1191 </CSV> 1192 </InputSerialization> 1193 <OutputSerialization> 1194 <JSON> 1195 </JSON> 1196 </OutputSerialization> 1197 <RequestProgress> 1198 <Enabled>FALSE</Enabled> 1199 </RequestProgress> 1200 </SelectObjectContentRequest> 1201 `), []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}, 1202 }, 1203 { 1204 []byte(` 1205 <?xml version="1.0" encoding="UTF-8"?> 1206 <SelectObjectContentRequest> 1207 <Expression>SELECT one from S3Object limit 1</Expression> 1208 <ExpressionType>SQL</ExpressionType> 1209 <InputSerialization> 1210 <CompressionType>NONE</CompressionType> 1211 <CSV> 1212 <FileHeaderInfo>USE</FileHeaderInfo> 1213 </CSV> 1214 </InputSerialization> 1215 <OutputSerialization> 1216 <CSV> 1217 </CSV> 1218 </OutputSerialization> 1219 <RequestProgress> 1220 <Enabled>FALSE</Enabled> 1221 </RequestProgress> 1222 </SelectObjectContentRequest> 1223 `), []byte{ 1224 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, 1225 }, 1226 }, 1227 } 1228 1229 csvData := []byte(`one,two,three 1230 -1,foo,true 1231 ,bar,false 1232 2.5,baz,true 1233 `) 1234 1235 for i, testCase := range testTable { 1236 t.Run(fmt.Sprint(i), func(t *testing.T) { 1237 s3Select, err := NewS3Select(bytes.NewReader(testCase.requestXML)) 1238 if err != nil { 1239 t.Fatal(err) 1240 } 1241 1242 if err = s3Select.Open(newBytesRSC(csvData)); err != nil { 1243 t.Fatal(err) 1244 } 1245 1246 w := &testResponseWriter{} 1247 s3Select.Evaluate(w) 1248 s3Select.Close() 1249 1250 if !reflect.DeepEqual(w.response, testCase.expectedResult) { 1251 resp := http.Response{ 1252 StatusCode: http.StatusOK, 1253 Body: io.NopCloser(bytes.NewReader(w.response)), 1254 ContentLength: int64(len(w.response)), 1255 } 1256 res, err := minio.NewSelectResults(&resp, "testbucket") 1257 if err != nil { 1258 t.Error(err) 1259 return 1260 } 1261 got, err := io.ReadAll(res) 1262 if err != nil { 1263 t.Error(err) 1264 return 1265 } 1266 1267 t.Errorf("received response does not match with expected reply\ngot: %#v\nwant:%#v\ndecoded:%s", w.response, testCase.expectedResult, string(got)) 1268 } 1269 }) 1270 } 1271 } 1272 1273 func TestJSONInput(t *testing.T) { 1274 testTable := []struct { 1275 requestXML []byte 1276 expectedResult []byte 1277 }{ 1278 { 1279 []byte(` 1280 <?xml version="1.0" encoding="UTF-8"?> 1281 <SelectObjectContentRequest> 1282 <Expression>SELECT one, two, three from S3Object</Expression> 1283 <ExpressionType>SQL</ExpressionType> 1284 <InputSerialization> 1285 <CompressionType>NONE</CompressionType> 1286 <JSON> 1287 <Type>DOCUMENT</Type> 1288 </JSON> 1289 </InputSerialization> 1290 <OutputSerialization> 1291 <CSV> 1292 </CSV> 1293 </OutputSerialization> 1294 <RequestProgress> 1295 <Enabled>FALSE</Enabled> 1296 </RequestProgress> 1297 </SelectObjectContentRequest> 1298 `), []byte{ 1299 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, 1300 }, 1301 }, 1302 { 1303 []byte(` 1304 <?xml version="1.0" encoding="UTF-8"?> 1305 <SelectObjectContentRequest> 1306 <Expression>SELECT COUNT(*) AS total_record_count from S3Object</Expression> 1307 <ExpressionType>SQL</ExpressionType> 1308 <InputSerialization> 1309 <CompressionType>NONE</CompressionType> 1310 <JSON> 1311 <Type>DOCUMENT</Type> 1312 </JSON> 1313 </InputSerialization> 1314 <OutputSerialization> 1315 <CSV> 1316 </CSV> 1317 </OutputSerialization> 1318 <RequestProgress> 1319 <Enabled>FALSE</Enabled> 1320 </RequestProgress> 1321 </SelectObjectContentRequest> 1322 `), []byte{ 1323 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, 1324 }, 1325 }, 1326 { 1327 []byte(` 1328 <?xml version="1.0" encoding="UTF-8"?> 1329 <SelectObjectContentRequest> 1330 <Expression>SELECT * from S3Object</Expression> 1331 <ExpressionType>SQL</ExpressionType> 1332 <InputSerialization> 1333 <CompressionType>NONE</CompressionType> 1334 <JSON> 1335 <Type>DOCUMENT</Type> 1336 </JSON> 1337 </InputSerialization> 1338 <OutputSerialization> 1339 <CSV> 1340 </CSV> 1341 </OutputSerialization> 1342 <RequestProgress> 1343 <Enabled>FALSE</Enabled> 1344 </RequestProgress> 1345 </SelectObjectContentRequest> 1346 `), []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}, 1347 }, 1348 } 1349 1350 jsonData := []byte(`{"three":true,"two":"foo","one":-1} 1351 {"three":false,"two":"bar","one":null} 1352 {"three":true,"two":"baz","one":2.5} 1353 `) 1354 1355 for i, testCase := range testTable { 1356 t.Run(fmt.Sprint(i), func(t *testing.T) { 1357 s3Select, err := NewS3Select(bytes.NewReader(testCase.requestXML)) 1358 if err != nil { 1359 t.Fatal(err) 1360 } 1361 1362 if err = s3Select.Open(newBytesRSC(jsonData)); err != nil { 1363 t.Fatal(err) 1364 } 1365 1366 w := &testResponseWriter{} 1367 s3Select.Evaluate(w) 1368 s3Select.Close() 1369 1370 if !reflect.DeepEqual(w.response, testCase.expectedResult) { 1371 resp := http.Response{ 1372 StatusCode: http.StatusOK, 1373 Body: io.NopCloser(bytes.NewReader(w.response)), 1374 ContentLength: int64(len(w.response)), 1375 } 1376 res, err := minio.NewSelectResults(&resp, "testbucket") 1377 if err != nil { 1378 t.Error(err) 1379 return 1380 } 1381 got, err := io.ReadAll(res) 1382 if err != nil { 1383 t.Error(err) 1384 return 1385 } 1386 1387 t.Errorf("received response does not match with expected reply\ngot: %#v\nwant:%#v\ndecoded:%s", w.response, testCase.expectedResult, string(got)) 1388 } 1389 }) 1390 } 1391 } 1392 1393 func TestCSVRanges(t *testing.T) { 1394 testInput := []byte(`id,time,num,num2,text 1395 1,2010-01-01T,7867786,4565.908123,"a text, with comma" 1396 2,2017-01-02T03:04Z,-5, 0.765111, 1397 `) 1398 testTable := []struct { 1399 name string 1400 query string 1401 input []byte 1402 requestXML []byte // override request XML 1403 wantResult string 1404 wantErr bool 1405 }{ 1406 { 1407 name: "select-all", 1408 input: testInput, 1409 query: ``, 1410 // Since we are doing offset, no headers are used. 1411 wantResult: `{"_1":"2","_2":"2017-01-02T03:04Z","_3":"-5","_4":" 0.765111","_5":""}`, 1412 requestXML: []byte(`<?xml version="1.0" encoding="UTF-8"?> 1413 <SelectObjectContentRequest> 1414 <Expression>SELECT * from s3object AS s</Expression> 1415 <ExpressionType>SQL</ExpressionType> 1416 <InputSerialization> 1417 <CompressionType>NONE</CompressionType> 1418 <CSV> 1419 <FileHeaderInfo>NONE</FileHeaderInfo> 1420 <QuoteCharacter>"</QuoteCharacter> 1421 </CSV> 1422 </InputSerialization> 1423 <OutputSerialization> 1424 <JSON> 1425 </JSON> 1426 </OutputSerialization> 1427 <RequestProgress> 1428 <Enabled>FALSE</Enabled> 1429 </RequestProgress> 1430 <ScanRange><Start>76</Start><End>109</End></ScanRange> 1431 </SelectObjectContentRequest>`), 1432 }, 1433 { 1434 name: "select-remain", 1435 input: testInput, 1436 // Since we are doing offset, no headers are used. 1437 wantResult: `{"_1":"2","_2":"2017-01-02T03:04Z","_3":"-5","_4":" 0.765111","_5":""}`, 1438 requestXML: []byte(`<?xml version="1.0" encoding="UTF-8"?> 1439 <SelectObjectContentRequest> 1440 <Expression>SELECT * from s3object AS s</Expression> 1441 <ExpressionType>SQL</ExpressionType> 1442 <InputSerialization> 1443 <CompressionType>NONE</CompressionType> 1444 <CSV> 1445 <FileHeaderInfo>NONE</FileHeaderInfo> 1446 <QuoteCharacter>"</QuoteCharacter> 1447 </CSV> 1448 </InputSerialization> 1449 <OutputSerialization> 1450 <JSON> 1451 </JSON> 1452 </OutputSerialization> 1453 <RequestProgress> 1454 <Enabled>FALSE</Enabled> 1455 </RequestProgress> 1456 <ScanRange><Start>76</Start></ScanRange> 1457 </SelectObjectContentRequest>`), 1458 }, 1459 { 1460 name: "select-end-bytes", 1461 input: testInput, 1462 query: ``, 1463 // Since we are doing offset, no headers are used. 1464 wantResult: `{"_1":"2","_2":"2017-01-02T03:04Z","_3":"-5","_4":" 0.765111","_5":""}`, 1465 requestXML: []byte(`<?xml version="1.0" encoding="UTF-8"?> 1466 <SelectObjectContentRequest> 1467 <Expression>SELECT * from s3object AS s</Expression> 1468 <ExpressionType>SQL</ExpressionType> 1469 <InputSerialization> 1470 <CompressionType>NONE</CompressionType> 1471 <CSV> 1472 <FileHeaderInfo>NONE</FileHeaderInfo> 1473 <QuoteCharacter>"</QuoteCharacter> 1474 </CSV> 1475 </InputSerialization> 1476 <OutputSerialization> 1477 <JSON> 1478 </JSON> 1479 </OutputSerialization> 1480 <RequestProgress> 1481 <Enabled>FALSE</Enabled> 1482 </RequestProgress> 1483 <ScanRange><End>35</End></ScanRange> 1484 </SelectObjectContentRequest>`), 1485 }, 1486 { 1487 name: "select-middle", 1488 input: testInput, 1489 // Since we are doing offset, no headers are used. 1490 wantResult: `{"_1":"a text, with comma"}`, 1491 requestXML: []byte(`<?xml version="1.0" encoding="UTF-8"?> 1492 <SelectObjectContentRequest> 1493 <Expression>SELECT * from s3object AS s</Expression> 1494 <ExpressionType>SQL</ExpressionType> 1495 <InputSerialization> 1496 <CompressionType>NONE</CompressionType> 1497 <CSV> 1498 <FileHeaderInfo>NONE</FileHeaderInfo> 1499 <QuoteCharacter>"</QuoteCharacter> 1500 </CSV> 1501 </InputSerialization> 1502 <OutputSerialization> 1503 <JSON> 1504 </JSON> 1505 </OutputSerialization> 1506 <RequestProgress> 1507 <Enabled>FALSE</Enabled> 1508 </RequestProgress> 1509 <ScanRange><Start>56</Start><End>76</End></ScanRange> 1510 </SelectObjectContentRequest>`), 1511 }, 1512 { 1513 name: "error-end-before-start", 1514 input: testInput, 1515 // Since we are doing offset, no headers are used. 1516 wantResult: ``, 1517 wantErr: true, 1518 requestXML: []byte(`<?xml version="1.0" encoding="UTF-8"?> 1519 <SelectObjectContentRequest> 1520 <Expression>SELECT * from s3object AS s</Expression> 1521 <ExpressionType>SQL</ExpressionType> 1522 <InputSerialization> 1523 <CompressionType>NONE</CompressionType> 1524 <CSV> 1525 <FileHeaderInfo>NONE</FileHeaderInfo> 1526 <QuoteCharacter>"</QuoteCharacter> 1527 </CSV> 1528 </InputSerialization> 1529 <OutputSerialization> 1530 <JSON> 1531 </JSON> 1532 </OutputSerialization> 1533 <RequestProgress> 1534 <Enabled>FALSE</Enabled> 1535 </RequestProgress> 1536 <ScanRange><Start>56</Start><End>26</End></ScanRange> 1537 </SelectObjectContentRequest>`), 1538 }, 1539 { 1540 name: "error-empty", 1541 input: testInput, 1542 // Since we are doing offset, no headers are used. 1543 wantResult: ``, 1544 wantErr: true, 1545 requestXML: []byte(`<?xml version="1.0" encoding="UTF-8"?> 1546 <SelectObjectContentRequest> 1547 <Expression>SELECT * from s3object AS s</Expression> 1548 <ExpressionType>SQL</ExpressionType> 1549 <InputSerialization> 1550 <CompressionType>NONE</CompressionType> 1551 <CSV> 1552 <FileHeaderInfo>NONE</FileHeaderInfo> 1553 <QuoteCharacter>"</QuoteCharacter> 1554 </CSV> 1555 </InputSerialization> 1556 <OutputSerialization> 1557 <JSON> 1558 </JSON> 1559 </OutputSerialization> 1560 <RequestProgress> 1561 <Enabled>FALSE</Enabled> 1562 </RequestProgress> 1563 <ScanRange></ScanRange> 1564 </SelectObjectContentRequest>`), 1565 }, 1566 { 1567 name: "var-field-count", 1568 input: []byte(`id,time,num,num2,text 1569 1,2010-01-01T,7867786,4565.908123 1570 2,2017-01-02T03:04Z,-5, 0.765111,Some some 1571 `), 1572 // Since we are doing offset, no headers are used. 1573 wantResult: `{"id":"1","time":"2010-01-01T","num":"7867786","num2":"4565.908123"} 1574 {"id":"2","time":"2017-01-02T03:04Z","num":"-5","num2":" 0.765111","text":"Some some"}`, 1575 wantErr: false, 1576 requestXML: []byte(`<?xml version="1.0" encoding="UTF-8"?> 1577 <SelectObjectContentRequest> 1578 <Expression>SELECT * from s3object</Expression> 1579 <ExpressionType>SQL</ExpressionType> 1580 <InputSerialization> 1581 <CompressionType>NONE</CompressionType> 1582 <CSV> 1583 <FileHeaderInfo>USE</FileHeaderInfo> 1584 <QuoteCharacter>"</QuoteCharacter> 1585 </CSV> 1586 </InputSerialization> 1587 <OutputSerialization> 1588 <JSON> 1589 </JSON> 1590 </OutputSerialization> 1591 <RequestProgress> 1592 <Enabled>FALSE</Enabled> 1593 </RequestProgress> 1594 </SelectObjectContentRequest>`), 1595 }, 1596 { 1597 name: "error-after-eof", 1598 input: testInput, 1599 // Since we are doing offset, no headers are used. 1600 wantResult: ``, 1601 wantErr: true, 1602 requestXML: []byte(`<?xml version="1.0" encoding="UTF-8"?> 1603 <SelectObjectContentRequest> 1604 <Expression>SELECT * from s3object AS s</Expression> 1605 <ExpressionType>SQL</ExpressionType> 1606 <InputSerialization> 1607 <CompressionType>NONE</CompressionType> 1608 <CSV> 1609 <FileHeaderInfo>NONE</FileHeaderInfo> 1610 <QuoteCharacter>"</QuoteCharacter> 1611 </CSV> 1612 </InputSerialization> 1613 <OutputSerialization> 1614 <JSON> 1615 </JSON> 1616 </OutputSerialization> 1617 <RequestProgress> 1618 <Enabled>FALSE</Enabled> 1619 </RequestProgress> 1620 <ScanRange><Start>2600000</Start></ScanRange> 1621 </SelectObjectContentRequest>`), 1622 }, 1623 { 1624 name: "error-after-eof", 1625 input: testInput, 1626 // Since we are doing offset, no headers are used. 1627 wantResult: ``, 1628 wantErr: true, 1629 requestXML: []byte(`<?xml version="1.0" encoding="UTF-8"?> 1630 <SelectObjectContentRequest> 1631 <Expression>SELECT * from s3object AS s</Expression> 1632 <ExpressionType>SQL</ExpressionType> 1633 <InputSerialization> 1634 <CompressionType>NONE</CompressionType> 1635 <CSV> 1636 <FileHeaderInfo>NONE</FileHeaderInfo> 1637 <QuoteCharacter>"</QuoteCharacter> 1638 </CSV> 1639 </InputSerialization> 1640 <OutputSerialization> 1641 <JSON> 1642 </JSON> 1643 </OutputSerialization> 1644 <RequestProgress> 1645 <Enabled>FALSE</Enabled> 1646 </RequestProgress> 1647 <ScanRange><Start>2600000</Start><End>2600001</End></ScanRange> 1648 </SelectObjectContentRequest>`), 1649 }, 1650 } 1651 1652 for _, testCase := range testTable { 1653 t.Run(testCase.name, func(t *testing.T) { 1654 testReq := testCase.requestXML 1655 s3Select, err := NewS3Select(bytes.NewReader(testReq)) 1656 if err != nil { 1657 if !testCase.wantErr { 1658 t.Fatal(err) 1659 } 1660 t.Logf("got expected error: %v", err) 1661 return 1662 } 1663 1664 if err = s3Select.Open(newBytesRSC(testCase.input)); err != nil { 1665 if !testCase.wantErr { 1666 t.Fatal(err) 1667 } 1668 t.Logf("got expected error: %v", err) 1669 return 1670 } else if testCase.wantErr { 1671 t.Error("did not get expected error") 1672 return 1673 } 1674 1675 w := &testResponseWriter{} 1676 s3Select.Evaluate(w) 1677 s3Select.Close() 1678 resp := http.Response{ 1679 StatusCode: http.StatusOK, 1680 Body: io.NopCloser(bytes.NewReader(w.response)), 1681 ContentLength: int64(len(w.response)), 1682 } 1683 res, err := minio.NewSelectResults(&resp, "testbucket") 1684 if err != nil { 1685 t.Error(err) 1686 return 1687 } 1688 got, err := io.ReadAll(res) 1689 if err != nil { 1690 t.Error(err) 1691 return 1692 } 1693 gotS := strings.TrimSpace(string(got)) 1694 if !reflect.DeepEqual(gotS, testCase.wantResult) { 1695 t.Errorf("received response does not match with expected reply. Query: %s\ngot: %s\nwant:%s", testCase.query, gotS, testCase.wantResult) 1696 } 1697 }) 1698 } 1699 } 1700 1701 func TestParquetInput(t *testing.T) { 1702 saved := parquetSupport 1703 defer func() { 1704 parquetSupport = saved 1705 }() 1706 parquetSupport = true 1707 1708 testTable := []struct { 1709 requestXML []byte 1710 expectedResult []byte 1711 }{ 1712 { 1713 []byte(` 1714 <?xml version="1.0" encoding="UTF-8"?> 1715 <SelectObjectContentRequest> 1716 <Expression>SELECT one, two, three from S3Object</Expression> 1717 <ExpressionType>SQL</ExpressionType> 1718 <InputSerialization> 1719 <CompressionType>NONE</CompressionType> 1720 <Parquet> 1721 </Parquet> 1722 </InputSerialization> 1723 <OutputSerialization> 1724 <CSV> 1725 </CSV> 1726 </OutputSerialization> 1727 <RequestProgress> 1728 <Enabled>FALSE</Enabled> 1729 </RequestProgress> 1730 </SelectObjectContentRequest> 1731 `), []byte{ 1732 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, 1733 }, 1734 }, 1735 { 1736 []byte(` 1737 <?xml version="1.0" encoding="UTF-8"?> 1738 <SelectObjectContentRequest> 1739 <Expression>SELECT COUNT(*) AS total_record_count from S3Object</Expression> 1740 <ExpressionType>SQL</ExpressionType> 1741 <InputSerialization> 1742 <CompressionType>NONE</CompressionType> 1743 <Parquet> 1744 </Parquet> 1745 </InputSerialization> 1746 <OutputSerialization> 1747 <CSV> 1748 </CSV> 1749 </OutputSerialization> 1750 <RequestProgress> 1751 <Enabled>FALSE</Enabled> 1752 </RequestProgress> 1753 </SelectObjectContentRequest> 1754 `), []byte{ 1755 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, 1756 }, 1757 }, 1758 } 1759 1760 for i, testCase := range testTable { 1761 t.Run(fmt.Sprint(i), func(t *testing.T) { 1762 testdataFile := "testdata/testdata.parquet" 1763 file, err := os.Open(testdataFile) 1764 if err != nil { 1765 t.Fatal(err) 1766 } 1767 1768 s3Select, err := NewS3Select(bytes.NewReader(testCase.requestXML)) 1769 if err != nil { 1770 t.Fatal(err) 1771 } 1772 1773 if err = s3Select.Open(file); err != nil { 1774 t.Fatal(err) 1775 } 1776 1777 fmt.Printf("R: \nE: %s\n" /* string(w.response), */, string(testCase.expectedResult)) 1778 1779 w := &testResponseWriter{} 1780 s3Select.Evaluate(w) 1781 s3Select.Close() 1782 1783 if !reflect.DeepEqual(w.response, testCase.expectedResult) { 1784 resp := http.Response{ 1785 StatusCode: http.StatusOK, 1786 Body: io.NopCloser(bytes.NewReader(w.response)), 1787 ContentLength: int64(len(w.response)), 1788 } 1789 res, err := minio.NewSelectResults(&resp, "testbucket") 1790 if err != nil { 1791 t.Error(err) 1792 return 1793 } 1794 got, err := io.ReadAll(res) 1795 if err != nil { 1796 t.Error(err) 1797 return 1798 } 1799 1800 t.Errorf("received response does not match with expected reply\ngot: %#v\nwant:%#v\ndecoded:%s", w.response, testCase.expectedResult, string(got)) 1801 } 1802 }) 1803 } 1804 } 1805 1806 func TestParquetInputSchema(t *testing.T) { 1807 saved := parquetSupport 1808 defer func() { 1809 parquetSupport = saved 1810 }() 1811 parquetSupport = true 1812 1813 testTable := []struct { 1814 requestXML []byte 1815 wantResult string 1816 }{ 1817 { 1818 requestXML: []byte(` 1819 <?xml version="1.0" encoding="UTF-8"?> 1820 <SelectObjectContentRequest> 1821 <Expression>SELECT * FROM S3Object LIMIT 5</Expression> 1822 <ExpressionType>SQL</ExpressionType> 1823 <InputSerialization> 1824 <CompressionType>NONE</CompressionType> 1825 <Parquet> 1826 </Parquet> 1827 </InputSerialization> 1828 <OutputSerialization> 1829 <JSON> 1830 </JSON> 1831 </OutputSerialization> 1832 <RequestProgress> 1833 <Enabled>FALSE</Enabled> 1834 </RequestProgress> 1835 </SelectObjectContentRequest> 1836 `), wantResult: `{"shipdate":"1996-03-13T"} 1837 {"shipdate":"1996-04-12T"} 1838 {"shipdate":"1996-01-29T"} 1839 {"shipdate":"1996-04-21T"} 1840 {"shipdate":"1996-03-30T"}`, 1841 }, 1842 { 1843 requestXML: []byte(` 1844 <?xml version="1.0" encoding="UTF-8"?> 1845 <SelectObjectContentRequest> 1846 <Expression>SELECT DATE_ADD(day, 2, shipdate) as shipdate FROM S3Object LIMIT 5</Expression> 1847 <ExpressionType>SQL</ExpressionType> 1848 <InputSerialization> 1849 <CompressionType>NONE</CompressionType> 1850 <Parquet> 1851 </Parquet> 1852 </InputSerialization> 1853 <OutputSerialization> 1854 <JSON> 1855 </JSON> 1856 </OutputSerialization> 1857 <RequestProgress> 1858 <Enabled>FALSE</Enabled> 1859 </RequestProgress> 1860 </SelectObjectContentRequest> 1861 `), wantResult: `{"shipdate":"1996-03-15T"} 1862 {"shipdate":"1996-04-14T"} 1863 {"shipdate":"1996-01-31T"} 1864 {"shipdate":"1996-04-23T"} 1865 {"shipdate":"1996-04T"}`, 1866 }, 1867 } 1868 1869 for i, testCase := range testTable { 1870 t.Run(fmt.Sprint(i), func(t *testing.T) { 1871 testdataFile := "testdata/lineitem_shipdate.parquet" 1872 file, err := os.Open(testdataFile) 1873 if err != nil { 1874 t.Fatal(err) 1875 } 1876 1877 s3Select, err := NewS3Select(bytes.NewReader(testCase.requestXML)) 1878 if err != nil { 1879 t.Fatal(err) 1880 } 1881 1882 if err = s3Select.Open(file); err != nil { 1883 t.Fatal(err) 1884 } 1885 1886 w := &testResponseWriter{} 1887 s3Select.Evaluate(w) 1888 s3Select.Close() 1889 resp := http.Response{ 1890 StatusCode: http.StatusOK, 1891 Body: io.NopCloser(bytes.NewReader(w.response)), 1892 ContentLength: int64(len(w.response)), 1893 } 1894 res, err := minio.NewSelectResults(&resp, "testbucket") 1895 if err != nil { 1896 t.Error(err) 1897 return 1898 } 1899 got, err := io.ReadAll(res) 1900 if err != nil { 1901 t.Error(err) 1902 return 1903 } 1904 gotS := strings.TrimSpace(string(got)) 1905 if !reflect.DeepEqual(gotS, testCase.wantResult) { 1906 t.Errorf("received response does not match with expected reply. Query: %s\ngot: %s\nwant:%s", testCase.requestXML, gotS, testCase.wantResult) 1907 } 1908 }) 1909 } 1910 } 1911 1912 func TestParquetInputSchemaCSV(t *testing.T) { 1913 saved := parquetSupport 1914 defer func() { 1915 parquetSupport = saved 1916 }() 1917 parquetSupport = true 1918 1919 testTable := []struct { 1920 requestXML []byte 1921 wantResult string 1922 }{ 1923 { 1924 requestXML: []byte(` 1925 <?xml version="1.0" encoding="UTF-8"?> 1926 <SelectObjectContentRequest> 1927 <Expression>SELECT * FROM S3Object LIMIT 5</Expression> 1928 <ExpressionType>SQL</ExpressionType> 1929 <InputSerialization> 1930 <CompressionType>NONE</CompressionType> 1931 <Parquet> 1932 </Parquet> 1933 </InputSerialization> 1934 <OutputSerialization> 1935 <CSV/> 1936 </OutputSerialization> 1937 <RequestProgress> 1938 <Enabled>FALSE</Enabled> 1939 </RequestProgress> 1940 </SelectObjectContentRequest> 1941 `), wantResult: `1996-03-13T 1942 1996-04-12T 1943 1996-01-29T 1944 1996-04-21T 1945 1996-03-30T`, 1946 }, 1947 { 1948 requestXML: []byte(` 1949 <?xml version="1.0" encoding="UTF-8"?> 1950 <SelectObjectContentRequest> 1951 <Expression>SELECT DATE_ADD(day, 2, shipdate) as shipdate FROM S3Object LIMIT 5</Expression> 1952 <ExpressionType>SQL</ExpressionType> 1953 <InputSerialization> 1954 <CompressionType>NONE</CompressionType> 1955 <Parquet> 1956 </Parquet> 1957 </InputSerialization> 1958 <OutputSerialization> 1959 <CSV/> 1960 </OutputSerialization> 1961 <RequestProgress> 1962 <Enabled>FALSE</Enabled> 1963 </RequestProgress> 1964 </SelectObjectContentRequest> 1965 `), wantResult: `1996-03-15T 1966 1996-04-14T 1967 1996-01-31T 1968 1996-04-23T 1969 1996-04T`, 1970 }, 1971 } 1972 1973 for i, testCase := range testTable { 1974 t.Run(fmt.Sprint(i), func(t *testing.T) { 1975 testdataFile := "testdata/lineitem_shipdate.parquet" 1976 file, err := os.Open(testdataFile) 1977 if err != nil { 1978 t.Fatal(err) 1979 } 1980 1981 s3Select, err := NewS3Select(bytes.NewReader(testCase.requestXML)) 1982 if err != nil { 1983 t.Fatal(err) 1984 } 1985 1986 if err = s3Select.Open(file); err != nil { 1987 t.Fatal(err) 1988 } 1989 1990 w := &testResponseWriter{} 1991 s3Select.Evaluate(w) 1992 s3Select.Close() 1993 resp := http.Response{ 1994 StatusCode: http.StatusOK, 1995 Body: io.NopCloser(bytes.NewReader(w.response)), 1996 ContentLength: int64(len(w.response)), 1997 } 1998 res, err := minio.NewSelectResults(&resp, "testbucket") 1999 if err != nil { 2000 t.Error(err) 2001 return 2002 } 2003 got, err := io.ReadAll(res) 2004 if err != nil { 2005 t.Error(err) 2006 return 2007 } 2008 gotS := strings.TrimSpace(string(got)) 2009 if !reflect.DeepEqual(gotS, testCase.wantResult) { 2010 t.Errorf("received response does not match with expected reply. Query: %s\ngot: %s\nwant:%s", testCase.requestXML, gotS, testCase.wantResult) 2011 } 2012 }) 2013 } 2014 }