Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

sql/sem/tree: support parsing of tuples from string literals #71916

Merged
merged 2 commits into from
Oct 29, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions pkg/cli/clisqlexec/format_value.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,8 @@ func FormatVal(
return formatArray(b, colType[1:], showPrintableUnicode, showNewLinesAndTabs)
}

if colType == "NAME" {
// Names, records, and user-defined types should all be displayed as strings.
if colType == "NAME" || colType == "RECORD" || colType == "" {
val = string(b)
colType = "VARCHAR"
}
Expand Down Expand Up @@ -151,7 +152,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
19 changes: 19 additions & 0 deletions pkg/cli/clisqlexec/format_value_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,12 @@ func Example_sql_format() {
// Sanity check for other array types.
c.RunWithArgs([]string{"sql", "-e", "select array[true, false], array['01:01'::time], array['2021-03-20'::date]"})
c.RunWithArgs([]string{"sql", "-e", "select array[123::int2], array[123::int4], array[123::int8]"})
// Check that tuples and user-defined types are escaped correctly.
c.RunWithArgs([]string{"sql", "-e", `create table tup (a text, b int8)`})
c.RunWithArgs([]string{"sql", "-e", `select row('\n',1), row('\\n',2), '("\n",3)'::tup, '("\\n",4)'::tup`})
c.RunWithArgs([]string{"sql", "-e", `select '{"(n,1)","(\n,2)","(\\n,3)"}'::tup[]`})
c.RunWithArgs([]string{"sql", "-e", `create type e as enum('a', '\n')`})
c.RunWithArgs([]string{"sql", "-e", `select '\n'::e, '{a, "\\n"}'::e[]`})

// Output:
// sql -e create database t; create table t.times (bare timestamp, withtz timestamptz)
Expand Down Expand Up @@ -79,4 +85,17 @@ func Example_sql_format() {
// sql -e select array[123::int2], array[123::int4], array[123::int8]
// array array array
// {123} {123} {123}
// sql -e create table tup (a text, b int8)
// CREATE TABLE
// sql -e select row('\n',1), row('\\n',2), '("\n",3)'::tup, '("\\n",4)'::tup
// row row tup tup
// "(""\\n"",1)" "(""\\\\n"",2)" (n,3) "(""\\n"",4)"
// sql -e select '{"(n,1)","(\n,2)","(\\n,3)"}'::tup[]
// tup
// "{""(n,1)"",""(n,2)"",""(n,3)""}"
// sql -e create type e as enum('a', '\n')
// CREATE TYPE
// sql -e select '\n'::e, '{a, "\\n"}'::e[]
// e e
// \n "{a,""\\n""}"
}
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
2 changes: 2 additions & 0 deletions pkg/sql/sem/tree/datum.go
Original file line number Diff line number Diff line change
Expand Up @@ -4459,6 +4459,8 @@ func (d *DEnum) Format(ctx *FmtCtx) {
ctx.WithFlags(ctx.flags|fmtFormatByteLiterals, func() {
s.Format(ctx)
})
} else if ctx.HasFlags(FmtPgwireText) {
ctx.WriteString(d.LogicalRep)
} else {
s := DString(d.LogicalRep)
s.Format(ctx)
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)"}`,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would be good to test non-int/text types, e.g. timestamptz

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

will do. i need to iron out some of the random test issues too

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