Skip to content

Commit d29812c

Browse files
committed
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 8a15b41 commit d29812c

File tree

4 files changed

+120
-22
lines changed

4 files changed

+120
-22
lines changed

src/backend/parser/parse_target.c

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1472,7 +1472,8 @@ ExpandRowReference(ParseState *pstate, Node *expr,
14721472
* drill down to find the ultimate defining expression and attempt to infer
14731473
* the tupdesc from it. We ereport if we can't determine the tupdesc.
14741474
*
1475-
* levelsup is an extra offset to interpret the Var's varlevelsup correctly.
1475+
* levelsup is an extra offset to interpret the Var's varlevelsup correctly
1476+
* when recursing. Outside callers should pass zero.
14761477
*/
14771478
TupleDesc
14781479
expandRecordVariable(ParseState *pstate, Var *var, int levelsup)
@@ -1554,11 +1555,17 @@ expandRecordVariable(ParseState *pstate, Var *var, int levelsup)
15541555
/*
15551556
* Recurse into the sub-select to see what its Var refers
15561557
* to. We have to build an additional level of ParseState
1557-
* to keep in step with varlevelsup in the subselect.
1558+
* to keep in step with varlevelsup in the subselect;
1559+
* furthermore, the subquery RTE might be from an outer
1560+
* query level, in which case the ParseState for the
1561+
* subselect must have that outer level as parent.
15581562
*/
1559-
ParseState mypstate;
1563+
ParseState mypstate = {0};
1564+
Index levelsup;
15601565

1561-
MemSet(&mypstate, 0, sizeof(mypstate));
1566+
/* this loop must work, since GetRTEByRangeTablePosn did */
1567+
for (levelsup = 0; levelsup < netlevelsup; levelsup++)
1568+
pstate = pstate->parentParseState;
15621569
mypstate.parentParseState = pstate;
15631570
mypstate.p_rtable = rte->subquery->rtable;
15641571
/* don't bother filling the rest of the fake pstate */
@@ -1609,12 +1616,11 @@ expandRecordVariable(ParseState *pstate, Var *var, int levelsup)
16091616
* Recurse into the CTE to see what its Var refers to. We
16101617
* have to build an additional level of ParseState to keep
16111618
* in step with varlevelsup in the CTE; furthermore it
1612-
* could be an outer CTE.
1619+
* could be an outer CTE (compare SUBQUERY case above).
16131620
*/
1614-
ParseState mypstate;
1621+
ParseState mypstate = {0};
16151622
Index levelsup;
16161623

1617-
MemSet(&mypstate, 0, sizeof(mypstate));
16181624
/* this loop must work, since GetCTEForRTE did */
16191625
for (levelsup = 0;
16201626
levelsup < rte->ctelevelsup + netlevelsup;

src/backend/utils/adt/ruleutils.c

Lines changed: 22 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -7189,22 +7189,28 @@ get_name_for_var_field(Var *var, int fieldno,
71897189
* Recurse into the sub-select to see what its Var
71907190
* refers to. We have to build an additional level of
71917191
* namespace to keep in step with varlevelsup in the
7192-
* subselect.
7192+
* subselect; furthermore, the subquery RTE might be
7193+
* from an outer query level, in which case the
7194+
* namespace for the subselect must have that outer
7195+
* level as parent namespace.
71937196
*/
7197+
List *save_nslist = context->namespaces;
7198+
List *parent_namespaces;
71947199
deparse_namespace mydpns;
71957200
const char *result;
71967201

7202+
parent_namespaces = list_copy_tail(context->namespaces,
7203+
netlevelsup);
7204+
71977205
set_deparse_for_query(&mydpns, rte->subquery,
7198-
context->namespaces);
7206+
parent_namespaces);
71997207

7200-
context->namespaces = lcons(&mydpns,
7201-
context->namespaces);
7208+
context->namespaces = lcons(&mydpns, parent_namespaces);
72027209

72037210
result = get_name_for_var_field((Var *) expr, fieldno,
72047211
0, context);
72057212

7206-
context->namespaces =
7207-
list_delete_first(context->namespaces);
7213+
context->namespaces = save_nslist;
72087214

72097215
return result;
72107216
}
@@ -7296,29 +7302,30 @@ get_name_for_var_field(Var *var, int fieldno,
72967302
attnum);
72977303

72987304
if (ste == NULL || ste->resjunk)
7299-
elog(ERROR, "subquery %s does not have attribute %d",
7305+
elog(ERROR, "CTE %s does not have attribute %d",
73007306
rte->eref->aliasname, attnum);
73017307
expr = (Node *) ste->expr;
73027308
if (IsA(expr, Var))
73037309
{
73047310
/*
73057311
* Recurse into the CTE to see what its Var refers to.
73067312
* We have to build an additional level of namespace
7307-
* to keep in step with varlevelsup in the CTE.
7308-
* Furthermore it could be an outer CTE, so we may
7309-
* have to delete some levels of namespace.
7313+
* to keep in step with varlevelsup in the CTE;
7314+
* furthermore it could be an outer CTE (compare
7315+
* SUBQUERY case above).
73107316
*/
73117317
List *save_nslist = context->namespaces;
7312-
List *new_nslist;
7318+
List *parent_namespaces;
73137319
deparse_namespace mydpns;
73147320
const char *result;
73157321

7322+
parent_namespaces = list_copy_tail(context->namespaces,
7323+
ctelevelsup);
7324+
73167325
set_deparse_for_query(&mydpns, ctequery,
7317-
context->namespaces);
7326+
parent_namespaces);
73187327

7319-
new_nslist = list_copy_tail(context->namespaces,
7320-
ctelevelsup);
7321-
context->namespaces = lcons(&mydpns, new_nslist);
7328+
context->namespaces = lcons(&mydpns, parent_namespaces);
73227329

73237330
result = get_name_for_var_field((Var *) expr, fieldno,
73247331
0, context);

src/test/regress/expected/rowtypes.out

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

1206+
--
1207+
-- Check parsing of indirect references to composite values (bug #18077)
1208+
--
1209+
explain (verbose, costs off)
1210+
with cte(c) as materialized (select row(1, 2)),
1211+
cte2(c) as (select * from cte)
1212+
select * from cte2 as t
1213+
where (select * from (select c as c1) s
1214+
where (select (c1).f1 > 0)) is not null;
1215+
QUERY PLAN
1216+
--------------------------------------------
1217+
CTE Scan on cte
1218+
Output: cte.c
1219+
Filter: ((SubPlan 3) IS NOT NULL)
1220+
CTE cte
1221+
-> Result
1222+
Output: '(1,2)'::record
1223+
SubPlan 3
1224+
-> Result
1225+
Output: cte.c
1226+
One-Time Filter: $2
1227+
InitPlan 2 (returns $2)
1228+
-> Result
1229+
Output: ((cte.c).f1 > 0)
1230+
(13 rows)
1231+
1232+
with cte(c) as materialized (select row(1, 2)),
1233+
cte2(c) as (select * from cte)
1234+
select * from cte2 as t
1235+
where (select * from (select c as c1) s
1236+
where (select (c1).f1 > 0)) is not null;
1237+
c
1238+
-------
1239+
(1,2)
1240+
(1 row)
1241+
1242+
-- Also check deparsing of such cases
1243+
create view composite_v as
1244+
with cte(c) as materialized (select row(1, 2)),
1245+
cte2(c) as (select * from cte)
1246+
select 1 as one from cte2 as t
1247+
where (select * from (select c as c1) s
1248+
where (select (c1).f1 > 0)) is not null;
1249+
select pg_get_viewdef('composite_v', true);
1250+
pg_get_viewdef
1251+
--------------------------------------------------------
1252+
WITH cte(c) AS MATERIALIZED ( +
1253+
SELECT ROW(1, 2) AS "row" +
1254+
), cte2(c) AS ( +
1255+
SELECT cte.c +
1256+
FROM cte +
1257+
) +
1258+
SELECT 1 AS one +
1259+
FROM cte2 t +
1260+
WHERE (( SELECT s.c1 +
1261+
FROM ( SELECT t.c AS c1) s +
1262+
WHERE ( SELECT (s.c1).f1 > 0))) IS NOT NULL;
1263+
(1 row)
1264+
1265+
drop view composite_v;
12061266
--
12071267
-- Tests for component access / FieldSelect
12081268
--

src/test/regress/sql/rowtypes.sql

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

486+
--
487+
-- Check parsing of indirect references to composite values (bug #18077)
488+
--
489+
explain (verbose, costs off)
490+
with cte(c) as materialized (select row(1, 2)),
491+
cte2(c) as (select * from cte)
492+
select * from cte2 as t
493+
where (select * from (select c as c1) s
494+
where (select (c1).f1 > 0)) is not null;
495+
496+
with cte(c) as materialized (select row(1, 2)),
497+
cte2(c) as (select * from cte)
498+
select * from cte2 as t
499+
where (select * from (select c as c1) s
500+
where (select (c1).f1 > 0)) is not null;
501+
502+
-- Also check deparsing of such cases
503+
create view composite_v as
504+
with cte(c) as materialized (select row(1, 2)),
505+
cte2(c) as (select * from cte)
506+
select 1 as one from cte2 as t
507+
where (select * from (select c as c1) s
508+
where (select (c1).f1 > 0)) is not null;
509+
select pg_get_viewdef('composite_v', true);
510+
drop view composite_v;
486511

487512
--
488513
-- Tests for component access / FieldSelect

0 commit comments

Comments
 (0)