Skip to content

Commit d575347

Browse files
committed
Fix EXPLAIN of SEARCH BREADTH FIRST with a constant initial value.
If the non-recursive term of a SEARCH BREADTH FIRST recursive query has only constants in its target list, the planner will fold the starting RowExpr added by rewrite into a simple Const of type RECORD. The executor doesn't have any problem with that --- but EXPLAIN VERBOSE will encounter the Const as the ultimate source of truth about what the field names of the SET column are, and it didn't know what to do with that. Fortunately, we can pull the identifying typmod out of the Const, in much the same way that record_out would. For reasons that remain a bit obscure to me, this only fails with SEARCH BREADTH FIRST, not SEARCH DEPTH FIRST or CYCLE. But I added regression test cases for both of those options too, just to make sure we don't break it in future. Per bug #17644 from Matthijs van der Vleuten. Back-patch to v14 where these constructs were added. Discussion: https://postgr.es/m/17644-3bd1f3036d6d7a16@postgresql.org
1 parent 717ec1a commit d575347

File tree

4 files changed

+208
-1
lines changed

4 files changed

+208
-1
lines changed

src/backend/utils/adt/ruleutils.c

+2-1
Original file line numberDiff line numberDiff line change
@@ -7500,7 +7500,8 @@ get_name_for_var_field(Var *var, int fieldno,
75007500

75017501
/*
75027502
* If it's a RowExpr that was expanded from a whole-row Var, use the
7503-
* column names attached to it.
7503+
* column names attached to it. (We could let get_expr_result_tupdesc()
7504+
* handle this, but it's much cheaper to just pull out the name we need.)
75047505
*/
75057506
if (IsA(var, RowExpr))
75067507
{

src/backend/utils/fmgr/funcapi.c

+34
Original file line numberDiff line numberDiff line change
@@ -339,6 +339,40 @@ get_expr_result_type(Node *expr,
339339
*resultTupleDesc = BlessTupleDesc(tupdesc);
340340
return TYPEFUNC_COMPOSITE;
341341
}
342+
else if (expr && IsA(expr, Const) &&
343+
((Const *) expr)->consttype == RECORDOID &&
344+
!((Const *) expr)->constisnull)
345+
{
346+
/*
347+
* When EXPLAIN'ing some queries with SEARCH/CYCLE clauses, we may
348+
* need to resolve field names of a RECORD-type Const. The datum
349+
* should contain a typmod that will tell us that.
350+
*/
351+
HeapTupleHeader rec;
352+
Oid tupType;
353+
int32 tupTypmod;
354+
355+
rec = DatumGetHeapTupleHeader(((Const *) expr)->constvalue);
356+
tupType = HeapTupleHeaderGetTypeId(rec);
357+
tupTypmod = HeapTupleHeaderGetTypMod(rec);
358+
if (resultTypeId)
359+
*resultTypeId = tupType;
360+
if (tupType != RECORDOID || tupTypmod >= 0)
361+
{
362+
/* Should be able to look it up */
363+
if (resultTupleDesc)
364+
*resultTupleDesc = lookup_rowtype_tupdesc_copy(tupType,
365+
tupTypmod);
366+
return TYPEFUNC_COMPOSITE;
367+
}
368+
else
369+
{
370+
/* This shouldn't really happen ... */
371+
if (resultTupleDesc)
372+
*resultTupleDesc = NULL;
373+
return TYPEFUNC_RECORD;
374+
}
375+
}
342376
else
343377
{
344378
/* handle as a generic expression; no chance to resolve RECORD */

src/test/regress/expected/with.out

+120
Original file line numberDiff line numberDiff line change
@@ -790,6 +790,83 @@ select * from search_graph order by seq;
790790
4 | 5 | arc 4 -> 5 | (1,4,5)
791791
(7 rows)
792792

793+
-- a constant initial value causes issues for EXPLAIN
794+
explain (verbose, costs off)
795+
with recursive test as (
796+
select 1 as x
797+
union all
798+
select x + 1
799+
from test
800+
) search depth first by x set y
801+
select * from test limit 5;
802+
QUERY PLAN
803+
-----------------------------------------------------------------------------------------
804+
Limit
805+
Output: test.x, test.y
806+
CTE test
807+
-> Recursive Union
808+
-> Result
809+
Output: 1, '{(1)}'::record[]
810+
-> WorkTable Scan on test test_1
811+
Output: (test_1.x + 1), array_cat(test_1.y, ARRAY[ROW((test_1.x + 1))])
812+
-> CTE Scan on test
813+
Output: test.x, test.y
814+
(10 rows)
815+
816+
with recursive test as (
817+
select 1 as x
818+
union all
819+
select x + 1
820+
from test
821+
) search depth first by x set y
822+
select * from test limit 5;
823+
x | y
824+
---+-----------------------
825+
1 | {(1)}
826+
2 | {(1),(2)}
827+
3 | {(1),(2),(3)}
828+
4 | {(1),(2),(3),(4)}
829+
5 | {(1),(2),(3),(4),(5)}
830+
(5 rows)
831+
832+
explain (verbose, costs off)
833+
with recursive test as (
834+
select 1 as x
835+
union all
836+
select x + 1
837+
from test
838+
) search breadth first by x set y
839+
select * from test limit 5;
840+
QUERY PLAN
841+
--------------------------------------------------------------------------------------------
842+
Limit
843+
Output: test.x, test.y
844+
CTE test
845+
-> Recursive Union
846+
-> Result
847+
Output: 1, '(0,1)'::record
848+
-> WorkTable Scan on test test_1
849+
Output: (test_1.x + 1), ROW(int8inc((test_1.y)."*DEPTH*"), (test_1.x + 1))
850+
-> CTE Scan on test
851+
Output: test.x, test.y
852+
(10 rows)
853+
854+
with recursive test as (
855+
select 1 as x
856+
union all
857+
select x + 1
858+
from test
859+
) search breadth first by x set y
860+
select * from test limit 5;
861+
x | y
862+
---+-------
863+
1 | (0,1)
864+
2 | (1,2)
865+
3 | (2,3)
866+
4 | (3,4)
867+
5 | (4,5)
868+
(5 rows)
869+
793870
-- various syntax errors
794871
with recursive search_graph(f, t, label) as (
795872
select * from graph0 g
@@ -1132,6 +1209,49 @@ select * from search_graph;
11321209
2 | 3 | arc 2 -> 3 | N | {"(1,4)","(4,5)","(5,1)","(1,2)","(2,3)"}
11331210
(25 rows)
11341211

1212+
explain (verbose, costs off)
1213+
with recursive test as (
1214+
select 0 as x
1215+
union all
1216+
select (x + 1) % 10
1217+
from test
1218+
) cycle x set is_cycle using path
1219+
select * from test;
1220+
QUERY PLAN
1221+
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
1222+
CTE Scan on test
1223+
Output: test.x, test.is_cycle, test.path
1224+
CTE test
1225+
-> Recursive Union
1226+
-> Result
1227+
Output: 0, false, '{(0)}'::record[]
1228+
-> WorkTable Scan on test test_1
1229+
Output: ((test_1.x + 1) % 10), CASE WHEN (ROW(((test_1.x + 1) % 10)) = ANY (test_1.path)) THEN true ELSE false END, array_cat(test_1.path, ARRAY[ROW(((test_1.x + 1) % 10))])
1230+
Filter: (NOT test_1.is_cycle)
1231+
(9 rows)
1232+
1233+
with recursive test as (
1234+
select 0 as x
1235+
union all
1236+
select (x + 1) % 10
1237+
from test
1238+
) cycle x set is_cycle using path
1239+
select * from test;
1240+
x | is_cycle | path
1241+
---+----------+-----------------------------------------------
1242+
0 | f | {(0)}
1243+
1 | f | {(0),(1)}
1244+
2 | f | {(0),(1),(2)}
1245+
3 | f | {(0),(1),(2),(3)}
1246+
4 | f | {(0),(1),(2),(3),(4)}
1247+
5 | f | {(0),(1),(2),(3),(4),(5)}
1248+
6 | f | {(0),(1),(2),(3),(4),(5),(6)}
1249+
7 | f | {(0),(1),(2),(3),(4),(5),(6),(7)}
1250+
8 | f | {(0),(1),(2),(3),(4),(5),(6),(7),(8)}
1251+
9 | f | {(0),(1),(2),(3),(4),(5),(6),(7),(8),(9)}
1252+
0 | t | {(0),(1),(2),(3),(4),(5),(6),(7),(8),(9),(0)}
1253+
(11 rows)
1254+
11351255
-- multiple CTEs
11361256
with recursive
11371257
graph(f, t, label) as (

src/test/regress/sql/with.sql

+52
Original file line numberDiff line numberDiff line change
@@ -414,6 +414,41 @@ with recursive search_graph(f, t, label) as (
414414
) search breadth first by f, t set seq
415415
select * from search_graph order by seq;
416416

417+
-- a constant initial value causes issues for EXPLAIN
418+
explain (verbose, costs off)
419+
with recursive test as (
420+
select 1 as x
421+
union all
422+
select x + 1
423+
from test
424+
) search depth first by x set y
425+
select * from test limit 5;
426+
427+
with recursive test as (
428+
select 1 as x
429+
union all
430+
select x + 1
431+
from test
432+
) search depth first by x set y
433+
select * from test limit 5;
434+
435+
explain (verbose, costs off)
436+
with recursive test as (
437+
select 1 as x
438+
union all
439+
select x + 1
440+
from test
441+
) search breadth first by x set y
442+
select * from test limit 5;
443+
444+
with recursive test as (
445+
select 1 as x
446+
union all
447+
select x + 1
448+
from test
449+
) search breadth first by x set y
450+
select * from test limit 5;
451+
417452
-- various syntax errors
418453
with recursive search_graph(f, t, label) as (
419454
select * from graph0 g
@@ -561,6 +596,23 @@ with recursive search_graph(f, t, label) as (
561596
) cycle f, t set is_cycle to 'Y' default 'N' using path
562597
select * from search_graph;
563598

599+
explain (verbose, costs off)
600+
with recursive test as (
601+
select 0 as x
602+
union all
603+
select (x + 1) % 10
604+
from test
605+
) cycle x set is_cycle using path
606+
select * from test;
607+
608+
with recursive test as (
609+
select 0 as x
610+
union all
611+
select (x + 1) % 10
612+
from test
613+
) cycle x set is_cycle using path
614+
select * from test;
615+
564616
-- multiple CTEs
565617
with recursive
566618
graph(f, t, label) as (

0 commit comments

Comments
 (0)