Skip to content

Commit

Permalink
sql/sem/tree: support parsing of tuples from string literals
Browse files Browse the repository at this point in the history
Release note (sql change): String literals can now be parsed as tuples,
either in a cast expression, or in other contexts like function
arguments.
  • Loading branch information
rafiss committed Oct 29, 2021
1 parent d91fead commit b376038
Show file tree
Hide file tree
Showing 10 changed files with 677 additions and 3 deletions.
2 changes: 1 addition & 1 deletion pkg/cli/clisqlexec/format_value.go
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ func formatArray(
intArray := []int64{}
backingArray = &intArray
parsingArray = (*pq.Int64Array)(&intArray)
case "TEXT", "VARCHAR", "NAME", "CHAR", "BPCHAR":
case "TEXT", "VARCHAR", "NAME", "CHAR", "BPCHAR", "RECORD":
stringArray := []string{}
backingArray = &stringArray
parsingArray = (*pq.StringArray)(&stringArray)
Expand Down
22 changes: 22 additions & 0 deletions pkg/sql/logictest/testdata/logic_test/record
Original file line number Diff line number Diff line change
Expand Up @@ -148,3 +148,25 @@ CREATE VIEW v AS SELECT (1,'a')::b

statement error cannot modify table record type
CREATE VIEW v AS SELECT ((1,'a')::b).a

# Test parsing of record types from string literals.

query T
SELECT COALESCE(ARRAY[ROW(1, 2)], '{}')
----
{"(1,2)"}

query T
SELECT COALESCE(NULL, '{}'::record[]);
----
{}

query T
SELECT '{"(1, 3)", "(1, 2)"}'::a[]
----
{"(1,\" 3\")","(1,\" 2\")"}

query T
SELECT COALESCE(NULL::a[], '{"(1, 3)", "(1, 2)"}');
----
{"(1,\" 3\")","(1,\" 2\")"}
11 changes: 10 additions & 1 deletion pkg/sql/randgen/type.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,12 +111,21 @@ func RandTypeFromSlice(rng *rand.Rand, typs []*types.T) *types.T {
}
return types.MakeArray(inner)
}
if typ.ArrayContents().Family() == types.TupleFamily {
// Generate tuples between 0 and 4 datums in length
len := rng.Intn(5)
contents := make([]*types.T, len)
for i := range contents {
contents[i] = RandTypeFromSlice(rng, typs)
}
return types.MakeArray(types.MakeTuple(contents))
}
case types.TupleFamily:
// Generate tuples between 0 and 4 datums in length
len := rng.Intn(5)
contents := make([]*types.T, len)
for i := range contents {
contents[i] = RandType(rng)
contents[i] = RandTypeFromSlice(rng, typs)
}
return types.MakeTuple(contents)
}
Expand Down
4 changes: 4 additions & 0 deletions pkg/sql/sem/tree/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ go_library(
"overload.go",
"parse_array.go",
"parse_string.go", # keep
"parse_tuple.go",
"persistence.go",
"pgwire_encode.go",
"placeholders.go",
Expand Down Expand Up @@ -203,6 +204,7 @@ go_test(
"operators_test.go",
"overload_test.go",
"parse_array_test.go",
"parse_tuple_test.go",
"placeholders_test.go",
"pretty_test.go",
"role_spec_test.go",
Expand Down Expand Up @@ -242,6 +244,7 @@ go_test(
"//pkg/testutils/sqlutils",
"//pkg/util/duration",
"//pkg/util/hlc",
"//pkg/util/json",
"//pkg/util/leaktest",
"//pkg/util/log",
"//pkg/util/pretty",
Expand All @@ -250,6 +253,7 @@ go_test(
"//pkg/util/timeofday",
"//pkg/util/timetz",
"//pkg/util/timeutil",
"//pkg/util/timeutil/pgdate",
"@com_github_cockroachdb_apd_v2//:apd",
"@com_github_cockroachdb_datadriven//:datadriven",
"@com_github_lib_pq//oid",
Expand Down
2 changes: 2 additions & 0 deletions pkg/sql/sem/tree/constant.go
Original file line number Diff line number Diff line change
Expand Up @@ -495,6 +495,8 @@ var (
types.AnyEnumArray,
types.INetArray,
types.VarBitArray,
types.AnyTuple,
types.AnyTupleArray,
}
// StrValAvailBytes is the set of types convertible to byte array.
StrValAvailBytes = []*types.T{types.Bytes, types.Uuid, types.String, types.AnyEnum}
Expand Down
42 changes: 42 additions & 0 deletions pkg/sql/sem/tree/parse_array_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ import (
"github.com/cockroachdb/cockroach/pkg/util/log"
)

var tupleOfTwoInts = types.MakeTuple([]*types.T{types.Int, types.Int})
var tupleOfStringAndInt = types.MakeTuple([]*types.T{types.String, types.Int})

func TestParseArray(t *testing.T) {
defer leaktest.AfterTest(t)()
defer log.Scope(t).Close(t)
Expand Down Expand Up @@ -80,6 +83,39 @@ lo}`, types.String, Datums{NewDString(`hel`), NewDString(`lo`)}},
// occur.
{string([]byte{'{', 'a', 200, '}'}), types.String, Datums{NewDString("a\xc8")}},
{string([]byte{'{', 'a', 200, 'a', '}'}), types.String, Datums{NewDString("a\xc8a")}},

// Arrays of tuples can also be parsed from string literals.
{
`{"(3,4)"}`,
tupleOfTwoInts,
Datums{NewDTuple(tupleOfTwoInts, NewDInt(3), NewDInt(4))},
},
{
`{"(3,4)", null}`,
tupleOfTwoInts,
Datums{NewDTuple(tupleOfTwoInts, NewDInt(3), NewDInt(4)), DNull},
},
{
`{"(a12',4)"}`,
tupleOfStringAndInt,
Datums{NewDTuple(tupleOfStringAndInt, NewDString("a12'"), NewDInt(4))},
},
{
`{"(cat,4)", "(null,0)"}`,
tupleOfStringAndInt,
Datums{
NewDTuple(tupleOfStringAndInt, NewDString("cat"), NewDInt(4)),
NewDTuple(tupleOfStringAndInt, NewDString("null"), NewDInt(0)),
},
},
{
`{"(1,2)", "(3,)"}`,
tupleOfTwoInts,
Datums{
NewDTuple(tupleOfTwoInts, NewDInt(1), NewDInt(2)),
NewDTuple(tupleOfTwoInts, NewDInt(3), DNull),
},
},
}
for _, td := range testData {
t.Run(td.str, func(t *testing.T) {
Expand Down Expand Up @@ -182,6 +218,12 @@ func TestParseArrayError(t *testing.T) {

{string([]byte{200}), types.String, `could not parse "\xc8" as type string[]: array must be enclosed in { and }`},
{string([]byte{'{', 'a', 200}), types.String, `could not parse "{a\xc8" as type string[]: malformed array`},

{`{"(1,2)", "(3,4,5)"}`, tupleOfTwoInts, `could not parse "{\"(1,2)\", \"(3,4,5)\"}" as type tuple{int, int}[]: could not parse "(3,4,5)" as type tuple{int, int}: malformed record literal`},
{`{"(1,2)", "(3,4,)"}`, tupleOfTwoInts, `could not parse "{\"(1,2)\", \"(3,4,)\"}" as type tuple{int, int}[]: could not parse "(3,4,)" as type tuple{int, int}: malformed record literal`},
{`{"(1,2)", "(3)"}`, tupleOfTwoInts, `could not parse "{\"(1,2)\", \"(3)\"}" as type tuple{int, int}[]: could not parse "(3)" as type tuple{int, int}: malformed record literal`},
{`{"(1,2)", "(3,4"}`, tupleOfTwoInts, `could not parse "{\"(1,2)\", \"(3,4\"}" as type tuple{int, int}[]: could not parse "(3,4" as type tuple{int, int}: malformed record literal`},
{`{"(1,2)", "(3,,4)"}`, tupleOfTwoInts, `could not parse "{\"(1,2)\", \"(3,,4)\"}" as type tuple{int, int}[]: could not parse "(3,,4)" as type tuple{int, int}: malformed record literal`},
}
for _, td := range testData {
t.Run(td.str, func(t *testing.T) {
Expand Down
4 changes: 3 additions & 1 deletion pkg/sql/sem/tree/parse_string.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ func ParseAndRequireString(
}
d = formatBitArrayToType(r, t)
case types.BoolFamily:
d, err = ParseDBool(s)
d, err = ParseDBool(strings.TrimSpace(s))
case types.BytesFamily:
d, err = ParseDByte(s)
case types.DateFamily:
Expand Down Expand Up @@ -89,6 +89,8 @@ func ParseAndRequireString(
d, err = ParseDUuidFromString(s)
case types.EnumFamily:
d, err = MakeDEnumFromLogicalRepresentation(t, s)
case types.TupleFamily:
d, dependsOnContext, err = ParseDTupleFromString(ctx, s, t)
default:
return nil, false, errors.AssertionFailedf("unknown type %s (%T)", t, t)
}
Expand Down
Loading

0 comments on commit b376038

Please sign in to comment.