Skip to content

Commit 72cfc17

Browse files
committed
Improve handling of unknown-type literals in UNION/INTERSECT/EXCEPT.
This patch causes unknown-type Consts to be coerced to the resolved output type of the set operation at parse time. Formerly such Consts were left alone until late in the planning stage. The disadvantage of that approach is that it disables some optimizations, because the planner sees the set-op leaf query as having different output column types than the overall set-op. We saw an example of that in a recent performance gripe from Claudio Freire. Fixing such a Const requires scribbling on the leaf query in transformSetOperationTree, but that should be all right since if the leaf query's semantics depended on that output column, it would already have resolved the unknown to something else. Most of the bulk of this patch is a simple adjustment of transformSetOperationTree's API so that upper levels can get at the TargetEntry containing a Const to be replaced: it now returns a list of TargetEntries, instead of just the bare expressions.
1 parent 898a14e commit 72cfc17

File tree

1 file changed

+96
-54
lines changed

1 file changed

+96
-54
lines changed

src/backend/parser/analyze.c

Lines changed: 96 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -52,9 +52,9 @@ static Query *transformSelectStmt(ParseState *pstate, SelectStmt *stmt);
5252
static Query *transformValuesClause(ParseState *pstate, SelectStmt *stmt);
5353
static Query *transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt);
5454
static Node *transformSetOperationTree(ParseState *pstate, SelectStmt *stmt,
55-
bool isTopLevel, List **colInfo);
55+
bool isTopLevel, List **targetlist);
5656
static void determineRecursiveColTypes(ParseState *pstate,
57-
Node *larg, List *lcolinfo);
57+
Node *larg, List *nrtargetlist);
5858
static void applyColumnNames(List *dst, List *src);
5959
static Query *transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt);
6060
static List *transformReturningList(ParseState *pstate, List *returningList);
@@ -1197,7 +1197,6 @@ transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt)
11971197
int leftmostRTI;
11981198
Query *leftmostQuery;
11991199
SetOperationStmt *sostmt;
1200-
List *socolinfo;
12011200
List *intoColNames = NIL;
12021201
List *sortClause;
12031202
Node *limitOffset;
@@ -1271,7 +1270,7 @@ transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt)
12711270
*/
12721271
sostmt = (SetOperationStmt *) transformSetOperationTree(pstate, stmt,
12731272
true,
1274-
&socolinfo);
1273+
NULL);
12751274
Assert(sostmt && IsA(sostmt, SetOperationStmt));
12761275
qry->setOperations = (Node *) sostmt;
12771276

@@ -1425,16 +1424,19 @@ transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt)
14251424
* transformSetOperationTree
14261425
* Recursively transform leaves and internal nodes of a set-op tree
14271426
*
1428-
* In addition to returning the transformed node, we return a list of
1429-
* expression nodes showing the type, typmod, collation, and location (for error messages)
1430-
* of each output column of the set-op node. This is used only during the
1431-
* internal recursion of this function. At the upper levels we use
1432-
* SetToDefault nodes for this purpose, since they carry exactly the fields
1433-
* needed, but any other expression node type would do as well.
1427+
* In addition to returning the transformed node, if targetlist isn't NULL
1428+
* then we return a list of its non-resjunk TargetEntry nodes. For a leaf
1429+
* set-op node these are the actual targetlist entries; otherwise they are
1430+
* dummy entries created to carry the type, typmod, collation, and location
1431+
* (for error messages) of each output column of the set-op node. This info
1432+
* is needed only during the internal recursion of this function, so outside
1433+
* callers pass NULL for targetlist. Note: the reason for passing the
1434+
* actual targetlist entries of a leaf node is so that upper levels can
1435+
* replace UNKNOWN Consts with properly-coerced constants.
14341436
*/
14351437
static Node *
14361438
transformSetOperationTree(ParseState *pstate, SelectStmt *stmt,
1437-
bool isTopLevel, List **colInfo)
1439+
bool isTopLevel, List **targetlist)
14381440
{
14391441
bool isLeaf;
14401442

@@ -1512,15 +1514,18 @@ transformSetOperationTree(ParseState *pstate, SelectStmt *stmt,
15121514
}
15131515

15141516
/*
1515-
* Extract a list of the result expressions for upper-level checking.
1517+
* Extract a list of the non-junk TLEs for upper-level processing.
15161518
*/
1517-
*colInfo = NIL;
1518-
foreach(tl, selectQuery->targetList)
1519+
if (targetlist)
15191520
{
1520-
TargetEntry *tle = (TargetEntry *) lfirst(tl);
1521+
*targetlist = NIL;
1522+
foreach(tl, selectQuery->targetList)
1523+
{
1524+
TargetEntry *tle = (TargetEntry *) lfirst(tl);
15211525

1522-
if (!tle->resjunk)
1523-
*colInfo = lappend(*colInfo, tle->expr);
1526+
if (!tle->resjunk)
1527+
*targetlist = lappend(*targetlist, tle);
1528+
}
15241529
}
15251530

15261531
/*
@@ -1546,10 +1551,10 @@ transformSetOperationTree(ParseState *pstate, SelectStmt *stmt,
15461551
{
15471552
/* Process an internal node (set operation node) */
15481553
SetOperationStmt *op = makeNode(SetOperationStmt);
1549-
List *lcolinfo;
1550-
List *rcolinfo;
1551-
ListCell *lci;
1552-
ListCell *rci;
1554+
List *ltargetlist;
1555+
List *rtargetlist;
1556+
ListCell *ltl;
1557+
ListCell *rtl;
15531558
const char *context;
15541559

15551560
context = (stmt->op == SETOP_UNION ? "UNION" :
@@ -1564,7 +1569,7 @@ transformSetOperationTree(ParseState *pstate, SelectStmt *stmt,
15641569
*/
15651570
op->larg = transformSetOperationTree(pstate, stmt->larg,
15661571
false,
1567-
&lcolinfo);
1572+
&ltargetlist);
15681573

15691574
/*
15701575
* If we are processing a recursive union query, now is the time to
@@ -1575,42 +1580,45 @@ transformSetOperationTree(ParseState *pstate, SelectStmt *stmt,
15751580
if (isTopLevel &&
15761581
pstate->p_parent_cte &&
15771582
pstate->p_parent_cte->cterecursive)
1578-
determineRecursiveColTypes(pstate, op->larg, lcolinfo);
1583+
determineRecursiveColTypes(pstate, op->larg, ltargetlist);
15791584

15801585
/*
15811586
* Recursively transform the right child node.
15821587
*/
15831588
op->rarg = transformSetOperationTree(pstate, stmt->rarg,
15841589
false,
1585-
&rcolinfo);
1590+
&rtargetlist);
15861591

15871592
/*
15881593
* Verify that the two children have the same number of non-junk
15891594
* columns, and determine the types of the merged output columns.
15901595
*/
1591-
if (list_length(lcolinfo) != list_length(rcolinfo))
1596+
if (list_length(ltargetlist) != list_length(rtargetlist))
15921597
ereport(ERROR,
15931598
(errcode(ERRCODE_SYNTAX_ERROR),
15941599
errmsg("each %s query must have the same number of columns",
15951600
context),
15961601
parser_errposition(pstate,
1597-
exprLocation((Node *) rcolinfo))));
1602+
exprLocation((Node *) rtargetlist))));
15981603

1599-
*colInfo = NIL;
1604+
if (targetlist)
1605+
*targetlist = NIL;
16001606
op->colTypes = NIL;
16011607
op->colTypmods = NIL;
16021608
op->colCollations = NIL;
16031609
op->groupClauses = NIL;
1604-
forboth(lci, lcolinfo, rci, rcolinfo)
1610+
forboth(ltl, ltargetlist, rtl, rtargetlist)
16051611
{
1606-
Node *lcolnode = (Node *) lfirst(lci);
1607-
Node *rcolnode = (Node *) lfirst(rci);
1612+
TargetEntry *ltle = (TargetEntry *) lfirst(ltl);
1613+
TargetEntry *rtle = (TargetEntry *) lfirst(rtl);
1614+
Node *lcolnode = (Node *) ltle->expr;
1615+
Node *rcolnode = (Node *) rtle->expr;
16081616
Oid lcoltype = exprType(lcolnode);
16091617
Oid rcoltype = exprType(rcolnode);
16101618
int32 lcoltypmod = exprTypmod(lcolnode);
16111619
int32 rcoltypmod = exprTypmod(rcolnode);
16121620
Node *bestexpr;
1613-
SetToDefault *rescolnode;
1621+
int bestlocation;
16141622
Oid rescoltype;
16151623
int32 rescoltypmod;
16161624
Oid rescolcoll;
@@ -1620,6 +1628,7 @@ transformSetOperationTree(ParseState *pstate, SelectStmt *stmt,
16201628
list_make2(lcolnode, rcolnode),
16211629
context,
16221630
&bestexpr);
1631+
bestlocation = exprLocation(bestexpr);
16231632
/* if same type and same typmod, use typmod; else default */
16241633
if (lcoltype == rcoltype && lcoltypmod == rcoltypmod)
16251634
rescoltypmod = lcoltypmod;
@@ -1637,32 +1646,44 @@ transformSetOperationTree(ParseState *pstate, SelectStmt *stmt,
16371646
* later anyway, but we want to fail now while we have sufficient
16381647
* context to produce an error cursor position.
16391648
*
1640-
* The if-tests might look wrong, but they are correct: we should
1641-
* verify if the input is non-UNKNOWN *or* if it is an UNKNOWN
1642-
* Const (to verify the literal is valid for the target data type)
1643-
* or Param (to possibly resolve the Param's type). We should do
1644-
* nothing if the input is say an UNKNOWN Var, which can happen in
1645-
* some cases. The planner is sometimes able to fold the Var to a
1649+
* For all non-UNKNOWN-type cases, we verify coercibility but we
1650+
* don't modify the child's expression, for fear of changing the
1651+
* child query's semantics.
1652+
*
1653+
* If a child expression is an UNKNOWN-type Const or Param, we
1654+
* want to replace it with the coerced expression. This can only
1655+
* happen when the child is a leaf set-op node. It's safe to
1656+
* replace the expression because if the child query's semantics
1657+
* depended on the type of this output column, it'd have already
1658+
* coerced the UNKNOWN to something else. We want to do this
1659+
* because (a) we want to verify that a Const is valid for the
1660+
* target type, or resolve the actual type of an UNKNOWN Param,
1661+
* and (b) we want to avoid unnecessary discrepancies between the
1662+
* output type of the child query and the resolved target type.
1663+
* Such a discrepancy would disable optimization in the planner.
1664+
*
1665+
* If it's some other UNKNOWN-type node, eg a Var, we do nothing.
1666+
* The planner is sometimes able to fold an UNKNOWN Var to a
16461667
* constant before it has to coerce the type, so failing now would
16471668
* just break cases that might work.
16481669
*/
1649-
if (lcoltype != UNKNOWNOID ||
1650-
IsA(lcolnode, Const) ||IsA(lcolnode, Param))
1670+
if (lcoltype != UNKNOWNOID)
16511671
(void) coerce_to_common_type(pstate, lcolnode,
16521672
rescoltype, context);
1653-
if (rcoltype != UNKNOWNOID ||
1654-
IsA(rcolnode, Const) ||IsA(rcolnode, Param))
1673+
else if (IsA(lcolnode, Const) ||IsA(lcolnode, Param))
1674+
ltle->expr = (Expr *)
1675+
coerce_to_common_type(pstate, lcolnode,
1676+
rescoltype, context);
1677+
1678+
if (rcoltype != UNKNOWNOID)
16551679
(void) coerce_to_common_type(pstate, rcolnode,
16561680
rescoltype, context);
1681+
else if (IsA(rcolnode, Const) ||IsA(rcolnode, Param))
1682+
rtle->expr = (Expr *)
1683+
coerce_to_common_type(pstate, rcolnode,
1684+
rescoltype, context);
16571685

16581686
/* emit results */
1659-
rescolnode = makeNode(SetToDefault);
1660-
rescolnode->typeId = rescoltype;
1661-
rescolnode->typeMod = rescoltypmod;
1662-
rescolnode->collid = rescolcoll;
1663-
rescolnode->location = exprLocation(bestexpr);
1664-
*colInfo = lappend(*colInfo, rescolnode);
1665-
16661687
op->colTypes = lappend_oid(op->colTypes, rescoltype);
16671688
op->colTypmods = lappend_int(op->colTypmods, rescoltypmod);
16681689
op->colCollations = lappend_oid(op->colCollations, rescolcoll);
@@ -1681,7 +1702,7 @@ transformSetOperationTree(ParseState *pstate, SelectStmt *stmt,
16811702
ParseCallbackState pcbstate;
16821703

16831704
setup_parser_errposition_callback(&pcbstate, pstate,
1684-
rescolnode->location);
1705+
bestlocation);
16851706

16861707
/* determine the eqop and optional sortop */
16871708
get_sort_group_operators(rescoltype,
@@ -1700,6 +1721,27 @@ transformSetOperationTree(ParseState *pstate, SelectStmt *stmt,
17001721

17011722
op->groupClauses = lappend(op->groupClauses, grpcl);
17021723
}
1724+
1725+
/*
1726+
* Construct a dummy tlist entry to return. We use a SetToDefault
1727+
* node for the expression, since it carries exactly the fields
1728+
* needed, but any other expression node type would do as well.
1729+
*/
1730+
if (targetlist)
1731+
{
1732+
SetToDefault *rescolnode = makeNode(SetToDefault);
1733+
TargetEntry *restle;
1734+
1735+
rescolnode->typeId = rescoltype;
1736+
rescolnode->typeMod = rescoltypmod;
1737+
rescolnode->collid = rescolcoll;
1738+
rescolnode->location = bestlocation;
1739+
restle = makeTargetEntry((Expr *) rescolnode,
1740+
0, /* no need to set resno */
1741+
NULL,
1742+
false);
1743+
*targetlist = lappend(*targetlist, restle);
1744+
}
17031745
}
17041746

17051747
return (Node *) op;
@@ -1711,14 +1753,14 @@ transformSetOperationTree(ParseState *pstate, SelectStmt *stmt,
17111753
* to set up the parent CTE's columns
17121754
*/
17131755
static void
1714-
determineRecursiveColTypes(ParseState *pstate, Node *larg, List *lcolinfo)
1756+
determineRecursiveColTypes(ParseState *pstate, Node *larg, List *nrtargetlist)
17151757
{
17161758
Node *node;
17171759
int leftmostRTI;
17181760
Query *leftmostQuery;
17191761
List *targetList;
17201762
ListCell *left_tlist;
1721-
ListCell *lci;
1763+
ListCell *nrtl;
17221764
int next_resno;
17231765

17241766
/*
@@ -1740,16 +1782,16 @@ determineRecursiveColTypes(ParseState *pstate, Node *larg, List *lcolinfo)
17401782
left_tlist = list_head(leftmostQuery->targetList);
17411783
next_resno = 1;
17421784

1743-
foreach(lci, lcolinfo)
1785+
foreach(nrtl, nrtargetlist)
17441786
{
1745-
Expr *lcolexpr = (Expr *) lfirst(lci);
1787+
TargetEntry *nrtle = (TargetEntry *) lfirst(nrtl);
17461788
TargetEntry *lefttle = (TargetEntry *) lfirst(left_tlist);
17471789
char *colName;
17481790
TargetEntry *tle;
17491791

17501792
Assert(!lefttle->resjunk);
17511793
colName = pstrdup(lefttle->resname);
1752-
tle = makeTargetEntry(lcolexpr,
1794+
tle = makeTargetEntry(nrtle->expr,
17531795
next_resno++,
17541796
colName,
17551797
false);

0 commit comments

Comments
 (0)