static Query *transformValuesClause(ParseState *pstate, SelectStmt *stmt);
static Query *transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt);
static Node *transformSetOperationTree(ParseState *pstate, SelectStmt *stmt,
- bool isTopLevel, List **colInfo);
+ bool isTopLevel, List **targetlist);
static void determineRecursiveColTypes(ParseState *pstate,
- Node *larg, List *lcolinfo);
+ Node *larg, List *nrtargetlist);
static void applyColumnNames(List *dst, List *src);
static Query *transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt);
static List *transformReturningList(ParseState *pstate, List *returningList);
int leftmostRTI;
Query *leftmostQuery;
SetOperationStmt *sostmt;
- List *socolinfo;
List *intoColNames = NIL;
List *sortClause;
Node *limitOffset;
*/
sostmt = (SetOperationStmt *) transformSetOperationTree(pstate, stmt,
true,
- &socolinfo);
+ NULL);
Assert(sostmt && IsA(sostmt, SetOperationStmt));
qry->setOperations = (Node *) sostmt;
* transformSetOperationTree
* Recursively transform leaves and internal nodes of a set-op tree
*
- * In addition to returning the transformed node, we return a list of
- * expression nodes showing the type, typmod, collation, and location (for error messages)
- * of each output column of the set-op node. This is used only during the
- * internal recursion of this function. At the upper levels we use
- * SetToDefault nodes for this purpose, since they carry exactly the fields
- * needed, but any other expression node type would do as well.
+ * In addition to returning the transformed node, if targetlist isn't NULL
+ * then we return a list of its non-resjunk TargetEntry nodes. For a leaf
+ * set-op node these are the actual targetlist entries; otherwise they are
+ * dummy entries created to carry the type, typmod, collation, and location
+ * (for error messages) of each output column of the set-op node. This info
+ * is needed only during the internal recursion of this function, so outside
+ * callers pass NULL for targetlist. Note: the reason for passing the
+ * actual targetlist entries of a leaf node is so that upper levels can
+ * replace UNKNOWN Consts with properly-coerced constants.
*/
static Node *
transformSetOperationTree(ParseState *pstate, SelectStmt *stmt,
- bool isTopLevel, List **colInfo)
+ bool isTopLevel, List **targetlist)
{
bool isLeaf;
}
/*
- * Extract a list of the result expressions for upper-level checking.
+ * Extract a list of the non-junk TLEs for upper-level processing.
*/
- *colInfo = NIL;
- foreach(tl, selectQuery->targetList)
+ if (targetlist)
{
- TargetEntry *tle = (TargetEntry *) lfirst(tl);
+ *targetlist = NIL;
+ foreach(tl, selectQuery->targetList)
+ {
+ TargetEntry *tle = (TargetEntry *) lfirst(tl);
- if (!tle->resjunk)
- *colInfo = lappend(*colInfo, tle->expr);
+ if (!tle->resjunk)
+ *targetlist = lappend(*targetlist, tle);
+ }
}
/*
{
/* Process an internal node (set operation node) */
SetOperationStmt *op = makeNode(SetOperationStmt);
- List *lcolinfo;
- List *rcolinfo;
- ListCell *lci;
- ListCell *rci;
+ List *ltargetlist;
+ List *rtargetlist;
+ ListCell *ltl;
+ ListCell *rtl;
const char *context;
context = (stmt->op == SETOP_UNION ? "UNION" :
*/
op->larg = transformSetOperationTree(pstate, stmt->larg,
false,
- &lcolinfo);
+ <argetlist);
/*
* If we are processing a recursive union query, now is the time to
if (isTopLevel &&
pstate->p_parent_cte &&
pstate->p_parent_cte->cterecursive)
- determineRecursiveColTypes(pstate, op->larg, lcolinfo);
+ determineRecursiveColTypes(pstate, op->larg, ltargetlist);
/*
* Recursively transform the right child node.
*/
op->rarg = transformSetOperationTree(pstate, stmt->rarg,
false,
- &rcolinfo);
+ &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(lcolinfo) != list_length(rcolinfo))
+ 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 *) rcolinfo))));
+ exprLocation((Node *) rtargetlist))));
- *colInfo = NIL;
+ if (targetlist)
+ *targetlist = NIL;
op->colTypes = NIL;
op->colTypmods = NIL;
op->colCollations = NIL;
op->groupClauses = NIL;
- forboth(lci, lcolinfo, rci, rcolinfo)
+ forboth(ltl, ltargetlist, rtl, rtargetlist)
{
- Node *lcolnode = (Node *) lfirst(lci);
- Node *rcolnode = (Node *) lfirst(rci);
+ 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);
int32 lcoltypmod = exprTypmod(lcolnode);
int32 rcoltypmod = exprTypmod(rcolnode);
Node *bestexpr;
- SetToDefault *rescolnode;
+ int bestlocation;
Oid rescoltype;
int32 rescoltypmod;
Oid rescolcoll;
list_make2(lcolnode, rcolnode),
context,
&bestexpr);
+ bestlocation = exprLocation(bestexpr);
/* if same type and same typmod, use typmod; else default */
if (lcoltype == rcoltype && lcoltypmod == rcoltypmod)
rescoltypmod = lcoltypmod;
* later anyway, but we want to fail now while we have sufficient
* context to produce an error cursor position.
*
- * The if-tests might look wrong, but they are correct: we should
- * verify if the input is non-UNKNOWN *or* if it is an UNKNOWN
- * Const (to verify the literal is valid for the target data type)
- * or Param (to possibly resolve the Param's type). We should do
- * nothing if the input is say an UNKNOWN Var, which can happen in
- * some cases. The planner is sometimes able to fold the Var to a
+ * 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.
+ * 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 ||
- IsA(lcolnode, Const) ||IsA(lcolnode, Param))
+ if (lcoltype != UNKNOWNOID)
(void) coerce_to_common_type(pstate, lcolnode,
rescoltype, context);
- if (rcoltype != UNKNOWNOID ||
- IsA(rcolnode, Const) ||IsA(rcolnode, Param))
+ else if (IsA(lcolnode, Const) ||IsA(lcolnode, Param))
+ ltle->expr = (Expr *)
+ coerce_to_common_type(pstate, lcolnode,
+ rescoltype, context);
+
+ if (rcoltype != UNKNOWNOID)
(void) coerce_to_common_type(pstate, rcolnode,
rescoltype, context);
+ else if (IsA(rcolnode, Const) ||IsA(rcolnode, Param))
+ rtle->expr = (Expr *)
+ coerce_to_common_type(pstate, rcolnode,
+ rescoltype, context);
/* emit results */
- rescolnode = makeNode(SetToDefault);
- rescolnode->typeId = rescoltype;
- rescolnode->typeMod = rescoltypmod;
- rescolnode->collid = rescolcoll;
- rescolnode->location = exprLocation(bestexpr);
- *colInfo = lappend(*colInfo, rescolnode);
-
op->colTypes = lappend_oid(op->colTypes, rescoltype);
op->colTypmods = lappend_int(op->colTypmods, rescoltypmod);
op->colCollations = lappend_oid(op->colCollations, rescolcoll);
ParseCallbackState pcbstate;
setup_parser_errposition_callback(&pcbstate, pstate,
- rescolnode->location);
+ bestlocation);
/* determine the eqop and optional sortop */
get_sort_group_operators(rescoltype,
op->groupClauses = lappend(op->groupClauses, grpcl);
}
+
+ /*
+ * 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->collid = rescolcoll;
+ rescolnode->location = bestlocation;
+ restle = makeTargetEntry((Expr *) rescolnode,
+ 0, /* no need to set resno */
+ NULL,
+ false);
+ *targetlist = lappend(*targetlist, restle);
+ }
}
return (Node *) op;
* to set up the parent CTE's columns
*/
static void
-determineRecursiveColTypes(ParseState *pstate, Node *larg, List *lcolinfo)
+determineRecursiveColTypes(ParseState *pstate, Node *larg, List *nrtargetlist)
{
Node *node;
int leftmostRTI;
Query *leftmostQuery;
List *targetList;
ListCell *left_tlist;
- ListCell *lci;
+ ListCell *nrtl;
int next_resno;
/*
left_tlist = list_head(leftmostQuery->targetList);
next_resno = 1;
- foreach(lci, lcolinfo)
+ foreach(nrtl, nrtargetlist)
{
- Expr *lcolexpr = (Expr *) lfirst(lci);
+ TargetEntry *nrtle = (TargetEntry *) lfirst(nrtl);
TargetEntry *lefttle = (TargetEntry *) lfirst(left_tlist);
char *colName;
TargetEntry *tle;
Assert(!lefttle->resjunk);
colName = pstrdup(lefttle->resname);
- tle = makeTargetEntry(lcolexpr,
+ tle = makeTargetEntry(nrtle->expr,
next_resno++,
colName,
false);