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

release-21.2: opt: do not add invalid casts to match types in set operations #75276

Merged
merged 1 commit into from
Jan 24, 2022
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
14 changes: 14 additions & 0 deletions pkg/sql/opt/optbuilder/testdata/union
Original file line number Diff line number Diff line change
Expand Up @@ -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
25 changes: 16 additions & 9 deletions pkg/sql/opt/optbuilder/union.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()

Expand Down Expand Up @@ -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,
Expand All @@ -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
Expand Down
6 changes: 6 additions & 0 deletions pkg/sql/opt/optbuilder/union_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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})),
Copy link
Collaborator

Choose a reason for hiding this comment

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

i noticed that this test case differs from the one in #75219 - is that on purpose?

Copy link
Collaborator

Choose a reason for hiding this comment

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

Yes. In #75219 we're using the OID->OID castMap to determine which casts are valid. In the backports castMap does not yet exist, so we're using the family->family validCasts. They don't match up perfectly so the cast from types.Any -> types.Bool that is not allowed on master is allowed in these release branches, so the test had to change.

expected: nil,
},
}

for _, tc := range testCases {
Expand Down