github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/sql/sem/tree/parse_array_test.go (about)

     1  // Copyright 2016 The Cockroach Authors.
     2  //
     3  // Use of this software is governed by the Business Source License
     4  // included in the file licenses/BSL.txt.
     5  //
     6  // As of the Change Date specified in that file, in accordance with
     7  // the Business Source License, use of this software will be governed
     8  // by the Apache License, Version 2.0, included in the file
     9  // licenses/APL.txt.
    10  
    11  package tree
    12  
    13  import (
    14  	"bytes"
    15  	"math/rand"
    16  	"testing"
    17  
    18  	"github.com/cockroachdb/cockroach/pkg/settings/cluster"
    19  	"github.com/cockroachdb/cockroach/pkg/sql/types"
    20  	"github.com/cockroachdb/cockroach/pkg/util/leaktest"
    21  )
    22  
    23  func TestParseArray(t *testing.T) {
    24  	defer leaktest.AfterTest(t)()
    25  	testData := []struct {
    26  		str      string
    27  		typ      *types.T
    28  		expected Datums
    29  	}{
    30  		{`{}`, types.Int, Datums{}},
    31  		{`{1}`, types.Int, Datums{NewDInt(1)}},
    32  		{`{1,2}`, types.Int, Datums{NewDInt(1), NewDInt(2)}},
    33  		{`   { 1    ,  2  }  `, types.Int, Datums{NewDInt(1), NewDInt(2)}},
    34  		{`   { 1    ,
    35  			"2"  }  `, types.Int, Datums{NewDInt(1), NewDInt(2)}},
    36  		{`{1,2,3}`, types.Int, Datums{NewDInt(1), NewDInt(2), NewDInt(3)}},
    37  		{`{"1"}`, types.Int, Datums{NewDInt(1)}},
    38  		{` { "1" , "2"}`, types.Int, Datums{NewDInt(1), NewDInt(2)}},
    39  		{` { "1" , 2}`, types.Int, Datums{NewDInt(1), NewDInt(2)}},
    40  		{`{1,NULL}`, types.Int, Datums{NewDInt(1), DNull}},
    41  
    42  		{`{hello}`, types.String, Datums{NewDString(`hello`)}},
    43  		{`{hel
    44  lo}`, types.String, Datums{NewDString(`hel
    45  lo`)}},
    46  		{`{hel,
    47  lo}`, types.String, Datums{NewDString(`hel`), NewDString(`lo`)}},
    48  		{`{hel,lo}`, types.String, Datums{NewDString(`hel`), NewDString(`lo`)}},
    49  		{`{  he llo  }`, types.String, Datums{NewDString(`he llo`)}},
    50  		{"{hello,\u1680world}", types.String, Datums{NewDString(`hello`), NewDString(`world`)}},
    51  		{`{hell\\o}`, types.String, Datums{NewDString(`hell\o`)}},
    52  		{`{"hell\\o"}`, types.String, Datums{NewDString(`hell\o`)}},
    53  		{`{NULL,"NULL"}`, types.String, Datums{DNull, NewDString(`NULL`)}},
    54  		{`{"hello"}`, types.String, Datums{NewDString(`hello`)}},
    55  		{`{" hello "}`, types.String, Datums{NewDString(` hello `)}},
    56  		{`{"hel,lo"}`, types.String, Datums{NewDString(`hel,lo`)}},
    57  		{`{"hel\"lo"}`, types.String, Datums{NewDString(`hel"lo`)}},
    58  		{`{"h\"el\"lo"}`, types.String, Datums{NewDString(`h"el"lo`)}},
    59  		{`{"\"hello"}`, types.String, Datums{NewDString(`"hello`)}},
    60  		{`{"hello\""}`, types.String, Datums{NewDString(`hello"`)}},
    61  		{`{"hel\nlo"}`, types.String, Datums{NewDString(`helnlo`)}},
    62  		{`{"hel\\lo"}`, types.String, Datums{NewDString(`hel\lo`)}},
    63  		{`{"hel\\\"lo"}`, types.String, Datums{NewDString(`hel\"lo`)}},
    64  		{`{"hel\\\\lo"}`, types.String, Datums{NewDString(`hel\\lo`)}},
    65  		{`{"hel\\\\\\lo"}`, types.String, Datums{NewDString(`hel\\\lo`)}},
    66  		{`{"\\"}`, types.String, Datums{NewDString(`\`)}},
    67  		{`{"\\\\"}`, types.String, Datums{NewDString(`\\`)}},
    68  		{`{"\\\\\\"}`, types.String, Datums{NewDString(`\\\`)}},
    69  		{`{"he\,l\}l\{o"}`, types.String, Datums{NewDString(`he,l}l{o`)}},
    70  		// There is no way to input non-printing characters (without having used an escape string previously).
    71  		{`{\\x07}`, types.String, Datums{NewDString(`\x07`)}},
    72  		{`{\x07}`, types.String, Datums{NewDString(`x07`)}},
    73  
    74  		{`{日本語}`, types.String, Datums{NewDString(`日本語`)}},
    75  
    76  		// This can generate some strings with invalid UTF-8, but this isn't a
    77  		// problem, since the input would have had to be invalid UTF-8 for that to
    78  		// occur.
    79  		{string([]byte{'{', 'a', 200, '}'}), types.String, Datums{NewDString("a\xc8")}},
    80  		{string([]byte{'{', 'a', 200, 'a', '}'}), types.String, Datums{NewDString("a\xc8a")}},
    81  	}
    82  	for _, td := range testData {
    83  		t.Run(td.str, func(t *testing.T) {
    84  			expected := NewDArray(td.typ)
    85  			for _, d := range td.expected {
    86  				if err := expected.Append(d); err != nil {
    87  					t.Fatal(err)
    88  				}
    89  			}
    90  			evalContext := NewTestingEvalContext(cluster.MakeTestingClusterSettings())
    91  			actual, err := ParseDArrayFromString(evalContext, td.str, td.typ)
    92  			if err != nil {
    93  				t.Fatalf("ARRAY %s: got error %s, expected %s", td.str, err.Error(), expected)
    94  			}
    95  			if actual.Compare(evalContext, expected) != 0 {
    96  				t.Fatalf("ARRAY %s: got %s, expected %s", td.str, actual, expected)
    97  			}
    98  		})
    99  	}
   100  }
   101  
   102  const randomArrayIterations = 1000
   103  const randomArrayMaxLength = 10
   104  const randomStringMaxLength = 1000
   105  
   106  func TestParseArrayRandomParseArray(t *testing.T) {
   107  	defer leaktest.AfterTest(t)()
   108  	for i := 0; i < randomArrayIterations; i++ {
   109  		numElems := rand.Intn(randomArrayMaxLength)
   110  		ary := make([][]byte, numElems)
   111  		for aryIdx := range ary {
   112  			len := rand.Intn(randomStringMaxLength)
   113  			str := make([]byte, len)
   114  			for strIdx := 0; strIdx < len; strIdx++ {
   115  				str[strIdx] = byte(rand.Intn(256))
   116  			}
   117  			ary[aryIdx] = str
   118  		}
   119  
   120  		var buf bytes.Buffer
   121  		buf.WriteByte('{')
   122  		for j, b := range ary {
   123  			if j > 0 {
   124  				buf.WriteByte(',')
   125  			}
   126  			buf.WriteByte('"')
   127  			// The input format for this doesn't support regular escaping, any
   128  			// character can be preceded by a backslash to encode it directly (this
   129  			// means that there's no printable way to encode non-printing characters,
   130  			// users must use `e` prefixed strings).
   131  			for _, c := range b {
   132  				if c == '"' || c == '\\' || rand.Intn(10) == 0 {
   133  					buf.WriteByte('\\')
   134  				}
   135  				buf.WriteByte(c)
   136  			}
   137  			buf.WriteByte('"')
   138  		}
   139  		buf.WriteByte('}')
   140  
   141  		parsed, err := ParseDArrayFromString(
   142  			NewTestingEvalContext(cluster.MakeTestingClusterSettings()), buf.String(), types.String)
   143  		if err != nil {
   144  			t.Fatalf(`got error: "%s" for elem "%s"`, err, buf.String())
   145  		}
   146  		for aryIdx := range ary {
   147  			value := MustBeDString(parsed.Array[aryIdx])
   148  			if string(value) != string(ary[aryIdx]) {
   149  				t.Fatalf(`iteration %d: array "%s", got %#v, expected %#v`, i, buf.String(), value, string(ary[aryIdx]))
   150  			}
   151  		}
   152  	}
   153  }
   154  
   155  func TestParseArrayError(t *testing.T) {
   156  	defer leaktest.AfterTest(t)()
   157  	testData := []struct {
   158  		str           string
   159  		typ           *types.T
   160  		expectedError string
   161  	}{
   162  		{``, types.Int, `could not parse "" as type int[]: array must be enclosed in { and }`},
   163  		{`1`, types.Int, `could not parse "1" as type int[]: array must be enclosed in { and }`},
   164  		{`1,2`, types.Int, `could not parse "1,2" as type int[]: array must be enclosed in { and }`},
   165  		{`{1,2`, types.Int, `could not parse "{1,2" as type int[]: malformed array`},
   166  		{`{1,2,`, types.Int, `could not parse "{1,2," as type int[]: malformed array`},
   167  		{`{`, types.Int, `could not parse "{" as type int[]: malformed array`},
   168  		{`{,}`, types.Int, `could not parse "{,}" as type int[]: malformed array`},
   169  		{`{}{}`, types.Int, `could not parse "{}{}" as type int[]: extra text after closing right brace`},
   170  		{`{} {}`, types.Int, `could not parse "{} {}" as type int[]: extra text after closing right brace`},
   171  		{`{{}}`, types.Int, `could not parse "{{}}" as type int[]: unimplemented: nested arrays not supported`},
   172  		{`{1, {1}}`, types.Int, `could not parse "{1, {1}}" as type int[]: unimplemented: nested arrays not supported`},
   173  		{`{hello}`, types.Int, `could not parse "{hello}" as type int[]: could not parse "hello" as type int: strconv.ParseInt: parsing "hello": invalid syntax`},
   174  		{`{"hello}`, types.String, `could not parse "{\"hello}" as type string[]: malformed array`},
   175  		// It might be unnecessary to disallow this, but Postgres does.
   176  		{`{he"lo}`, types.String, `could not parse "{he\"lo}" as type string[]: malformed array`},
   177  
   178  		{string([]byte{200}), types.String, `could not parse "\xc8" as type string[]: array must be enclosed in { and }`},
   179  		{string([]byte{'{', 'a', 200}), types.String, `could not parse "{a\xc8" as type string[]: malformed array`},
   180  	}
   181  	for _, td := range testData {
   182  		t.Run(td.str, func(t *testing.T) {
   183  			_, err := ParseDArrayFromString(
   184  				NewTestingEvalContext(cluster.MakeTestingClusterSettings()), td.str, td.typ)
   185  			if err == nil {
   186  				t.Fatalf("expected %#v to error with message %#v", td.str, td.expectedError)
   187  			}
   188  			if err.Error() != td.expectedError {
   189  				t.Fatalf("ARRAY %s: got error %s, expected error %s", td.str, err.Error(), td.expectedError)
   190  			}
   191  		})
   192  	}
   193  }