Track nesting depth correctly when drilling down into RECORD Vars.
authorTom Lane <[email protected]>
Fri, 15 Sep 2023 21:01:26 +0000 (17:01 -0400)
committerTom Lane <[email protected]>
Fri, 15 Sep 2023 21:01:26 +0000 (17:01 -0400)
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

src/backend/parser/parse_target.c
src/backend/utils/adt/ruleutils.c
src/test/regress/expected/rowtypes.out
src/test/regress/sql/rowtypes.sql

index 5a342b6fc7fa1b962555093baf45a79a4c325b17..e79f02f27121506bc55b167319c2d2dfbde7fd0d 100644 (file)
@@ -1471,7 +1471,8 @@ ExpandRowReference(ParseState *pstate, Node *expr,
  * drill down to find the ultimate defining expression and attempt to infer
  * the tupdesc from it.  We ereport if we can't determine the tupdesc.
  *
- * levelsup is an extra offset to interpret the Var's varlevelsup correctly.
+ * levelsup is an extra offset to interpret the Var's varlevelsup correctly
+ * when recursing.  Outside callers should pass zero.
  */
 TupleDesc
 expandRecordVariable(ParseState *pstate, Var *var, int levelsup)
@@ -1552,11 +1553,17 @@ expandRecordVariable(ParseState *pstate, Var *var, int levelsup)
                    /*
                     * Recurse into the sub-select to see what its Var refers
                     * to.  We have to build an additional level of ParseState
-                    * to keep in step with varlevelsup in the subselect.
+                    * to keep in step with varlevelsup in the subselect;
+                    * furthermore, the subquery RTE might be from an outer
+                    * query level, in which case the ParseState for the
+                    * subselect must have that outer level as parent.
                     */
-                   ParseState  mypstate;
+                   ParseState  mypstate = {0};
+                   Index       levelsup;
 
-                   MemSet(&mypstate, 0, sizeof(mypstate));
+                   /* this loop must work, since GetRTEByRangeTablePosn did */
+                   for (levelsup = 0; levelsup < netlevelsup; levelsup++)
+                       pstate = pstate->parentParseState;
                    mypstate.parentParseState = pstate;
                    mypstate.p_rtable = rte->subquery->rtable;
                    /* don't bother filling the rest of the fake pstate */
@@ -1607,12 +1614,11 @@ expandRecordVariable(ParseState *pstate, Var *var, int levelsup)
                     * Recurse into the CTE to see what its Var refers to. We
                     * have to build an additional level of ParseState to keep
                     * in step with varlevelsup in the CTE; furthermore it
-                    * could be an outer CTE.
+                    * could be an outer CTE (compare SUBQUERY case above).
                     */
-                   ParseState  mypstate;
+                   ParseState  mypstate = {0};
                    Index       levelsup;
 
-                   MemSet(&mypstate, 0, sizeof(mypstate));
                    /* this loop must work, since GetCTEForRTE did */
                    for (levelsup = 0;
                         levelsup < rte->ctelevelsup + netlevelsup;
index 90dd6ef25c672225b6da4acfa56380e767aa7a6d..475b2071a0dc71626a5cfb653bca05d51ef0146d 100644 (file)
@@ -7114,22 +7114,28 @@ get_name_for_var_field(Var *var, int fieldno,
                         * Recurse into the sub-select to see what its Var
                         * refers to. We have to build an additional level of
                         * namespace to keep in step with varlevelsup in the
-                        * subselect.
+                        * subselect; furthermore, the subquery RTE might be
+                        * from an outer query level, in which case the
+                        * namespace for the subselect must have that outer
+                        * level as parent namespace.
                         */
+                       List       *save_nslist = context->namespaces;
+                       List       *parent_namespaces;
                        deparse_namespace mydpns;
                        const char *result;
 
+                       parent_namespaces = list_copy_tail(context->namespaces,
+                                                          netlevelsup);
+
                        set_deparse_for_query(&mydpns, rte->subquery,
-                                             context->namespaces);
+                                             parent_namespaces);
 
-                       context->namespaces = lcons(&mydpns,
-                                                   context->namespaces);
+                       context->namespaces = lcons(&mydpns, parent_namespaces);
 
                        result = get_name_for_var_field((Var *) expr, fieldno,
                                                        0, context);
 
-                       context->namespaces =
-                           list_delete_first(context->namespaces);
+                       context->namespaces = save_nslist;
 
                        return result;
                    }
@@ -7221,7 +7227,7 @@ get_name_for_var_field(Var *var, int fieldno,
                                                        attnum);
 
                    if (ste == NULL || ste->resjunk)
-                       elog(ERROR, "subquery %s does not have attribute %d",
+                       elog(ERROR, "CTE %s does not have attribute %d",
                             rte->eref->aliasname, attnum);
                    expr = (Node *) ste->expr;
                    if (IsA(expr, Var))
@@ -7229,21 +7235,22 @@ get_name_for_var_field(Var *var, int fieldno,
                        /*
                         * Recurse into the CTE to see what its Var refers to.
                         * We have to build an additional level of namespace
-                        * to keep in step with varlevelsup in the CTE.
-                        * Furthermore it could be an outer CTE, so we may
-                        * have to delete some levels of namespace.
+                        * to keep in step with varlevelsup in the CTE;
+                        * furthermore it could be an outer CTE (compare
+                        * SUBQUERY case above).
                         */
                        List       *save_nslist = context->namespaces;
-                       List       *new_nslist;
+                       List       *parent_namespaces;
                        deparse_namespace mydpns;
                        const char *result;
 
+                       parent_namespaces = list_copy_tail(context->namespaces,
+                                                          ctelevelsup);
+
                        set_deparse_for_query(&mydpns, ctequery,
-                                             context->namespaces);
+                                             parent_namespaces);
 
-                       new_nslist = list_copy_tail(context->namespaces,
-                                                   ctelevelsup);
-                       context->namespaces = lcons(&mydpns, new_nslist);
+                       context->namespaces = lcons(&mydpns, parent_namespaces);
 
                        result = get_name_for_var_field((Var *) expr, fieldno,
                                                        0, context);
index 798a182e5720ed2158e48cc23f0d8c5ab4aaace6..48e14543b1f2e7e50cb337797316073d1071924f 100644 (file)
@@ -1093,6 +1093,69 @@ select r, r is null as isnull, r is not null as isnotnull from r;
  (,)         | t      | f
 (6 rows)
 
+--
+-- Check parsing of indirect references to composite values (bug #18077)
+--
+explain (verbose, costs off)
+with cte(c) as (select row(1, 2)),
+     cte2(c) as (select * from cte)
+select * from cte2 as t
+where (select * from (select c as c1) s
+       where (select (c1).f1 > 0)) is not null;
+                QUERY PLAN                
+------------------------------------------
+ CTE Scan on cte2 t
+   Output: t.c
+   Filter: ((SubPlan 4) IS NOT NULL)
+   CTE cte
+     ->  Result
+           Output: '(1,2)'::record
+   CTE cte2
+     ->  CTE Scan on cte
+           Output: cte.c
+   SubPlan 4
+     ->  Result
+           Output: t.c
+           One-Time Filter: $3
+           InitPlan 3 (returns $3)
+             ->  Result
+                   Output: ((t.c).f1 > 0)
+(16 rows)
+
+with cte(c) as (select row(1, 2)),
+     cte2(c) as (select * from cte)
+select * from cte2 as t
+where (select * from (select c as c1) s
+       where (select (c1).f1 > 0)) is not null;
+   c   
+-------
+ (1,2)
+(1 row)
+
+-- Also check deparsing of such cases
+create view composite_v as
+with cte(c) as (select row(1, 2)),
+     cte2(c) as (select * from cte)
+select 1 as one from cte2 as t
+where (select * from (select c as c1) s
+       where (select (c1).f1 > 0)) is not null;
+select pg_get_viewdef('composite_v', true);
+                     pg_get_viewdef                     
+--------------------------------------------------------
+  WITH cte(c) AS (                                     +
+          SELECT ROW(1, 2) AS "row"                    +
+         ), cte2(c) AS (                               +
+          SELECT cte.c                                 +
+            FROM cte                                   +
+         )                                             +
+  SELECT 1 AS one                                      +
+    FROM cte2 t                                        +
+   WHERE (( SELECT s.c1                                +
+            FROM ( SELECT t.c AS c1) s                 +
+           WHERE ( SELECT (s.c1).f1 > 0))) IS NOT NULL;
+(1 row)
+
+drop view composite_v;
 --
 -- Tests for component access / FieldSelect
 --
index 67a748f82e662207c6a7296c5cad89c30153bddf..f66ec10056d75b116f9cf593d7e46c6f47be1bb8 100644 (file)
@@ -450,6 +450,31 @@ with r(a,b) as
           (null,row(1,2)), (null,row(null,null)), (null,null) )
 select r, r is null as isnull, r is not null as isnotnull from r;
 
+--
+-- Check parsing of indirect references to composite values (bug #18077)
+--
+explain (verbose, costs off)
+with cte(c) as (select row(1, 2)),
+     cte2(c) as (select * from cte)
+select * from cte2 as t
+where (select * from (select c as c1) s
+       where (select (c1).f1 > 0)) is not null;
+
+with cte(c) as (select row(1, 2)),
+     cte2(c) as (select * from cte)
+select * from cte2 as t
+where (select * from (select c as c1) s
+       where (select (c1).f1 > 0)) is not null;
+
+-- Also check deparsing of such cases
+create view composite_v as
+with cte(c) as (select row(1, 2)),
+     cte2(c) as (select * from cte)
+select 1 as one from cte2 as t
+where (select * from (select c as c1) s
+       where (select (c1).f1 > 0)) is not null;
+select pg_get_viewdef('composite_v', true);
+drop view composite_v;
 
 --
 -- Tests for component access / FieldSelect