diff --git a/pkg/sql/opt/optbuilder/testdata/union b/pkg/sql/opt/optbuilder/testdata/union index 8da5e9163cd1..9625f180e023 100644 --- a/pkg/sql/opt/optbuilder/testdata/union +++ b/pkg/sql/opt/optbuilder/testdata/union @@ -1079,3 +1079,17 @@ sort ├── columns: t68702.a:7 b:8 c:9 └── scan t68702 └── columns: t68702.a:7 b:8 c:9 rowid:10!null crdb_internal_mvcc_timestamp:11 tableoid:12 + + +# Regression test for #70831. Do not add non-existent casts to match types on +# either side of a set operation. +# NOTE: In Postgres this example successfully returns a result. Our tuple typing +# and casting logic doesn't match Postgres's semantics exactly, so we return a +# user error. See +# https://github.com/cockroachdb/cockroach/issues/70831#issuecomment-929547297. +build +SELECT * FROM (VALUES (ARRAY[(true, NULL)])) AS v1 +EXCEPT ALL +SELECT * FROM (VALUES (ARRAY[]::RECORD[])) AS v2 +---- +error (42804): EXCEPT types tuple{bool, unknown}[] and tuple[] cannot be matched diff --git a/pkg/sql/opt/optbuilder/union.go b/pkg/sql/opt/optbuilder/union.go index 4dcfb27da631..f34373096d28 100644 --- a/pkg/sql/opt/optbuilder/union.go +++ b/pkg/sql/opt/optbuilder/union.go @@ -153,15 +153,20 @@ func determineUnionType(left, right *types.T, clauseTag string) *types.T { } if left.Equivalent(right) { - // Do a best-effort attempt to determine which type is "larger". - if left.Width() > right.Width() { - return left - } + // In the default case, use the left type. + src, tgt := right, left if left.Width() < right.Width() { - return right + // If the right type is "larger", use it. + src, tgt = left, right } - // In other cases, use the left type. - return left + if _, ok := tree.LookupCastVolatility(src, tgt, nil /* sessionData */); !ok { + // Error if no cast exists from src to tgt. + panic(pgerror.Newf( + pgcode.DatatypeMismatch, + "%v types %s and %s cannot be matched", clauseTag, left, right, + )) + } + return tgt } leftFam, rightFam := left.Family(), right.Family() @@ -189,7 +194,7 @@ func determineUnionType(left, right *types.T, clauseTag string) *types.T { return right } - // TODO(radu): Postgres has more encompassing rules: + // TODO(#75103): Postgres has more encompassing rules: // http://www.postgresql.org/docs/12/static/typeconv-union-case.html panic(pgerror.Newf( pgcode.DatatypeMismatch, @@ -198,7 +203,9 @@ func determineUnionType(left, right *types.T, clauseTag string) *types.T { } // addCasts adds a projection to a scope, adding casts as necessary so that the -// resulting columns have the given types. +// resulting columns have the given types. This function assumes that there is a +// valid cast from the column types in dst.cols to the corresponding types in +// outTypes. func (b *Builder) addCasts(dst *scope, outTypes []*types.T) *scope { expr := dst.expr.(memo.RelExpr) dstCols := dst.cols diff --git a/pkg/sql/opt/optbuilder/union_test.go b/pkg/sql/opt/optbuilder/union_test.go index 112d5159e3a0..6f04e16e98f2 100644 --- a/pkg/sql/opt/optbuilder/union_test.go +++ b/pkg/sql/opt/optbuilder/union_test.go @@ -72,6 +72,12 @@ func TestUnionType(t *testing.T) { right: types.String, expected: nil, }, + { + // Error. + left: types.MakeArray(types.MakeTuple([]*types.T{types.Bool})), + right: types.MakeArray(types.MakeTuple([]*types.T{types.Bool, types.Int})), + expected: nil, + }, } for _, tc := range testCases {