Make pg_get_expr() more bulletproof.
authorTom Lane <tgl@sss.pgh.pa.us>
Sun, 9 Jan 2022 17:43:09 +0000 (12:43 -0500)
committerTom Lane <tgl@sss.pgh.pa.us>
Sun, 9 Jan 2022 17:43:09 +0000 (12:43 -0500)
Since this function is defined to accept pg_node_tree values, it could
get applied to any nodetree that can appear in a cataloged pg_node_tree
column.  Some such cases can't be supported --- for example, its API
doesn't allow providing referents for more than one relation --- but
we should try to throw a user-facing error rather than an internal
error when encountering such a case.

In support of this, extend expression_tree_walker/mutator to be sure
they'll work on any such node tree (which basically means adding
support for relpartbound node types).  That allows us to run pull_varnos
and check for the case of multiple relations before we start processing
the tree.  The alternative of changing the low-level error thrown for an
out-of-range varno isn't appealing, because that could mask actual bugs
in other usages of ruleutils.

Per report from Justin Pryzby.  This is basically cosmetic, so no
back-patch.

Discussion: https://postgr.es/m/20211219205422.GT17618@telsasoft.com

src/backend/nodes/nodeFuncs.c
src/backend/optimizer/util/var.c
src/backend/utils/adt/ruleutils.c

index 7d1a3aff8c1b2f979e532ca25b26ca57b245b42f..acc17da7177b80b6612dd13c6168837a2f4b131d 100644 (file)
@@ -2201,6 +2201,26 @@ expression_tree_walker(Node *node,
                    return true;
            }
            break;
+       case T_PartitionBoundSpec:
+           {
+               PartitionBoundSpec *pbs = (PartitionBoundSpec *) node;
+
+               if (walker(pbs->listdatums, context))
+                   return true;
+               if (walker(pbs->lowerdatums, context))
+                   return true;
+               if (walker(pbs->upperdatums, context))
+                   return true;
+           }
+           break;
+       case T_PartitionRangeDatum:
+           {
+               PartitionRangeDatum *prd = (PartitionRangeDatum *) node;
+
+               if (walker(prd->value, context))
+                   return true;
+           }
+           break;
        case T_List:
            foreach(temp, (List *) node)
            {
@@ -3092,6 +3112,28 @@ expression_tree_mutator(Node *node,
                return (Node *) newnode;
            }
            break;
+       case T_PartitionBoundSpec:
+           {
+               PartitionBoundSpec *pbs = (PartitionBoundSpec *) node;
+               PartitionBoundSpec *newnode;
+
+               FLATCOPY(newnode, pbs, PartitionBoundSpec);
+               MUTATE(newnode->listdatums, pbs->listdatums, List *);
+               MUTATE(newnode->lowerdatums, pbs->lowerdatums, List *);
+               MUTATE(newnode->upperdatums, pbs->upperdatums, List *);
+               return (Node *) newnode;
+           }
+           break;
+       case T_PartitionRangeDatum:
+           {
+               PartitionRangeDatum *prd = (PartitionRangeDatum *) node;
+               PartitionRangeDatum *newnode;
+
+               FLATCOPY(newnode, prd, PartitionRangeDatum);
+               MUTATE(newnode->value, prd->value, Node *);
+               return (Node *) newnode;
+           }
+           break;
        case T_List:
            {
                /*
index ac048beea09eb67be8e191106140b6a75bd44e94..a0543b7f47b388f254db033fa5131585fec7a0dc 100644 (file)
@@ -88,6 +88,9 @@ static Relids alias_relid_set(Query *query, Relids relids);
  *     Create a set of all the distinct varnos present in a parsetree.
  *     Only varnos that reference level-zero rtable entries are considered.
  *
+ * "root" can be passed as NULL if it is not necessary to process
+ * PlaceHolderVars.
+ *
  * NOTE: this is used on not-yet-planned expressions.  It may therefore find
  * bare SubLinks, and if so it needs to recurse into them to look for uplevel
  * references to the desired rtable level! But when we find a completed
@@ -168,9 +171,13 @@ pull_varnos_walker(Node *node, pull_varnos_context *context)
        /*
         * If a PlaceHolderVar is not of the target query level, ignore it,
         * instead recursing into its expression to see if it contains any
-        * vars that are of the target level.
+        * vars that are of the target level.  We'll also do that when the
+        * caller doesn't pass a "root" pointer.  (We probably shouldn't see
+        * PlaceHolderVars at all in such cases, but if we do, this is a
+        * reasonable behavior.)
         */
-       if (phv->phlevelsup == context->sublevels_up)
+       if (phv->phlevelsup == context->sublevels_up &&
+           context->root != NULL)
        {
            /*
             * Ideally, the PHV's contribution to context->varnos is its
index 039f897331c863336fa3f7f397810c5913522920..63b85566822f827adb30941a9372f9de9d32afa5 100644 (file)
@@ -2561,6 +2561,12 @@ decompile_column_index_array(Datum column_index_array, Oid relId,
  * the one specified by the second parameter.  This is sufficient for
  * partial indexes, column default expressions, etc.  We also support
  * Var-free expressions, for which the OID can be InvalidOid.
+ *
+ * We expect this function to work, or throw a reasonably clean error,
+ * for any node tree that can appear in a catalog pg_node_tree column.
+ * Query trees, such as those appearing in pg_rewrite.ev_action, are
+ * not supported.  Nor are expressions in more than one relation, which
+ * can appear in places like pg_rewrite.ev_qual.
  * ----------
  */
 Datum
@@ -2622,11 +2628,13 @@ static text *
 pg_get_expr_worker(text *expr, Oid relid, const char *relname, int prettyFlags)
 {
    Node       *node;
+   Node       *tst;
+   Relids      relids;
    List       *context;
    char       *exprstr;
    char       *str;
 
-   /* Convert input TEXT object to C string */
+   /* Convert input pg_node_tree (really TEXT) object to C string */
    exprstr = text_to_cstring(expr);
 
    /* Convert expression to node tree */
@@ -2634,6 +2642,40 @@ pg_get_expr_worker(text *expr, Oid relid, const char *relname, int prettyFlags)
 
    pfree(exprstr);
 
+   /*
+    * Throw error if the input is a querytree rather than an expression tree.
+    * While we could support queries here, there seems no very good reason
+    * to.  In most such catalog columns, we'll see a List of Query nodes, or
+    * even nested Lists, so drill down to a non-List node before checking.
+    */
+   tst = node;
+   while (tst && IsA(tst, List))
+       tst = linitial((List *) tst);
+   if (tst && IsA(tst, Query))
+       ereport(ERROR,
+               (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+                errmsg("input is a query, not an expression")));
+
+   /*
+    * Throw error if the expression contains Vars we won't be able to
+    * deparse.
+    */
+   relids = pull_varnos(NULL, node);
+   if (OidIsValid(relid))
+   {
+       if (!bms_is_subset(relids, bms_make_singleton(1)))
+           ereport(ERROR,
+                   (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+                    errmsg("expression contains variables of more than one relation")));
+   }
+   else
+   {
+       if (!bms_is_empty(relids))
+           ereport(ERROR,
+                   (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+                    errmsg("expression contains variables")));
+   }
+
    /* Prepare deparse context if needed */
    if (OidIsValid(relid))
        context = deparse_context_for(relname, relid);