Skip to content

Commit 81d8fff

Browse files
committed
optbuilder: preserve ORDER BY in set-returning UDFs with OUT parameters
When set-returning UDFs with OUT parameters are called directly in a SELECT list (e.g., SELECT f()), CockroachDB wraps multiple result columns into a single tuple column. During this transformation, two functions created new scopes without preserving ordering information, causing ORDER BY clauses in the UDF body to be ignored. This commit fixes the issue by calling copyOrdering() in two places: 1. combineRoutineColsIntoTuple - when wrapping columns into a tuple 2. maybeAddRoutineAssignmentCasts - when adding type casts The copyOrdering() method not only copies the ordering metadata but also adds the columns referenced by the ordering to extraCols, ensuring they remain available for the optimizer to enforce the ordering. Fixes #144013 Release note (bug fix): Fixed a bug where ORDER BY clauses in set-returning SQL user-defined functions with OUT parameters were ignored when the function was called directly in a SELECT list (e.g., SELECT f()). The ordering is now properly preserved and enforced. Epic: None
1 parent 07aef07 commit 81d8fff

File tree

2 files changed

+62
-0
lines changed

2 files changed

+62
-0
lines changed

pkg/sql/logictest/testdata/logic_test/udf_setof

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -325,3 +325,63 @@ statement ok
325325
DROP FUNCTION f145414;
326326

327327
subtest end
328+
329+
# Test that ORDER BY is preserved in SQL UDFs with OUT parameters
330+
subtest out_params_ordering
331+
332+
statement ok
333+
CREATE TABLE test_ordering (x INT PRIMARY KEY, y INT);
334+
335+
statement ok
336+
INSERT INTO test_ordering VALUES (1, 10), (2, 20), (3, 30), (4, 40);
337+
338+
statement ok
339+
CREATE FUNCTION f_out_desc(OUT a INT, OUT b INT) RETURNS SETOF RECORD AS $$
340+
SELECT x, y FROM test_ordering ORDER BY x DESC;
341+
$$ LANGUAGE SQL;
342+
343+
query T nosort
344+
SELECT f_out_desc();
345+
----
346+
(4,40)
347+
(3,30)
348+
(2,20)
349+
(1,10)
350+
351+
query II nosort
352+
SELECT * FROM f_out_desc();
353+
----
354+
4 40
355+
3 30
356+
2 20
357+
1 10
358+
359+
statement ok
360+
DROP FUNCTION f_out_desc;
361+
362+
statement ok
363+
CREATE FUNCTION f_out_asc(OUT a INT, OUT b INT) RETURNS SETOF RECORD AS $$
364+
SELECT x, y FROM test_ordering ORDER BY x ASC;
365+
$$ LANGUAGE SQL;
366+
367+
query T nosort
368+
SELECT f_out_asc();
369+
----
370+
(1,10)
371+
(2,20)
372+
(3,30)
373+
(4,40)
374+
375+
query II nosort
376+
SELECT * FROM f_out_asc();
377+
----
378+
1 10
379+
2 20
380+
3 30
381+
4 40
382+
383+
statement ok
384+
DROP FUNCTION f_out_asc;
385+
DROP TABLE test_ordering;
386+
387+
subtest end

pkg/sql/opt/optbuilder/routine.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -653,6 +653,7 @@ func (b *Builder) finalizeRoutineReturnType(
653653
// into a single tuple column.
654654
func (b *Builder) combineRoutineColsIntoTuple(stmtScope *scope) *scope {
655655
outScope := stmtScope.push()
656+
outScope.copyOrdering(stmtScope)
656657
elems := make(memo.ScalarListExpr, len(stmtScope.cols))
657658
typContents := make([]*types.T, len(stmtScope.cols))
658659
for i := range stmtScope.cols {
@@ -721,6 +722,7 @@ func (b *Builder) maybeAddRoutineAssignmentCasts(
721722
return stmtScope
722723
}
723724
outScope := stmtScope.push()
725+
outScope.copyOrdering(stmtScope)
724726
for i, col := range stmtScope.cols {
725727
scalar := b.factory.ConstructVariable(col.id)
726728
if !col.typ.Identical(desiredTypes[i]) {

0 commit comments

Comments
 (0)