Skip to content

Commit acb0b1f

Browse files
committed
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.
1 parent 51005e4 commit acb0b1f

File tree

16 files changed

+243
-45
lines changed

16 files changed

+243
-45
lines changed

pkg/ccl/logictestccl/tests/3node-tenant/generated_test.go

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pkg/sql/logictest/testdata/logic_test/udf

Lines changed: 7 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -52,24 +52,6 @@ CREATE FUNCTION err(i INT) RETURNS INT LANGUAGE SQL AS 'SELECT j'
5252
statement error pgcode 42703 column \"j\" does not exist
5353
CREATE FUNCTION err(i INT) RETURNS INT LANGUAGE SQL AS 'SELECT a FROM ab WHERE a = j'
5454

55-
statement error pgcode 0A000 functions do not currently support \* expressions
56-
CREATE FUNCTION err(i INT) RETURNS ab LANGUAGE SQL AS 'SELECT * FROM ab'
57-
58-
statement error pgcode 0A000 functions do not currently support \* expressions
59-
CREATE FUNCTION err(i INT) RETURNS ab LANGUAGE SQL AS 'SELECT ab.* FROM ab'
60-
61-
statement error pgcode 0A000 functions do not currently support \* expressions
62-
CREATE FUNCTION err(i INT) RETURNS ab LANGUAGE SQL AS $$
63-
SELECT 1;
64-
SELECT * FROM ab;
65-
$$
66-
67-
statement error pgcode 0A000 functions do not currently support \* expressions
68-
CREATE FUNCTION err(i INT) RETURNS INT LANGUAGE SQL AS $$
69-
SELECT * FROM ab;
70-
SELECT 1;
71-
$$
72-
7355
statement ok
7456
CREATE FUNCTION d(i INT2) RETURNS INT4 LANGUAGE SQL AS 'SELECT i'
7557

@@ -142,9 +124,9 @@ CREATE FUNCTION public.f(IN a test.public.notmyworkday)
142124
CALLED ON NULL INPUT
143125
LANGUAGE SQL
144126
AS $$
145-
SELECT a FROM test.public.t;
146-
SELECT b FROM test.public.t@t_idx_b;
147-
SELECT c FROM test.public.t@t_idx_c;
127+
SELECT a FROM [113(1, 2, 3) AS t];
128+
SELECT b FROM [113(1, 2, 3) AS t]@t_idx_b;
129+
SELECT c FROM [113(1, 2, 3) AS t]@t_idx_c;
148130
SELECT nextval('public.sq1'::REGCLASS);
149131
$$
150132

@@ -2321,7 +2303,7 @@ CREATE FUNCTION public.get_l(IN i INT8)
23212303
CALLED ON NULL INPUT
23222304
LANGUAGE SQL
23232305
AS $$
2324-
SELECT v FROM test.public.kv WHERE k = i;
2306+
SELECT v FROM [225(1, 2) AS kv] WHERE k = i;
23252307
$$
23262308

23272309
query T
@@ -2788,7 +2770,7 @@ SELECT oid, proname, pronamespace, proowner, prolang, proleakproof, proisstrict,
27882770
FROM pg_catalog.pg_proc WHERE proname IN ('f_93314', 'f_93314_alias', 'f_93314_comp', 'f_93314_comp_t')
27892771
ORDER BY oid;
27902772
----
2791-
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;
2792-
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;
2773+
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;
2774+
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;
27932775
100263 f_93314_comp 105 1546506610 14 false false false v 0 100260 · {} NULL SELECT (1, 2);
2794-
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;
2776+
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;
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
statement ok
2+
CREATE TABLE t_onecol (a INT);
3+
INSERT INTO t_onecol VALUES (1)
4+
5+
statement ok
6+
CREATE TABLE t_twocol (a INT, b INT);
7+
INSERT INTO t_twocol VALUES (1,2)
8+
9+
statement ok
10+
CREATE FUNCTION f_unqualified_onecol() RETURNS INT AS
11+
$$
12+
SELECT * FROM t_onecol;
13+
$$ LANGUAGE SQL;
14+
15+
statement ok
16+
CREATE FUNCTION f_subquery() RETURNS INT AS
17+
$$
18+
SELECT * FROM (SELECT a FROM (SELECT * FROM t_onecol) AS foo) AS bar;
19+
$$ LANGUAGE SQL;
20+
21+
statement ok
22+
CREATE FUNCTION f_unqualified_twocol() RETURNS t_twocol AS
23+
$$
24+
SELECT * FROM t_twocol;
25+
$$ LANGUAGE SQL;
26+
27+
statement ok
28+
CREATE FUNCTION f_allcolsel() RETURNS t_twocol AS
29+
$$
30+
SELECT t_twocol.* FROM t_twocol;
31+
$$ LANGUAGE SQL;
32+
33+
statement ok
34+
CREATE FUNCTION f_allcolsel_alias() RETURNS t_twocol AS
35+
$$
36+
SELECT t1.* FROM t_twocol AS t1, t_twocol AS t2 WHERE t1.a = t2.a;
37+
$$ LANGUAGE SQL;
38+
39+
statement ok
40+
CREATE FUNCTION f_tuplestar() RETURNS t_twocol AS
41+
$$
42+
SELECT (t_twocol.*).* FROM t_twocol;
43+
$$ LANGUAGE SQL;
44+
45+
query TTT
46+
SELECT oid, proname, prosrc
47+
FROM pg_catalog.pg_proc WHERE proname LIKE 'f\_%' ORDER BY oid;
48+
----
49+
100108 f_unqualified_onecol SELECT * FROM [106(1) AS t_onecol];
50+
100109 f_subquery SELECT * FROM (SELECT a FROM (SELECT * FROM [106(1) AS t_onecol]) AS foo) AS bar;
51+
100110 f_unqualified_twocol SELECT * FROM [107(1, 2) AS t_twocol];
52+
100111 f_allcolsel SELECT t_twocol.* FROM [107(1, 2) AS t_twocol];
53+
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;
54+
100113 f_tuplestar SELECT (t_twocol.*).* FROM [107(1, 2) AS t_twocol];
55+
56+
query I
57+
SELECT f_unqualified_onecol()
58+
----
59+
1
60+
61+
query I
62+
SELECT f_subquery()
63+
----
64+
1
65+
66+
statement ok
67+
ALTER TABLE t_onecol ADD COLUMN b INT DEFAULT 5;
68+
69+
query I
70+
SELECT f_unqualified_onecol()
71+
----
72+
1
73+
74+
query I
75+
SELECT f_subquery()
76+
----
77+
1
78+
79+
query T
80+
SELECT f_unqualified_twocol()
81+
----
82+
(1,2)
83+
84+
query T
85+
SELECT f_allcolsel()
86+
----
87+
(1,2)
88+
89+
query T
90+
SELECT f_allcolsel_alias()
91+
----
92+
(1,2)
93+
94+
statement ok
95+
ALTER TABLE t_twocol ADD COLUMN c INT DEFAULT 5;
96+
97+
# TODO(#95558): Postgres returns an error after adding a column when the table
98+
# is used as the return type.
99+
query T
100+
SELECT f_unqualified_twocol()
101+
----
102+
(1,2)
103+
104+
# TODO(harding): Postgres allows column renaming when only referenced by UDFs.
105+
statement error pq: cannot rename column "a" because function "f_unqualified_twocol" depends on it
106+
ALTER TABLE t_twocol RENAME COLUMN a TO d;
107+
108+
# TODO(harding): Postgres allows table renaming when only referenced by UDFs.
109+
statement error pq: cannot rename relation "t_twocol" because function "f_unqualified_twocol" depends on it
110+
ALTER TABLE t_twocol RENAME TO t_twocol_prime;

pkg/sql/logictest/tests/fakedist-disk/generated_test.go

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pkg/sql/logictest/tests/fakedist-vec-off/generated_test.go

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pkg/sql/logictest/tests/fakedist/generated_test.go

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pkg/sql/logictest/tests/local-legacy-schema-changer/generated_test.go

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pkg/sql/logictest/tests/local-vec-off/generated_test.go

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pkg/sql/logictest/tests/local/generated_test.go

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pkg/sql/opt/optbuilder/builder.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,10 @@ type Builder struct {
143143
// using AST annotations.
144144
qualifyDataSourceNamesInAST bool
145145

146+
// If set, the data source names in the AST are rewritten to the table ID and
147+
// all the visible column IDs.
148+
useIDsInAST bool
149+
146150
// isCorrelated is set to true if we already reported to telemetry that the
147151
// query contains a correlated subquery.
148152
isCorrelated bool

pkg/sql/opt/optbuilder/create_function.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import (
2727

2828
func (b *Builder) buildCreateFunction(cf *tree.CreateFunction, inScope *scope) (outScope *scope) {
2929
b.DisableMemoReuse = true
30+
b.useIDsInAST = true
3031
if cf.FuncName.ExplicitCatalog {
3132
if string(cf.FuncName.CatalogName) != b.evalCtx.SessionData().Database {
3233
panic(unimplemented.New("CREATE FUNCTION", "cross-db references not supported"))

pkg/sql/opt/optbuilder/join.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ import (
3333
func (b *Builder) buildJoin(
3434
join *tree.JoinTableExpr, locking lockingSpec, inScope *scope,
3535
) (outScope *scope) {
36-
leftScope := b.buildDataSource(join.Left, nil /* indexFlags */, locking, inScope)
36+
leftScope := b.buildDataSource(&join.Left, nil /* indexFlags */, locking, inScope)
3737

3838
inScopeRight := inScope
3939
isLateral := b.exprIsLateral(join.Right)
@@ -45,7 +45,7 @@ func (b *Builder) buildJoin(
4545
inScopeRight.context = exprKindLateralJoin
4646
}
4747

48-
rightScope := b.buildDataSource(join.Right, nil /* indexFlags */, locking, inScopeRight)
48+
rightScope := b.buildDataSource(&join.Right, nil /* indexFlags */, locking, inScopeRight)
4949

5050
// Check that the same table name is not used on both sides.
5151
b.validateJoinTableNames(leftScope, rightScope)

pkg/sql/opt/optbuilder/select.go

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ package optbuilder
1212

1313
import (
1414
"context"
15-
1615
"github.com/cockroachdb/cockroach/pkg/server/telemetry"
1716
"github.com/cockroachdb/cockroach/pkg/sql/catalog/colinfo"
1817
"github.com/cockroachdb/cockroach/pkg/sql/catalog/descpb"
@@ -44,14 +43,14 @@ import (
4443
// See Builder.buildStmt for a description of the remaining input and
4544
// return values.
4645
func (b *Builder) buildDataSource(
47-
texpr tree.TableExpr, indexFlags *tree.IndexFlags, locking lockingSpec, inScope *scope,
46+
texpr *tree.TableExpr, indexFlags *tree.IndexFlags, locking lockingSpec, inScope *scope,
4847
) (outScope *scope) {
4948
defer func(prevAtRoot bool) {
5049
inScope.atRoot = prevAtRoot
5150
}(inScope.atRoot)
5251
inScope.atRoot = false
5352
// NB: The case statements are sorted lexicographically.
54-
switch source := texpr.(type) {
53+
switch source := (*texpr).(type) {
5554
case *tree.AliasedTableExpr:
5655
if source.IndexFlags != nil {
5756
telemetry.Inc(sqltelemetry.IndexHintUseCounter)
@@ -64,7 +63,7 @@ func (b *Builder) buildDataSource(
6463
locking = locking.filter(source.As.Alias)
6564
}
6665

67-
outScope = b.buildDataSource(source.Expr, indexFlags, locking, inScope)
66+
outScope = b.buildDataSource(&source.Expr, indexFlags, locking, inScope)
6867

6968
if source.Ordinality {
7069
outScope = b.buildWithOrdinality(outScope)
@@ -110,6 +109,24 @@ func (b *Builder) buildDataSource(
110109
}
111110

112111
ds, depName, resName := b.resolveDataSource(tn, privilege.SELECT)
112+
if b.useIDsInAST {
113+
switch t := ds.(type) {
114+
case cat.Table:
115+
if t.IsVirtualTable() {
116+
break
117+
}
118+
var cols []tree.ColumnID
119+
for i := 0; i < t.ColumnCount(); i++ {
120+
if t.Column(i).Visibility() == cat.Visible {
121+
cols = append(cols, tree.ColumnID(t.Column(i).ColID()))
122+
}
123+
}
124+
expansion := &tree.TableRef{TableID: int64(ds.ID()),
125+
Columns: cols,
126+
As: tree.AliasClause{Alias: ds.Name()}}
127+
*texpr = expansion
128+
}
129+
}
113130

114131
locking = locking.filter(tn.ObjectName)
115132
if locking.isSet() {
@@ -142,7 +159,7 @@ func (b *Builder) buildDataSource(
142159
}
143160

144161
case *tree.ParenTableExpr:
145-
return b.buildDataSource(source.Expr, indexFlags, locking, inScope)
162+
return b.buildDataSource(&source.Expr, indexFlags, locking, inScope)
146163

147164
case *tree.RowsFromExpr:
148165
return b.buildZip(source.Items, inScope)
@@ -1216,7 +1233,7 @@ func (b *Builder) buildFromTables(
12161233
func (b *Builder) buildFromTablesRightDeep(
12171234
tables tree.TableExprs, locking lockingSpec, inScope *scope,
12181235
) (outScope *scope) {
1219-
outScope = b.buildDataSource(tables[0], nil /* indexFlags */, locking, inScope)
1236+
outScope = b.buildDataSource(&tables[0], nil /* indexFlags */, locking, inScope)
12201237

12211238
// Recursively build table join.
12221239
tables = tables[1:]
@@ -1266,7 +1283,7 @@ func (b *Builder) exprIsLateral(t tree.TableExpr) bool {
12661283
func (b *Builder) buildFromWithLateral(
12671284
tables tree.TableExprs, locking lockingSpec, inScope *scope,
12681285
) (outScope *scope) {
1269-
outScope = b.buildDataSource(tables[0], nil /* indexFlags */, locking, inScope)
1286+
outScope = b.buildDataSource(&tables[0], nil /* indexFlags */, locking, inScope)
12701287
for i := 1; i < len(tables); i++ {
12711288
scope := inScope
12721289
// Lateral expressions need to be able to refer to the expressions that
@@ -1275,7 +1292,7 @@ func (b *Builder) buildFromWithLateral(
12751292
scope = outScope
12761293
scope.context = exprKindLateralJoin
12771294
}
1278-
tableScope := b.buildDataSource(tables[i], nil /* indexFlags */, locking, scope)
1295+
tableScope := b.buildDataSource(&tables[i], nil /* indexFlags */, locking, scope)
12791296

12801297
// Check that the same table name is not used multiple times.
12811298
b.validateJoinTableNames(outScope, tableScope)

0 commit comments

Comments
 (0)