Skip to content

Commit 800f3c8

Browse files
tglsfdcpull[bot]
authored andcommitted
Track nesting depth correctly when drilling down into RECORD Vars.
expandRecordVariable() failed to adjust the parse nesting structure correctly when recursing to inspect an outer-level Var. This could result in assertion failures or core dumps in corner cases. Likewise, get_name_for_var_field() failed to adjust the deparse namespace stack correctly when recursing to inspect an outer-level Var. In this case the likely result was a "bogus varno" error while deparsing a view. Per bug #18077 from Jingzhou Fu. Back-patch to all supported branches. Richard Guo, with some adjustments by me Discussion: https://postgr.es/m/18077-b9db97c6e0ab45d8@postgresql.org
1 parent a792392 commit 800f3c8

File tree

4 files changed

+119
-20
lines changed

4 files changed

+119
-20
lines changed

src/backend/parser/parse_target.c

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1499,7 +1499,8 @@ ExpandRowReference(ParseState *pstate, Node *expr,
14991499
* drill down to find the ultimate defining expression and attempt to infer
15001500
* the tupdesc from it. We ereport if we can't determine the tupdesc.
15011501
*
1502-
* levelsup is an extra offset to interpret the Var's varlevelsup correctly.
1502+
* levelsup is an extra offset to interpret the Var's varlevelsup correctly
1503+
* when recursing. Outside callers should pass zero.
15031504
*/
15041505
TupleDesc
15051506
expandRecordVariable(ParseState *pstate, Var *var, int levelsup)
@@ -1587,10 +1588,17 @@ expandRecordVariable(ParseState *pstate, Var *var, int levelsup)
15871588
/*
15881589
* Recurse into the sub-select to see what its Var refers
15891590
* to. We have to build an additional level of ParseState
1590-
* to keep in step with varlevelsup in the subselect.
1591+
* to keep in step with varlevelsup in the subselect;
1592+
* furthermore, the subquery RTE might be from an outer
1593+
* query level, in which case the ParseState for the
1594+
* subselect must have that outer level as parent.
15911595
*/
15921596
ParseState mypstate = {0};
1597+
Index levelsup;
15931598

1599+
/* this loop must work, since GetRTEByRangeTablePosn did */
1600+
for (levelsup = 0; levelsup < netlevelsup; levelsup++)
1601+
pstate = pstate->parentParseState;
15941602
mypstate.parentParseState = pstate;
15951603
mypstate.p_rtable = rte->subquery->rtable;
15961604
/* don't bother filling the rest of the fake pstate */
@@ -1641,12 +1649,11 @@ expandRecordVariable(ParseState *pstate, Var *var, int levelsup)
16411649
* Recurse into the CTE to see what its Var refers to. We
16421650
* have to build an additional level of ParseState to keep
16431651
* in step with varlevelsup in the CTE; furthermore it
1644-
* could be an outer CTE.
1652+
* could be an outer CTE (compare SUBQUERY case above).
16451653
*/
1646-
ParseState mypstate;
1654+
ParseState mypstate = {0};
16471655
Index levelsup;
16481656

1649-
MemSet(&mypstate, 0, sizeof(mypstate));
16501657
/* this loop must work, since GetCTEForRTE did */
16511658
for (levelsup = 0;
16521659
levelsup < rte->ctelevelsup + netlevelsup;

src/backend/utils/adt/ruleutils.c

Lines changed: 22 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -7820,22 +7820,28 @@ get_name_for_var_field(Var *var, int fieldno,
78207820
* Recurse into the sub-select to see what its Var
78217821
* refers to. We have to build an additional level of
78227822
* namespace to keep in step with varlevelsup in the
7823-
* subselect.
7823+
* subselect; furthermore, the subquery RTE might be
7824+
* from an outer query level, in which case the
7825+
* namespace for the subselect must have that outer
7826+
* level as parent namespace.
78247827
*/
7828+
List *save_nslist = context->namespaces;
7829+
List *parent_namespaces;
78257830
deparse_namespace mydpns;
78267831
const char *result;
78277832

7833+
parent_namespaces = list_copy_tail(context->namespaces,
7834+
netlevelsup);
7835+
78287836
set_deparse_for_query(&mydpns, rte->subquery,
7829-
context->namespaces);
7837+
parent_namespaces);
78307838

7831-
context->namespaces = lcons(&mydpns,
7832-
context->namespaces);
7839+
context->namespaces = lcons(&mydpns, parent_namespaces);
78337840

78347841
result = get_name_for_var_field((Var *) expr, fieldno,
78357842
0, context);
78367843

7837-
context->namespaces =
7838-
list_delete_first(context->namespaces);
7844+
context->namespaces = save_nslist;
78397845

78407846
return result;
78417847
}
@@ -7927,29 +7933,30 @@ get_name_for_var_field(Var *var, int fieldno,
79277933
attnum);
79287934

79297935
if (ste == NULL || ste->resjunk)
7930-
elog(ERROR, "subquery %s does not have attribute %d",
7936+
elog(ERROR, "CTE %s does not have attribute %d",
79317937
rte->eref->aliasname, attnum);
79327938
expr = (Node *) ste->expr;
79337939
if (IsA(expr, Var))
79347940
{
79357941
/*
79367942
* Recurse into the CTE to see what its Var refers to.
79377943
* We have to build an additional level of namespace
7938-
* to keep in step with varlevelsup in the CTE.
7939-
* Furthermore it could be an outer CTE, so we may
7940-
* have to delete some levels of namespace.
7944+
* to keep in step with varlevelsup in the CTE;
7945+
* furthermore it could be an outer CTE (compare
7946+
* SUBQUERY case above).
79417947
*/
79427948
List *save_nslist = context->namespaces;
7943-
List *new_nslist;
7949+
List *parent_namespaces;
79447950
deparse_namespace mydpns;
79457951
const char *result;
79467952

7953+
parent_namespaces = list_copy_tail(context->namespaces,
7954+
ctelevelsup);
7955+
79477956
set_deparse_for_query(&mydpns, ctequery,
7948-
context->namespaces);
7957+
parent_namespaces);
79497958

7950-
new_nslist = list_copy_tail(context->namespaces,
7951-
ctelevelsup);
7952-
context->namespaces = lcons(&mydpns, new_nslist);
7959+
context->namespaces = lcons(&mydpns, parent_namespaces);
79537960

79547961
result = get_name_for_var_field((Var *) expr, fieldno,
79557962
0, context);

src/test/regress/expected/rowtypes.out

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1240,6 +1240,66 @@ select r, r is null as isnull, r is not null as isnotnull from r;
12401240
(,) | t | f
12411241
(6 rows)
12421242

1243+
--
1244+
-- Check parsing of indirect references to composite values (bug #18077)
1245+
--
1246+
explain (verbose, costs off)
1247+
with cte(c) as materialized (select row(1, 2)),
1248+
cte2(c) as (select * from cte)
1249+
select * from cte2 as t
1250+
where (select * from (select c as c1) s
1251+
where (select (c1).f1 > 0)) is not null;
1252+
QUERY PLAN
1253+
--------------------------------------------
1254+
CTE Scan on cte
1255+
Output: cte.c
1256+
Filter: ((SubPlan 3) IS NOT NULL)
1257+
CTE cte
1258+
-> Result
1259+
Output: '(1,2)'::record
1260+
SubPlan 3
1261+
-> Result
1262+
Output: cte.c
1263+
One-Time Filter: $2
1264+
InitPlan 2 (returns $2)
1265+
-> Result
1266+
Output: ((cte.c).f1 > 0)
1267+
(13 rows)
1268+
1269+
with cte(c) as materialized (select row(1, 2)),
1270+
cte2(c) as (select * from cte)
1271+
select * from cte2 as t
1272+
where (select * from (select c as c1) s
1273+
where (select (c1).f1 > 0)) is not null;
1274+
c
1275+
-------
1276+
(1,2)
1277+
(1 row)
1278+
1279+
-- Also check deparsing of such cases
1280+
create view composite_v as
1281+
with cte(c) as materialized (select row(1, 2)),
1282+
cte2(c) as (select * from cte)
1283+
select 1 as one from cte2 as t
1284+
where (select * from (select c as c1) s
1285+
where (select (c1).f1 > 0)) is not null;
1286+
select pg_get_viewdef('composite_v', true);
1287+
pg_get_viewdef
1288+
--------------------------------------------------------
1289+
WITH cte(c) AS MATERIALIZED ( +
1290+
SELECT ROW(1, 2) AS "row" +
1291+
), cte2(c) AS ( +
1292+
SELECT cte.c +
1293+
FROM cte +
1294+
) +
1295+
SELECT 1 AS one +
1296+
FROM cte2 t +
1297+
WHERE (( SELECT s.c1 +
1298+
FROM ( SELECT t.c AS c1) s +
1299+
WHERE ( SELECT (s.c1).f1 > 0))) IS NOT NULL;
1300+
(1 row)
1301+
1302+
drop view composite_v;
12431303
--
12441304
-- Tests for component access / FieldSelect
12451305
--

src/test/regress/sql/rowtypes.sql

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -494,6 +494,31 @@ with r(a,b) as materialized
494494
(null,row(1,2)), (null,row(null,null)), (null,null) )
495495
select r, r is null as isnull, r is not null as isnotnull from r;
496496

497+
--
498+
-- Check parsing of indirect references to composite values (bug #18077)
499+
--
500+
explain (verbose, costs off)
501+
with cte(c) as materialized (select row(1, 2)),
502+
cte2(c) as (select * from cte)
503+
select * from cte2 as t
504+
where (select * from (select c as c1) s
505+
where (select (c1).f1 > 0)) is not null;
506+
507+
with cte(c) as materialized (select row(1, 2)),
508+
cte2(c) as (select * from cte)
509+
select * from cte2 as t
510+
where (select * from (select c as c1) s
511+
where (select (c1).f1 > 0)) is not null;
512+
513+
-- Also check deparsing of such cases
514+
create view composite_v as
515+
with cte(c) as materialized (select row(1, 2)),
516+
cte2(c) as (select * from cte)
517+
select 1 as one from cte2 as t
518+
where (select * from (select c as c1) s
519+
where (select (c1).f1 > 0)) is not null;
520+
select pg_get_viewdef('composite_v', true);
521+
drop view composite_v;
497522

498523
--
499524
-- Tests for component access / FieldSelect

0 commit comments

Comments
 (0)