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 }