From acb0b1fbd80fca84bee392f717b370d2136feaff Mon Sep 17 00:00:00 2001 From: rharding6373 Date: Thu, 12 Jan 2023 11:29:27 -0800 Subject: [PATCH] sql: support `*` in udf bodies This change allows `*` usage in UDF bodies. In order to facilitate schema changes after a UDF is created but should not affect the UDF output, we rewrite UDF ASTs in place to reference tables and columns by ID instead of by name. Informs: #90080 Epic: CRDB-19496 Release note (sql change): Allow `*` expressions in UDFs. --- .../tests/3node-tenant/generated_test.go | 7 ++ pkg/sql/logictest/testdata/logic_test/udf | 32 ++--- .../logictest/testdata/logic_test/udf_star | 110 ++++++++++++++++++ .../tests/fakedist-disk/generated_test.go | 7 ++ .../tests/fakedist-vec-off/generated_test.go | 7 ++ .../tests/fakedist/generated_test.go | 7 ++ .../generated_test.go | 7 ++ .../tests/local-vec-off/generated_test.go | 7 ++ .../logictest/tests/local/generated_test.go | 7 ++ pkg/sql/opt/optbuilder/builder.go | 4 + pkg/sql/opt/optbuilder/create_function.go | 1 + pkg/sql/opt/optbuilder/join.go | 4 +- pkg/sql/opt/optbuilder/select.go | 33 ++++-- .../opt/optbuilder/testdata/create_function | 20 ++-- pkg/sql/opt/optbuilder/testdata/udf | 32 +++++ pkg/sql/opt/optbuilder/util.go | 3 - 16 files changed, 243 insertions(+), 45 deletions(-) create mode 100644 pkg/sql/logictest/testdata/logic_test/udf_star diff --git a/pkg/ccl/logictestccl/tests/3node-tenant/generated_test.go b/pkg/ccl/logictestccl/tests/3node-tenant/generated_test.go index 737f13d03980..e3dee7b709ce 100644 --- a/pkg/ccl/logictestccl/tests/3node-tenant/generated_test.go +++ b/pkg/ccl/logictestccl/tests/3node-tenant/generated_test.go @@ -1995,6 +1995,13 @@ func TestTenantLogic_udf( runLogicTest(t, "udf") } +func TestTenantLogic_udf_star( + t *testing.T, +) { + defer leaktest.AfterTest(t)() + runLogicTest(t, "udf_star") +} + func TestTenantLogic_union( t *testing.T, ) { diff --git a/pkg/sql/logictest/testdata/logic_test/udf b/pkg/sql/logictest/testdata/logic_test/udf index eca6514c9b34..337f1741d1a2 100644 --- a/pkg/sql/logictest/testdata/logic_test/udf +++ b/pkg/sql/logictest/testdata/logic_test/udf @@ -52,24 +52,6 @@ CREATE FUNCTION err(i INT) RETURNS INT LANGUAGE SQL AS 'SELECT j' statement error pgcode 42703 column \"j\" does not exist CREATE FUNCTION err(i INT) RETURNS INT LANGUAGE SQL AS 'SELECT a FROM ab WHERE a = j' -statement error pgcode 0A000 functions do not currently support \* expressions -CREATE FUNCTION err(i INT) RETURNS ab LANGUAGE SQL AS 'SELECT * FROM ab' - -statement error pgcode 0A000 functions do not currently support \* expressions -CREATE FUNCTION err(i INT) RETURNS ab LANGUAGE SQL AS 'SELECT ab.* FROM ab' - -statement error pgcode 0A000 functions do not currently support \* expressions -CREATE FUNCTION err(i INT) RETURNS ab LANGUAGE SQL AS $$ - SELECT 1; - SELECT * FROM ab; -$$ - -statement error pgcode 0A000 functions do not currently support \* expressions -CREATE FUNCTION err(i INT) RETURNS INT LANGUAGE SQL AS $$ - SELECT * FROM ab; - SELECT 1; -$$ - statement ok CREATE FUNCTION d(i INT2) RETURNS INT4 LANGUAGE SQL AS 'SELECT i' @@ -142,9 +124,9 @@ CREATE FUNCTION public.f(IN a test.public.notmyworkday) CALLED ON NULL INPUT LANGUAGE SQL AS $$ - SELECT a FROM test.public.t; - SELECT b FROM test.public.t@t_idx_b; - SELECT c FROM test.public.t@t_idx_c; + SELECT a FROM [113(1, 2, 3) AS t]; + SELECT b FROM [113(1, 2, 3) AS t]@t_idx_b; + SELECT c FROM [113(1, 2, 3) AS t]@t_idx_c; SELECT nextval('public.sq1'::REGCLASS); $$ @@ -2321,7 +2303,7 @@ CREATE FUNCTION public.get_l(IN i INT8) CALLED ON NULL INPUT LANGUAGE SQL AS $$ - SELECT v FROM test.public.kv WHERE k = i; + SELECT v FROM [225(1, 2) AS kv] WHERE k = i; $$ query T @@ -2788,7 +2770,7 @@ SELECT oid, proname, pronamespace, proowner, prolang, proleakproof, proisstrict, FROM pg_catalog.pg_proc WHERE proname IN ('f_93314', 'f_93314_alias', 'f_93314_comp', 'f_93314_comp_t') ORDER BY oid; ---- -100257 f_93314 105 1546506610 14 false false false v 0 100256 · {} NULL SELECT i, e FROM test.public.t_93314 ORDER BY i LIMIT 1; -100259 f_93314_alias 105 1546506610 14 false false false v 0 100258 · {} NULL SELECT i, e FROM test.public.t_93314_alias ORDER BY i LIMIT 1; +100257 f_93314 105 1546506610 14 false false false v 0 100256 · {} NULL SELECT i, e FROM [256(1, 2) AS t_93314] ORDER BY i LIMIT 1; +100259 f_93314_alias 105 1546506610 14 false false false v 0 100258 · {} NULL SELECT i, e FROM [258(1, 2) AS t_93314_alias] ORDER BY i LIMIT 1; 100263 f_93314_comp 105 1546506610 14 false false false v 0 100260 · {} NULL SELECT (1, 2); -100264 f_93314_comp_t 105 1546506610 14 false false false v 0 100262 · {} NULL SELECT a, c FROM test.public.t_93314_comp LIMIT 1; +100264 f_93314_comp_t 105 1546506610 14 false false false v 0 100262 · {} NULL SELECT a, c FROM [262(1, 2) AS t_93314_comp] LIMIT 1; diff --git a/pkg/sql/logictest/testdata/logic_test/udf_star b/pkg/sql/logictest/testdata/logic_test/udf_star new file mode 100644 index 000000000000..d9483bfcd09b --- /dev/null +++ b/pkg/sql/logictest/testdata/logic_test/udf_star @@ -0,0 +1,110 @@ +statement ok +CREATE TABLE t_onecol (a INT); +INSERT INTO t_onecol VALUES (1) + +statement ok +CREATE TABLE t_twocol (a INT, b INT); +INSERT INTO t_twocol VALUES (1,2) + +statement ok +CREATE FUNCTION f_unqualified_onecol() RETURNS INT AS +$$ + SELECT * FROM t_onecol; +$$ LANGUAGE SQL; + +statement ok +CREATE FUNCTION f_subquery() RETURNS INT AS +$$ + SELECT * FROM (SELECT a FROM (SELECT * FROM t_onecol) AS foo) AS bar; +$$ LANGUAGE SQL; + +statement ok +CREATE FUNCTION f_unqualified_twocol() RETURNS t_twocol AS +$$ + SELECT * FROM t_twocol; +$$ LANGUAGE SQL; + +statement ok +CREATE FUNCTION f_allcolsel() RETURNS t_twocol AS +$$ + SELECT t_twocol.* FROM t_twocol; +$$ LANGUAGE SQL; + +statement ok +CREATE FUNCTION f_allcolsel_alias() RETURNS t_twocol AS +$$ + SELECT t1.* FROM t_twocol AS t1, t_twocol AS t2 WHERE t1.a = t2.a; +$$ LANGUAGE SQL; + +statement ok +CREATE FUNCTION f_tuplestar() RETURNS t_twocol AS +$$ + SELECT (t_twocol.*).* FROM t_twocol; +$$ LANGUAGE SQL; + +query TTT +SELECT oid, proname, prosrc +FROM pg_catalog.pg_proc WHERE proname LIKE 'f\_%' ORDER BY oid; +---- +100108 f_unqualified_onecol SELECT * FROM [106(1) AS t_onecol]; +100109 f_subquery SELECT * FROM (SELECT a FROM (SELECT * FROM [106(1) AS t_onecol]) AS foo) AS bar; +100110 f_unqualified_twocol SELECT * FROM [107(1, 2) AS t_twocol]; +100111 f_allcolsel SELECT t_twocol.* FROM [107(1, 2) AS t_twocol]; +100112 f_allcolsel_alias SELECT t1.* FROM [107(1, 2) AS t_twocol] AS t1, [107(1, 2) AS t_twocol] AS t2 WHERE t1.a = t2.a; +100113 f_tuplestar SELECT (t_twocol.*).* FROM [107(1, 2) AS t_twocol]; + +query I +SELECT f_unqualified_onecol() +---- +1 + +query I +SELECT f_subquery() +---- +1 + +statement ok +ALTER TABLE t_onecol ADD COLUMN b INT DEFAULT 5; + +query I +SELECT f_unqualified_onecol() +---- +1 + +query I +SELECT f_subquery() +---- +1 + +query T +SELECT f_unqualified_twocol() +---- +(1,2) + +query T +SELECT f_allcolsel() +---- +(1,2) + +query T +SELECT f_allcolsel_alias() +---- +(1,2) + +statement ok +ALTER TABLE t_twocol ADD COLUMN c INT DEFAULT 5; + +# TODO(#95558): Postgres returns an error after adding a column when the table +# is used as the return type. +query T +SELECT f_unqualified_twocol() +---- +(1,2) + +# TODO(harding): Postgres allows column renaming when only referenced by UDFs. +statement error pq: cannot rename column "a" because function "f_unqualified_twocol" depends on it +ALTER TABLE t_twocol RENAME COLUMN a TO d; + +# TODO(harding): Postgres allows table renaming when only referenced by UDFs. +statement error pq: cannot rename relation "t_twocol" because function "f_unqualified_twocol" depends on it +ALTER TABLE t_twocol RENAME TO t_twocol_prime; diff --git a/pkg/sql/logictest/tests/fakedist-disk/generated_test.go b/pkg/sql/logictest/tests/fakedist-disk/generated_test.go index f75661af5ace..4b014dfef603 100644 --- a/pkg/sql/logictest/tests/fakedist-disk/generated_test.go +++ b/pkg/sql/logictest/tests/fakedist-disk/generated_test.go @@ -1976,6 +1976,13 @@ func TestLogic_udf( runLogicTest(t, "udf") } +func TestLogic_udf_star( + t *testing.T, +) { + defer leaktest.AfterTest(t)() + runLogicTest(t, "udf_star") +} + func TestLogic_union( t *testing.T, ) { diff --git a/pkg/sql/logictest/tests/fakedist-vec-off/generated_test.go b/pkg/sql/logictest/tests/fakedist-vec-off/generated_test.go index 64450445b8c3..1e21ada5a6fd 100644 --- a/pkg/sql/logictest/tests/fakedist-vec-off/generated_test.go +++ b/pkg/sql/logictest/tests/fakedist-vec-off/generated_test.go @@ -1983,6 +1983,13 @@ func TestLogic_udf( runLogicTest(t, "udf") } +func TestLogic_udf_star( + t *testing.T, +) { + defer leaktest.AfterTest(t)() + runLogicTest(t, "udf_star") +} + func TestLogic_union( t *testing.T, ) { diff --git a/pkg/sql/logictest/tests/fakedist/generated_test.go b/pkg/sql/logictest/tests/fakedist/generated_test.go index df09b0cd5d1e..64179d8fb348 100644 --- a/pkg/sql/logictest/tests/fakedist/generated_test.go +++ b/pkg/sql/logictest/tests/fakedist/generated_test.go @@ -1997,6 +1997,13 @@ func TestLogic_udf( runLogicTest(t, "udf") } +func TestLogic_udf_star( + t *testing.T, +) { + defer leaktest.AfterTest(t)() + runLogicTest(t, "udf_star") +} + func TestLogic_union( t *testing.T, ) { diff --git a/pkg/sql/logictest/tests/local-legacy-schema-changer/generated_test.go b/pkg/sql/logictest/tests/local-legacy-schema-changer/generated_test.go index a9cf11b1b661..f7a1ac1be6cf 100644 --- a/pkg/sql/logictest/tests/local-legacy-schema-changer/generated_test.go +++ b/pkg/sql/logictest/tests/local-legacy-schema-changer/generated_test.go @@ -1962,6 +1962,13 @@ func TestLogic_udf( runLogicTest(t, "udf") } +func TestLogic_udf_star( + t *testing.T, +) { + defer leaktest.AfterTest(t)() + runLogicTest(t, "udf_star") +} + func TestLogic_union( t *testing.T, ) { diff --git a/pkg/sql/logictest/tests/local-vec-off/generated_test.go b/pkg/sql/logictest/tests/local-vec-off/generated_test.go index 2cc4660507a1..259b372d7b97 100644 --- a/pkg/sql/logictest/tests/local-vec-off/generated_test.go +++ b/pkg/sql/logictest/tests/local-vec-off/generated_test.go @@ -1997,6 +1997,13 @@ func TestLogic_udf( runLogicTest(t, "udf") } +func TestLogic_udf_star( + t *testing.T, +) { + defer leaktest.AfterTest(t)() + runLogicTest(t, "udf_star") +} + func TestLogic_union( t *testing.T, ) { diff --git a/pkg/sql/logictest/tests/local/generated_test.go b/pkg/sql/logictest/tests/local/generated_test.go index 189c94f2f5fc..bfd7433e6c27 100644 --- a/pkg/sql/logictest/tests/local/generated_test.go +++ b/pkg/sql/logictest/tests/local/generated_test.go @@ -2186,6 +2186,13 @@ func TestLogic_udf( runLogicTest(t, "udf") } +func TestLogic_udf_star( + t *testing.T, +) { + defer leaktest.AfterTest(t)() + runLogicTest(t, "udf_star") +} + func TestLogic_union( t *testing.T, ) { diff --git a/pkg/sql/opt/optbuilder/builder.go b/pkg/sql/opt/optbuilder/builder.go index e2fddac93902..504535f320ea 100644 --- a/pkg/sql/opt/optbuilder/builder.go +++ b/pkg/sql/opt/optbuilder/builder.go @@ -143,6 +143,10 @@ type Builder struct { // using AST annotations. qualifyDataSourceNamesInAST bool + // If set, the data source names in the AST are rewritten to the table ID and + // all the visible column IDs. + useIDsInAST bool + // isCorrelated is set to true if we already reported to telemetry that the // query contains a correlated subquery. isCorrelated bool diff --git a/pkg/sql/opt/optbuilder/create_function.go b/pkg/sql/opt/optbuilder/create_function.go index 0d1418cd7497..ff3f37812d02 100644 --- a/pkg/sql/opt/optbuilder/create_function.go +++ b/pkg/sql/opt/optbuilder/create_function.go @@ -27,6 +27,7 @@ import ( func (b *Builder) buildCreateFunction(cf *tree.CreateFunction, inScope *scope) (outScope *scope) { b.DisableMemoReuse = true + b.useIDsInAST = true if cf.FuncName.ExplicitCatalog { if string(cf.FuncName.CatalogName) != b.evalCtx.SessionData().Database { panic(unimplemented.New("CREATE FUNCTION", "cross-db references not supported")) diff --git a/pkg/sql/opt/optbuilder/join.go b/pkg/sql/opt/optbuilder/join.go index be883ffd63a6..a825e44b50c4 100644 --- a/pkg/sql/opt/optbuilder/join.go +++ b/pkg/sql/opt/optbuilder/join.go @@ -33,7 +33,7 @@ import ( func (b *Builder) buildJoin( join *tree.JoinTableExpr, locking lockingSpec, inScope *scope, ) (outScope *scope) { - leftScope := b.buildDataSource(join.Left, nil /* indexFlags */, locking, inScope) + leftScope := b.buildDataSource(&join.Left, nil /* indexFlags */, locking, inScope) inScopeRight := inScope isLateral := b.exprIsLateral(join.Right) @@ -45,7 +45,7 @@ func (b *Builder) buildJoin( inScopeRight.context = exprKindLateralJoin } - rightScope := b.buildDataSource(join.Right, nil /* indexFlags */, locking, inScopeRight) + rightScope := b.buildDataSource(&join.Right, nil /* indexFlags */, locking, inScopeRight) // Check that the same table name is not used on both sides. b.validateJoinTableNames(leftScope, rightScope) diff --git a/pkg/sql/opt/optbuilder/select.go b/pkg/sql/opt/optbuilder/select.go index 8b9c53f8e79f..3a6d130dfd22 100644 --- a/pkg/sql/opt/optbuilder/select.go +++ b/pkg/sql/opt/optbuilder/select.go @@ -12,7 +12,6 @@ package optbuilder import ( "context" - "github.com/cockroachdb/cockroach/pkg/server/telemetry" "github.com/cockroachdb/cockroach/pkg/sql/catalog/colinfo" "github.com/cockroachdb/cockroach/pkg/sql/catalog/descpb" @@ -44,14 +43,14 @@ import ( // See Builder.buildStmt for a description of the remaining input and // return values. func (b *Builder) buildDataSource( - texpr tree.TableExpr, indexFlags *tree.IndexFlags, locking lockingSpec, inScope *scope, + texpr *tree.TableExpr, indexFlags *tree.IndexFlags, locking lockingSpec, inScope *scope, ) (outScope *scope) { defer func(prevAtRoot bool) { inScope.atRoot = prevAtRoot }(inScope.atRoot) inScope.atRoot = false // NB: The case statements are sorted lexicographically. - switch source := texpr.(type) { + switch source := (*texpr).(type) { case *tree.AliasedTableExpr: if source.IndexFlags != nil { telemetry.Inc(sqltelemetry.IndexHintUseCounter) @@ -64,7 +63,7 @@ func (b *Builder) buildDataSource( locking = locking.filter(source.As.Alias) } - outScope = b.buildDataSource(source.Expr, indexFlags, locking, inScope) + outScope = b.buildDataSource(&source.Expr, indexFlags, locking, inScope) if source.Ordinality { outScope = b.buildWithOrdinality(outScope) @@ -110,6 +109,24 @@ func (b *Builder) buildDataSource( } ds, depName, resName := b.resolveDataSource(tn, privilege.SELECT) + if b.useIDsInAST { + switch t := ds.(type) { + case cat.Table: + if t.IsVirtualTable() { + break + } + var cols []tree.ColumnID + for i := 0; i < t.ColumnCount(); i++ { + if t.Column(i).Visibility() == cat.Visible { + cols = append(cols, tree.ColumnID(t.Column(i).ColID())) + } + } + expansion := &tree.TableRef{TableID: int64(ds.ID()), + Columns: cols, + As: tree.AliasClause{Alias: ds.Name()}} + *texpr = expansion + } + } locking = locking.filter(tn.ObjectName) if locking.isSet() { @@ -142,7 +159,7 @@ func (b *Builder) buildDataSource( } case *tree.ParenTableExpr: - return b.buildDataSource(source.Expr, indexFlags, locking, inScope) + return b.buildDataSource(&source.Expr, indexFlags, locking, inScope) case *tree.RowsFromExpr: return b.buildZip(source.Items, inScope) @@ -1216,7 +1233,7 @@ func (b *Builder) buildFromTables( func (b *Builder) buildFromTablesRightDeep( tables tree.TableExprs, locking lockingSpec, inScope *scope, ) (outScope *scope) { - outScope = b.buildDataSource(tables[0], nil /* indexFlags */, locking, inScope) + outScope = b.buildDataSource(&tables[0], nil /* indexFlags */, locking, inScope) // Recursively build table join. tables = tables[1:] @@ -1266,7 +1283,7 @@ func (b *Builder) exprIsLateral(t tree.TableExpr) bool { func (b *Builder) buildFromWithLateral( tables tree.TableExprs, locking lockingSpec, inScope *scope, ) (outScope *scope) { - outScope = b.buildDataSource(tables[0], nil /* indexFlags */, locking, inScope) + outScope = b.buildDataSource(&tables[0], nil /* indexFlags */, locking, inScope) for i := 1; i < len(tables); i++ { scope := inScope // Lateral expressions need to be able to refer to the expressions that @@ -1275,7 +1292,7 @@ func (b *Builder) buildFromWithLateral( scope = outScope scope.context = exprKindLateralJoin } - tableScope := b.buildDataSource(tables[i], nil /* indexFlags */, locking, scope) + tableScope := b.buildDataSource(&tables[i], nil /* indexFlags */, locking, scope) // Check that the same table name is not used multiple times. b.validateJoinTableNames(outScope, tableScope) diff --git a/pkg/sql/opt/optbuilder/testdata/create_function b/pkg/sql/opt/optbuilder/testdata/create_function index 5ee1e03191e2..022af07662c4 100644 --- a/pkg/sql/opt/optbuilder/testdata/create_function +++ b/pkg/sql/opt/optbuilder/testdata/create_function @@ -42,7 +42,7 @@ create-function ├── CREATE FUNCTION f() │ RETURNS workday │ LANGUAGE SQL - │ AS $$SELECT w FROM t.public.workdays;$$ + │ AS $$SELECT w FROM [55(1) AS workdays];$$ └── dependencies ├── workdays [columns: w] └── workday @@ -65,7 +65,7 @@ create-function ├── CREATE FUNCTION f() │ RETURNS STRING │ LANGUAGE SQL - │ AS $$SELECT w::STRING FROM t.public.workdays;$$ + │ AS $$SELECT w::STRING FROM [55(1) AS workdays];$$ └── dependencies └── workdays [columns: w] @@ -76,7 +76,7 @@ create-function ├── CREATE FUNCTION f(IN a INT8) │ RETURNS INT8 │ LANGUAGE SQL - │ AS $$SELECT a FROM t.public.ab;$$ + │ AS $$SELECT a FROM [53(1, 2) AS ab];$$ └── dependencies └── ab [columns: a] @@ -87,7 +87,7 @@ create-function ├── CREATE FUNCTION f() │ RETURNS INT8 │ LANGUAGE SQL - │ AS $$SELECT b FROM t.public.ab@idx;$$ + │ AS $$SELECT b FROM [53(1, 2) AS ab]@idx;$$ └── dependencies └── ab@idx [columns: b] @@ -101,16 +101,22 @@ create-function ├── CREATE FUNCTION f() │ RETURNS INT8 │ LANGUAGE SQL - │ AS $$SELECT a FROM t.public.ab; + │ AS $$SELECT a FROM [53(1, 2) AS ab]; │ SELECT nextval('s');$$ └── dependencies ├── ab [columns: a] └── s build -CREATE FUNCTION f() RETURNS INT LANGUAGE SQL AS $$ SELECT * FROM ab $$ +CREATE FUNCTION f() RETURNS ab LANGUAGE SQL AS $$ SELECT * FROM ab $$ ---- -error (0A000): unimplemented: functions do not currently support * expressions +create-function + ├── CREATE FUNCTION f() + │ RETURNS ab + │ LANGUAGE SQL + │ AS $$SELECT * FROM [53(1, 2) AS ab];$$ + └── dependencies + └── ab [columns: a b] build CREATE FUNCTION f() RETURNS INT LANGUAGE SQL BEGIN ATOMIC SELECT 1; END; diff --git a/pkg/sql/opt/optbuilder/testdata/udf b/pkg/sql/opt/optbuilder/testdata/udf index a5102d72796c..28cc9f57f0eb 100644 --- a/pkg/sql/opt/optbuilder/testdata/udf +++ b/pkg/sql/opt/optbuilder/testdata/udf @@ -1029,3 +1029,35 @@ project └── projections └── assignment-cast: CHAR [as=column3:3] └── variable: s:2 + +# -------------------------------------------------- +# UDFs with * expressions. +# -------------------------------------------------- + +exec-ddl +CREATE TABLE tstar ( + a INT +) +---- + +exec-ddl +CREATE FUNCTION fn_star() RETURNS INT LANGUAGE SQL AS 'SELECT * FROM tstar' +---- + +build format=show-scalars +SELECT fn_star() +---- +project + ├── columns: fn_star:5 + ├── values + │ └── tuple + └── projections + └── udf: fn_star [as=fn_star:5] + └── body + └── limit + ├── columns: a:1 + ├── project + │ ├── columns: a:1 + │ └── scan tstar + │ └── columns: a:1 rowid:2!null crdb_internal_mvcc_timestamp:3 tableoid:4 + └── const: 1 diff --git a/pkg/sql/opt/optbuilder/util.go b/pkg/sql/opt/optbuilder/util.go index c3f7f68781a0..c210674d376c 100644 --- a/pkg/sql/opt/optbuilder/util.go +++ b/pkg/sql/opt/optbuilder/util.go @@ -64,9 +64,6 @@ func (b *Builder) expandStar( if b.insideViewDef { panic(unimplemented.NewWithIssue(10028, "views do not currently support * expressions")) } - if b.insideFuncDef { - panic(unimplemented.NewWithIssue(90080, "functions do not currently support * expressions")) - } switch t := expr.(type) { case *tree.TupleStar: texpr := inScope.resolveType(t.Expr, types.Any)