]> git.ipfire.org Git - thirdparty/postgresql.git/commitdiff
Factor out constructSetOpTargetlist() from transformSetOperationTree()
authorPeter Eisentraut <peter@eisentraut.org>
Fri, 13 Mar 2026 15:08:09 +0000 (16:08 +0100)
committerPeter Eisentraut <peter@eisentraut.org>
Fri, 13 Mar 2026 15:16:40 +0000 (16:16 +0100)
This would be used separately by a future patch.  It also makes a
little smaller.

Author: Ashutosh Bapat <ashutosh.bapat.oss@gmail.com>
Discussion: https://www.postgresql.org/message-id/flat/a855795d-e697-4fa5-8698-d20122126567@eisentraut.org

src/backend/parser/analyze.c

index 539c16c4f794ed7803c38279403953650c6da7e9..15cc6240203ef7fb7fc0633e64ac5965664eabc1 100644 (file)
@@ -79,6 +79,9 @@ static Query *transformValuesClause(ParseState *pstate, SelectStmt *stmt);
 static Query *transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt);
 static Node *transformSetOperationTree(ParseState *pstate, SelectStmt *stmt,
                                                                           bool isTopLevel, List **targetlist);
+static void constructSetOpTargetlist(ParseState *pstate, SetOperationStmt *op,
+                                                                        const List *ltargetlist, const List *rtargetlist,
+                                                                        List **targetlist, const char *context, bool recursive);
 static void determineRecursiveColTypes(ParseState *pstate,
                                                                           Node *larg, List *nrtargetlist);
 static Query *transformReturnStmt(ParseState *pstate, ReturnStmt *stmt);
@@ -2142,7 +2145,6 @@ transformSetOperationTree(ParseState *pstate, SelectStmt *stmt,
                Query      *selectQuery;
                ParseNamespaceItem *nsitem;
                RangeTblRef *rtr;
-               ListCell   *tl;
 
                /*
                 * Transform SelectStmt into a Query.
@@ -2182,6 +2184,8 @@ transformSetOperationTree(ParseState *pstate, SelectStmt *stmt,
                 */
                if (targetlist)
                {
+                       ListCell   *tl;
+
                        *targetlist = NIL;
                        foreach(tl, selectQuery->targetList)
                        {
@@ -2214,8 +2218,6 @@ transformSetOperationTree(ParseState *pstate, SelectStmt *stmt,
                SetOperationStmt *op = makeNode(SetOperationStmt);
                List       *ltargetlist;
                List       *rtargetlist;
-               ListCell   *ltl;
-               ListCell   *rtl;
                const char *context;
                bool            recursive = (pstate->p_parent_cte &&
                                                                 pstate->p_parent_cte->cterecursive);
@@ -2250,161 +2252,182 @@ transformSetOperationTree(ParseState *pstate, SelectStmt *stmt,
                                                                                         false,
                                                                                         &rtargetlist);
 
-               /*
-                * Verify that the two children have the same number of non-junk
-                * columns, and determine the types of the merged output columns.
-                */
-               if (list_length(ltargetlist) != list_length(rtargetlist))
-                       ereport(ERROR,
-                                       (errcode(ERRCODE_SYNTAX_ERROR),
-                                        errmsg("each %s query must have the same number of columns",
-                                                       context),
-                                        parser_errposition(pstate,
-                                                                               exprLocation((Node *) rtargetlist))));
+               constructSetOpTargetlist(pstate, op, ltargetlist, rtargetlist, targetlist,
+                                                                context, recursive);
 
-               if (targetlist)
-                       *targetlist = NIL;
-               op->colTypes = NIL;
-               op->colTypmods = NIL;
-               op->colCollations = NIL;
-               op->groupClauses = NIL;
-               forboth(ltl, ltargetlist, rtl, rtargetlist)
-               {
-                       TargetEntry *ltle = (TargetEntry *) lfirst(ltl);
-                       TargetEntry *rtle = (TargetEntry *) lfirst(rtl);
-                       Node       *lcolnode = (Node *) ltle->expr;
-                       Node       *rcolnode = (Node *) rtle->expr;
-                       Oid                     lcoltype = exprType(lcolnode);
-                       Oid                     rcoltype = exprType(rcolnode);
-                       Node       *bestexpr;
-                       int                     bestlocation;
-                       Oid                     rescoltype;
-                       int32           rescoltypmod;
-                       Oid                     rescolcoll;
-
-                       /* select common type, same as CASE et al */
-                       rescoltype = select_common_type(pstate,
-                                                                                       list_make2(lcolnode, rcolnode),
-                                                                                       context,
-                                                                                       &bestexpr);
-                       bestlocation = exprLocation(bestexpr);
+               return (Node *) op;
+       }
+}
 
-                       /*
-                        * Verify the coercions are actually possible.  If not, we'd fail
-                        * later anyway, but we want to fail now while we have sufficient
-                        * context to produce an error cursor position.
-                        *
-                        * For all non-UNKNOWN-type cases, we verify coercibility but we
-                        * don't modify the child's expression, for fear of changing the
-                        * child query's semantics.
-                        *
-                        * If a child expression is an UNKNOWN-type Const or Param, we
-                        * want to replace it with the coerced expression.  This can only
-                        * happen when the child is a leaf set-op node.  It's safe to
-                        * replace the expression because if the child query's semantics
-                        * depended on the type of this output column, it'd have already
-                        * coerced the UNKNOWN to something else.  We want to do this
-                        * because (a) we want to verify that a Const is valid for the
-                        * target type, or resolve the actual type of an UNKNOWN Param,
-                        * and (b) we want to avoid unnecessary discrepancies between the
-                        * output type of the child query and the resolved target type.
-                        * Such a discrepancy would disable optimization in the planner.
-                        *
-                        * If it's some other UNKNOWN-type node, eg a Var, we do nothing
-                        * (knowing that coerce_to_common_type would fail).  The planner
-                        * is sometimes able to fold an UNKNOWN Var to a constant before
-                        * it has to coerce the type, so failing now would just break
-                        * cases that might work.
-                        */
-                       if (lcoltype != UNKNOWNOID)
-                               lcolnode = coerce_to_common_type(pstate, lcolnode,
-                                                                                                rescoltype, context);
-                       else if (IsA(lcolnode, Const) ||
-                                        IsA(lcolnode, Param))
-                       {
-                               lcolnode = coerce_to_common_type(pstate, lcolnode,
-                                                                                                rescoltype, context);
-                               ltle->expr = (Expr *) lcolnode;
-                       }
+/*
+ * constructSetOpTargetlist
+ *             Compute the types, typmods and collations of the columns in the target
+ *             list of the given set operation.
+ *
+ * For every pair of columns in the targetlists of the children, compute the
+ * common type, typmod, and collation representing the output (UNION) column.
+ * If targetlist is not NULL, also build the dummy output targetlist
+ * containing non-resjunk output columns.  The values are stored into the
+ * given SetOperationStmt node.  context is a string for error messages
+ * ("UNION" etc.).  recursive is true if it is a recursive union.
+ */
+static void
+constructSetOpTargetlist(ParseState *pstate, SetOperationStmt *op,
+                                                const List *ltargetlist, const List *rtargetlist,
+                                                List **targetlist, const char *context, bool recursive)
+{
+       ListCell   *ltl;
+       ListCell   *rtl;
 
-                       if (rcoltype != UNKNOWNOID)
-                               rcolnode = coerce_to_common_type(pstate, rcolnode,
-                                                                                                rescoltype, context);
-                       else if (IsA(rcolnode, Const) ||
-                                        IsA(rcolnode, Param))
-                       {
-                               rcolnode = coerce_to_common_type(pstate, rcolnode,
-                                                                                                rescoltype, context);
-                               rtle->expr = (Expr *) rcolnode;
-                       }
+       /*
+        * Verify that the two children have the same number of non-junk columns,
+        * and determine the types of the merged output columns.
+        */
+       if (list_length(ltargetlist) != list_length(rtargetlist))
+               ereport(ERROR,
+                               (errcode(ERRCODE_SYNTAX_ERROR),
+                                errmsg("each %s query must have the same number of columns",
+                                               context),
+                                parser_errposition(pstate,
+                                                                       exprLocation((Node *) rtargetlist))));
 
-                       rescoltypmod = select_common_typmod(pstate,
-                                                                                               list_make2(lcolnode, rcolnode),
-                                                                                               rescoltype);
+       if (targetlist)
+               *targetlist = NIL;
+       op->colTypes = NIL;
+       op->colTypmods = NIL;
+       op->colCollations = NIL;
+       op->groupClauses = NIL;
 
-                       /*
-                        * Select common collation.  A common collation is required for
-                        * all set operators except UNION ALL; see SQL:2008 7.13 <query
-                        * expression> Syntax Rule 15c.  (If we fail to identify a common
-                        * collation for a UNION ALL column, the colCollations element
-                        * will be set to InvalidOid, which may result in a runtime error
-                        * if something at a higher query level wants to use the column's
-                        * collation.)
-                        */
-                       rescolcoll = select_common_collation(pstate,
-                                                                                                list_make2(lcolnode, rcolnode),
-                                                                                                (op->op == SETOP_UNION && op->all));
+       forboth(ltl, ltargetlist, rtl, rtargetlist)
+       {
+               TargetEntry *ltle = (TargetEntry *) lfirst(ltl);
+               TargetEntry *rtle = (TargetEntry *) lfirst(rtl);
+               Node       *lcolnode = (Node *) ltle->expr;
+               Node       *rcolnode = (Node *) rtle->expr;
+               Oid                     lcoltype = exprType(lcolnode);
+               Oid                     rcoltype = exprType(rcolnode);
+               Node       *bestexpr;
+               int                     bestlocation;
+               Oid                     rescoltype;
+               int32           rescoltypmod;
+               Oid                     rescolcoll;
+
+               /* select common type, same as CASE et al */
+               rescoltype = select_common_type(pstate,
+                                                                               list_make2(lcolnode, rcolnode),
+                                                                               context,
+                                                                               &bestexpr);
+               bestlocation = exprLocation(bestexpr);
+
+               /*
+                * Verify the coercions are actually possible.  If not, we'd fail
+                * later anyway, but we want to fail now while we have sufficient
+                * context to produce an error cursor position.
+                *
+                * For all non-UNKNOWN-type cases, we verify coercibility but we don't
+                * modify the child's expression, for fear of changing the child
+                * query's semantics.
+                *
+                * If a child expression is an UNKNOWN-type Const or Param, we want to
+                * replace it with the coerced expression.  This can only happen when
+                * the child is a leaf set-op node.  It's safe to replace the
+                * expression because if the child query's semantics depended on the
+                * type of this output column, it'd have already coerced the UNKNOWN
+                * to something else.  We want to do this because (a) we want to
+                * verify that a Const is valid for the target type, or resolve the
+                * actual type of an UNKNOWN Param, and (b) we want to avoid
+                * unnecessary discrepancies between the output type of the child
+                * query and the resolved target type. Such a discrepancy would
+                * disable optimization in the planner.
+                *
+                * If it's some other UNKNOWN-type node, eg a Var, we do nothing
+                * (knowing that coerce_to_common_type would fail).  The planner is
+                * sometimes able to fold an UNKNOWN Var to a constant before it has
+                * to coerce the type, so failing now would just break cases that
+                * might work.
+                */
+               if (lcoltype != UNKNOWNOID)
+                       lcolnode = coerce_to_common_type(pstate, lcolnode,
+                                                                                        rescoltype, context);
+               else if (IsA(lcolnode, Const) ||
+                                IsA(lcolnode, Param))
+               {
+                       lcolnode = coerce_to_common_type(pstate, lcolnode,
+                                                                                        rescoltype, context);
+                       ltle->expr = (Expr *) lcolnode;
+               }
 
-                       /* emit results */
-                       op->colTypes = lappend_oid(op->colTypes, rescoltype);
-                       op->colTypmods = lappend_int(op->colTypmods, rescoltypmod);
-                       op->colCollations = lappend_oid(op->colCollations, rescolcoll);
+               if (rcoltype != UNKNOWNOID)
+                       rcolnode = coerce_to_common_type(pstate, rcolnode,
+                                                                                        rescoltype, context);
+               else if (IsA(rcolnode, Const) ||
+                                IsA(rcolnode, Param))
+               {
+                       rcolnode = coerce_to_common_type(pstate, rcolnode,
+                                                                                        rescoltype, context);
+                       rtle->expr = (Expr *) rcolnode;
+               }
 
-                       /*
-                        * For all cases except UNION ALL, identify the grouping operators
-                        * (and, if available, sorting operators) that will be used to
-                        * eliminate duplicates.
-                        */
-                       if (op->op != SETOP_UNION || !op->all)
-                       {
-                               ParseCallbackState pcbstate;
+               rescoltypmod = select_common_typmod(pstate,
+                                                                                       list_make2(lcolnode, rcolnode),
+                                                                                       rescoltype);
 
-                               setup_parser_errposition_callback(&pcbstate, pstate,
-                                                                                                 bestlocation);
+               /*
+                * Select common collation.  A common collation is required for all
+                * set operators except UNION ALL; see SQL:2008 7.13 <query
+                * expression> Syntax Rule 15c.  (If we fail to identify a common
+                * collation for a UNION ALL column, the colCollations element will be
+                * set to InvalidOid, which may result in a runtime error if something
+                * at a higher query level wants to use the column's collation.)
+                */
+               rescolcoll = select_common_collation(pstate,
+                                                                                        list_make2(lcolnode, rcolnode),
+                                                                                        (op->op == SETOP_UNION && op->all));
 
-                               /*
-                                * If it's a recursive union, we need to require hashing
-                                * support.
-                                */
-                               op->groupClauses = lappend(op->groupClauses,
-                                                                                  makeSortGroupClauseForSetOp(rescoltype, recursive));
+               /* emit results */
+               op->colTypes = lappend_oid(op->colTypes, rescoltype);
+               op->colTypmods = lappend_int(op->colTypmods, rescoltypmod);
+               op->colCollations = lappend_oid(op->colCollations, rescolcoll);
 
-                               cancel_parser_errposition_callback(&pcbstate);
-                       }
+               /*
+                * For all cases except UNION ALL, identify the grouping operators
+                * (and, if available, sorting operators) that will be used to
+                * eliminate duplicates.
+                */
+               if (op->op != SETOP_UNION || !op->all)
+               {
+                       ParseCallbackState pcbstate;
 
-                       /*
-                        * Construct a dummy tlist entry to return.  We use a SetToDefault
-                        * node for the expression, since it carries exactly the fields
-                        * needed, but any other expression node type would do as well.
-                        */
-                       if (targetlist)
-                       {
-                               SetToDefault *rescolnode = makeNode(SetToDefault);
-                               TargetEntry *restle;
-
-                               rescolnode->typeId = rescoltype;
-                               rescolnode->typeMod = rescoltypmod;
-                               rescolnode->collation = rescolcoll;
-                               rescolnode->location = bestlocation;
-                               restle = makeTargetEntry((Expr *) rescolnode,
-                                                                                0, /* no need to set resno */
-                                                                                NULL,
-                                                                                false);
-                               *targetlist = lappend(*targetlist, restle);
-                       }
+                       setup_parser_errposition_callback(&pcbstate, pstate,
+                                                                                         bestlocation);
+
+                       /* If it's a recursive union, we need to require hashing support. */
+                       op->groupClauses = lappend(op->groupClauses,
+                                                                          makeSortGroupClauseForSetOp(rescoltype, recursive));
+
+                       cancel_parser_errposition_callback(&pcbstate);
                }
 
-               return (Node *) op;
+               /*
+                * Construct a dummy tlist entry to return.  We use a SetToDefault
+                * node for the expression, since it carries exactly the fields
+                * needed, but any other expression node type would do as well.
+                */
+               if (targetlist)
+               {
+                       SetToDefault *rescolnode = makeNode(SetToDefault);
+                       TargetEntry *restle;
+
+                       rescolnode->typeId = rescoltype;
+                       rescolnode->typeMod = rescoltypmod;
+                       rescolnode->collation = rescolcoll;
+                       rescolnode->location = bestlocation;
+                       restle = makeTargetEntry((Expr *) rescolnode,
+                                                                        0, /* no need to set resno */
+                                                                        NULL,
+                                                                        false);
+                       *targetlist = lappend(*targetlist, restle);
+               }
        }
 }