mirror of
https://github.com/postgres/postgres.git
synced 2026-03-22 18:33:19 -04:00
Factor out constructSetOpTargetlist() from transformSetOperationTree()
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
This commit is contained in:
parent
f9de9bf302
commit
8c2b30487c
1 changed files with 179 additions and 156 deletions
|
|
@ -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,164 +2252,185 @@ 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))));
|
||||
|
||||
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);
|
||||
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
rescoltypmod = select_common_typmod(pstate,
|
||||
list_make2(lcolnode, rcolnode),
|
||||
rescoltype);
|
||||
|
||||
/*
|
||||
* 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));
|
||||
|
||||
/* emit results */
|
||||
op->colTypes = lappend_oid(op->colTypes, rescoltype);
|
||||
op->colTypmods = lappend_int(op->colTypmods, rescoltypmod);
|
||||
op->colCollations = lappend_oid(op->colCollations, rescolcoll);
|
||||
|
||||
/*
|
||||
* 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;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
/*
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
constructSetOpTargetlist(pstate, op, ltargetlist, rtargetlist, targetlist,
|
||||
context, recursive);
|
||||
|
||||
return (Node *) op;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* 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;
|
||||
|
||||
/*
|
||||
* 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))));
|
||||
|
||||
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);
|
||||
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
rescoltypmod = select_common_typmod(pstate,
|
||||
list_make2(lcolnode, rcolnode),
|
||||
rescoltype);
|
||||
|
||||
/*
|
||||
* 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));
|
||||
|
||||
/* emit results */
|
||||
op->colTypes = lappend_oid(op->colTypes, rescoltype);
|
||||
op->colTypmods = lappend_int(op->colTypmods, rescoltypmod);
|
||||
op->colCollations = lappend_oid(op->colCollations, rescolcoll);
|
||||
|
||||
/*
|
||||
* 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;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
/*
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Process the outputs of the non-recursive term of a recursive union
|
||||
* to set up the parent CTE's columns
|
||||
|
|
|
|||
Loading…
Reference in a new issue