Skip to content

Commit

Permalink
opt: type check VALUES rows after building them
Browse files Browse the repository at this point in the history
The type of a RECORD-returning UDF is not fully resolved until after it
is fully built within the optbuilder. Therefore, a VALUES expression with
a UDF can only determine its column types after building its rows. We
accounted for this in cockroachdb#103078, but that fix was incomplete - building the row
resolves the type of the UDF, but does not update the type annotation for
any expressions that wrap it (for example, a COALESCE).

This commit fully fixes the type resolution bug by type checking the row
once again after it is built. This ensures that the UDF's resolved type is
correctly propagated to parent expressions.

Fixes cockroachdb#117101

Release note (bug fix): Fixed a bug that would cause an internal error
when the result of a RECORD-returning UDF was wrapped by another expression
(such as COALESCE) within a VALUES clause.
  • Loading branch information
DrewKimball committed Dec 17, 2024
1 parent 3c43ea6 commit 73c0921
Show file tree
Hide file tree
Showing 4 changed files with 34 additions and 7 deletions.
6 changes: 0 additions & 6 deletions pkg/ccl/testccl/sqlccl/explain_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import (

"github.com/cockroachdb/cockroach/pkg/base"
"github.com/cockroachdb/cockroach/pkg/internal/sqlsmith"
"github.com/cockroachdb/cockroach/pkg/sql/colexecerror"
"github.com/cockroachdb/cockroach/pkg/sql/lexbase"
"github.com/cockroachdb/cockroach/pkg/sql/sqltestutils"
"github.com/cockroachdb/cockroach/pkg/sql/tests"
Expand Down Expand Up @@ -138,11 +137,6 @@ func TestExplainGist(t *testing.T) {
skip.UnderDeadlock(t, "the test is too slow")
skip.UnderRace(t, "the test is too slow")

// Use the release-build panic-catching behavior instead of the
// crdb_test-build behavior. This is needed so that some known bugs like
// #117101 don't result in a test failure.
defer colexecerror.ProductionBehaviorForTests()()

ctx := context.Background()
rng, _ := randutil.NewTestRand()

Expand Down
22 changes: 22 additions & 0 deletions pkg/sql/logictest/testdata/logic_test/udf
Original file line number Diff line number Diff line change
Expand Up @@ -931,4 +931,26 @@ SELECT f('0');
statement ok
DROP FUNCTION f;

# Regression test for #117101 - update the resolved type of a VALUES row with a
# UDF after the UDF is built.
subtest regression_117101

statement ok
CREATE FUNCTION f117101() RETURNS RECORD CALLED ON NULL INPUT AS $funcbody$
SELECT ((42)::INT8, (43)::INT8)
$funcbody$ LANGUAGE SQL;

statement error pgcode 42804 pq: VALUES types tuple{int, int} and tuple{tuple{string, int}, unknown} cannot be matched
SELECT
*
FROM
(
VALUES
(
(('aloha'::TEXT,
(44)::INT8), NULL)
),
(COALESCE(f117101(), NULL))
);

subtest end
7 changes: 6 additions & 1 deletion pkg/sql/opt/optbuilder/values.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,9 +72,14 @@ func (b *Builder) buildValuesClause(
panic(err)
}
// UDFs modify their resolved type when built, so build the scalar before
// resolving the column types.
// resolving the column types. We have to call TypeCheck again in order to
// update expressions that wrap a UDF to reflect the modified type.
elems[elemPos] = b.buildScalar(texpr, inScope, nil, nil, nil)
elemPos += numCols
texpr, err = tree.TypeCheck(b.ctx, texpr, b.semaCtx, desired)
if err != nil {
panic(err)
}
if typ := texpr.ResolvedType(); typ.Family() != types.UnknownFamily {
if colTypes[colIdx].Family() == types.UnknownFamily {
colTypes[colIdx] = typ
Expand Down
6 changes: 6 additions & 0 deletions pkg/sql/sem/tree/type_check.go
Original file line number Diff line number Diff line change
Expand Up @@ -1178,6 +1178,12 @@ func (expr *FuncExpr) typeCheckWithFuncAncestor(semaCtx *SemaContext, fn func()
func (expr *FuncExpr) TypeCheck(
ctx context.Context, semaCtx *SemaContext, desired *types.T,
) (TypedExpr, error) {
if expr.fn != nil && expr.fn.Type != BuiltinRoutine && expr.typ != nil {
// Don't overwrite the resolved properties for a user-defined routine if the
// routine has already been resolved.
return expr, nil
}

searchPath := EmptySearchPath
var resolver FunctionReferenceResolver
if semaCtx != nil {
Expand Down

0 comments on commit 73c0921

Please sign in to comment.