storj.io/minio@v0.0.0-20230509071714-0cbc90f649b1/pkg/s3select/select_test.go (about)

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