|
41 | 41 | #include "commands/sequence.h"
|
42 | 42 | #include "foreign/foreign.h"
|
43 | 43 | #include "lib/stringinfo.h"
|
| 44 | +#include "miscadmin.h" |
44 | 45 | #include "nodes/nodeFuncs.h"
|
45 | 46 | #include "nodes/nodes.h"
|
46 | 47 | #include "nodes/parsenodes.h"
|
@@ -1638,3 +1639,200 @@ RoleSpecString(RoleSpec *spec, bool withQuoteIdentifier)
|
1638 | 1639 | }
|
1639 | 1640 | }
|
1640 | 1641 | }
|
| 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 | +} |
0 commit comments