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  }