Skip to content

Commit 6d69c20

Browse files
committed
Add a new function to un-rewrite the planner tree
We may need to reorder parts of the planner tree we are receiving in dsitributed_planner hook because it has been optimized by PostgreSQL. However we only need to rewrite tree which will be used to produce a query string, otherwise it's pointless. Proceed very early in the planner hook, which is probably a good place to pull up some similar fixes applied here and there. See the exported function from citus_ruleutils.c: void RebuildParserTreeFromPlannerTree(Query *query) Added a new include in citus_ruleutils.c: miscadmin.h from PostgreSQL, because it contains the check_stack_depth() function which is a good idea to use as we don't know the size of the query..
1 parent b0de950 commit 6d69c20

File tree

3 files changed

+207
-0
lines changed

3 files changed

+207
-0
lines changed

src/backend/distributed/deparser/citus_ruleutils.c

+198
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
#include "commands/sequence.h"
4242
#include "foreign/foreign.h"
4343
#include "lib/stringinfo.h"
44+
#include "miscadmin.h"
4445
#include "nodes/nodeFuncs.h"
4546
#include "nodes/nodes.h"
4647
#include "nodes/parsenodes.h"
@@ -1638,3 +1639,200 @@ RoleSpecString(RoleSpec *spec, bool withQuoteIdentifier)
16381639
}
16391640
}
16401641
}
1642+
1643+
/* Function to extract paramid from a FuncExpr node */
1644+
static AttrNumber extract_paramid_from_funcexpr(FuncExpr *func)
1645+
{
1646+
AttrNumber targetAttnum = InvalidAttrNumber;
1647+
ListCell *lc;
1648+
1649+
/* Iterate through the arguments of the FuncExpr */
1650+
foreach(lc, func->args)
1651+
{
1652+
Node *arg = (Node *) lfirst(lc);
1653+
1654+
/* Check if the argument is a PARAM node */
1655+
if (IsA(arg, Param))
1656+
{
1657+
Param *param = (Param *) arg;
1658+
targetAttnum = param->paramid;
1659+
1660+
break; // Exit loop once we find the PARAM node
1661+
}
1662+
}
1663+
1664+
return targetAttnum;
1665+
}
1666+
1667+
/*
1668+
* processTargetsIndirection - reorder targets list (from indirection)
1669+
*
1670+
* We don't change anything but the order of the target list.
1671+
* The purpose here is to be able to deparse a query tree as if it was
1672+
* provided by the PostgreSQL parser, not the rewriter (which is the one
1673+
* received by the planner hook).
1674+
*
1675+
* It's required only for UPDATE SET (MULTIEXPR) queries at the moment, other
1676+
* candidates are not supported by Citus.
1677+
*/
1678+
static void
1679+
processTargetsIndirection(List **targetList)
1680+
{
1681+
int nAssignableCols;
1682+
int targetListPosition;
1683+
bool sawJunk = false;
1684+
List *newTargetList = NIL;
1685+
ListCell *lc;
1686+
1687+
/* Count non-junk columns and ensure they precede junk columns */
1688+
nAssignableCols = 0;
1689+
foreach(lc, *targetList)
1690+
{
1691+
TargetEntry *tle = lfirst_node(TargetEntry, lc);
1692+
1693+
if (tle->resjunk)
1694+
{
1695+
sawJunk = true;
1696+
}
1697+
else
1698+
{
1699+
if (sawJunk)
1700+
elog(ERROR, "Subplan target list is out of order");
1701+
1702+
nAssignableCols++;
1703+
}
1704+
}
1705+
1706+
/* If no assignable columns, return the original target list */
1707+
if (nAssignableCols == 0)
1708+
return;
1709+
1710+
/* Reorder the target list */
1711+
/* we start from 1 */
1712+
targetListPosition = 1;
1713+
while (nAssignableCols > 0)
1714+
{
1715+
nAssignableCols--;
1716+
1717+
foreach(lc, *targetList)
1718+
{
1719+
TargetEntry *tle = lfirst_node(TargetEntry, lc);
1720+
1721+
if (IsA(tle->expr, FuncExpr))
1722+
{
1723+
FuncExpr *funcexpr = (FuncExpr *) tle->expr;
1724+
AttrNumber attnum = extract_paramid_from_funcexpr(funcexpr);
1725+
1726+
if (attnum == targetListPosition)
1727+
{
1728+
ereport(DEBUG1, (errmsg("Adding FuncExpr resno: %d", tle->resno)));
1729+
newTargetList = lappend(newTargetList, tle);
1730+
targetListPosition++;
1731+
break;
1732+
}
1733+
}
1734+
else if (IsA(tle->expr, Param))
1735+
{
1736+
Param *param = (Param *) tle->expr;
1737+
AttrNumber attnum = param->paramid;
1738+
1739+
if (attnum == targetListPosition)
1740+
{
1741+
newTargetList = lappend(newTargetList, tle);
1742+
targetListPosition++;
1743+
break;
1744+
}
1745+
}
1746+
}
1747+
}
1748+
1749+
/* Append any remaining junk columns */
1750+
foreach(lc, *targetList)
1751+
{
1752+
TargetEntry *tle = lfirst_node(TargetEntry, lc);
1753+
if (tle->resjunk)
1754+
newTargetList = lappend(newTargetList, tle);
1755+
}
1756+
*targetList = newTargetList;
1757+
}
1758+
1759+
/*
1760+
* helper function to evaluate if we are in an SET (...)
1761+
* Caller is responsible to check the command type (UPDATE)
1762+
*/
1763+
static inline bool
1764+
is_update_set_multiexpr(Query *query)
1765+
{
1766+
ListCell *lc;
1767+
/* we're only interested by UPDATE */
1768+
if (query->commandType != CMD_UPDATE)
1769+
return false;
1770+
1771+
/*
1772+
* Then foreach target entry, check if one of the node or it's descendant
1773+
* is a PARAM_MULTIEXPR (i.e. a SET (a, b) = (...))
1774+
*/
1775+
foreach(lc, query->targetList) {
1776+
TargetEntry *tle = (TargetEntry *) lfirst(lc);
1777+
Node *expr;
1778+
1779+
if (tle->resjunk)
1780+
continue;
1781+
1782+
expr = strip_implicit_coercions((Node *) tle->expr);
1783+
1784+
if (expr && IsA(expr, Param) &&
1785+
((Param *) expr)->paramkind == PARAM_MULTIEXPR)
1786+
{
1787+
return true;
1788+
}
1789+
}
1790+
1791+
/* No multi-column set expression found */
1792+
return false;
1793+
}
1794+
1795+
/*
1796+
* helper function to evaluate if we are in SELECT with CTE.
1797+
*/
1798+
static inline bool
1799+
is_select_cte(Query *query)
1800+
{
1801+
/* we are only looking for a SELECT query */
1802+
if (query->commandType != CMD_SELECT)
1803+
return false;
1804+
/* and it must contain CTE */
1805+
if (query->cteList == NIL)
1806+
return false;
1807+
return true;
1808+
}
1809+
1810+
/*
1811+
* We may need to reorder parts of the planner tree we are receiving here.
1812+
* We expect to produce an SQL query text but our tree has been optimized by
1813+
* PostgreSL rewriter already...
1814+
*/
1815+
void
1816+
RebuildParserTreeFromPlannerTree(Query *query)
1817+
{
1818+
/* Guard against excessively long or deeply-nested queries */
1819+
CHECK_FOR_INTERRUPTS();
1820+
1821+
/* prevent unloyal defeat */
1822+
check_stack_depth();
1823+
1824+
if (is_update_set_multiexpr(query))
1825+
{
1826+
processTargetsIndirection(&query->targetList);
1827+
}
1828+
/* also match UPDATE in CTE, this one is recursive */
1829+
else if (is_select_cte(query))
1830+
{
1831+
ListCell *lc;
1832+
foreach(lc, query->cteList)
1833+
{
1834+
CommonTableExpr *cte = (CommonTableExpr *) lfirst(lc);
1835+
RebuildParserTreeFromPlannerTree((Query *) cte->ctequery);
1836+
}
1837+
}
1838+
}

src/backend/distributed/planner/distributed_planner.c

+7
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,13 @@ distributed_planner(Query *parse,
217217

218218
planContext.originalQuery = copyObject(parse);
219219

220+
/*
221+
* We may need to reorder parts of the planner tree we are receiving here.
222+
* We expect to produce an SQL query text but our tree has been optimized by
223+
* PostgreSL rewriter already...
224+
* FIXME is there conditions to reduce the number of calls ?
225+
*/
226+
RebuildParserTreeFromPlannerTree(planContext.originalQuery);
220227

221228
if (!fastPathRouterQuery)
222229
{

src/include/distributed/citus_ruleutils.h

+2
Original file line numberDiff line numberDiff line change
@@ -60,5 +60,7 @@ extern char * generate_operator_name(Oid operid, Oid arg1, Oid arg2);
6060
extern List * getOwnedSequences_internal(Oid relid, AttrNumber attnum, char deptype);
6161
extern void AppendOptionListToString(StringInfo stringData, List *options);
6262

63+
extern void RebuildParserTreeFromPlannerTree(Query *query);
64+
6365

6466
#endif /* CITUS_RULEUTILS_H */

0 commit comments

Comments
 (0)