Add check for nextval in CTE inlining to prevent inlining of queries containing nextval

issue_7882_main_2
Mehmet Yilmaz 2025-03-24 19:30:38 +00:00
parent 5fbf89ea17
commit 1c6167f18e
1 changed files with 65 additions and 28 deletions

View File

@ -16,6 +16,7 @@
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
#include "rewrite/rewriteManip.h"
#include "distributed/citus_ruleutils.h" /* Ensure this header declares contain_nextval_expression_walker */
#include "pg_version_compat.h"
#include "pg_version_constants.h"
@ -48,6 +49,9 @@ static bool RecursivelyInlineCteWalker(Node *node, void *context);
static void InlineCTEsInQueryTree(Query *query);
static bool QueryTreeContainsInlinableCteWalker(Node *node, void *context);
static bool
QueryContainsNextval2(Query *q);
/*
* RecursivelyInlineCtesInQueryTree gets a query and recursively traverses the
@ -93,44 +97,77 @@ RecursivelyInlineCteWalker(Node *node, void *context)
}
/*
* InlineCTEsInQueryTree gets a query tree and tries to inline CTEs as subqueries
* in the query tree.
*
* Most of the code is coming from PostgreSQL's CTE inlining logic, there are very
* few additions that Citus added, which are already commented in the code.
*/
void
InlineCTEsInQueryTree(Query *query)
{
ListCell *cteCell = NULL;
ListCell *cteCell = NULL;
List *copyOfCteList = list_copy(query->cteList);
/* iterate on the copy of the list because we'll be modifying query->cteList */
List *copyOfCteList = list_copy(query->cteList);
foreach(cteCell, copyOfCteList)
{
CommonTableExpr *cte = (CommonTableExpr *) lfirst(cteCell);
foreach(cteCell, copyOfCteList)
{
CommonTableExpr *cte = (CommonTableExpr *) lfirst(cteCell);
/*
* First, make sure that Postgres is OK to inline the CTE. Later, check for
* distributed query planning constraints that might prevent inlining.
*/
if (PostgreSQLCTEInlineCondition(cte, query->commandType))
{
elog(DEBUG1, "CTE %s is going to be inlined via "
"distributed planning", cte->ctename);
/* Extra check: if the CTE query contains nextval, do not inline it */
if (QueryContainsNextval2((Query *) cte->ctequery))
{
elog(DEBUG1, "CTE \"%s\" contains nextval; materializing it instead of inlining", cte->ctename);
continue;
}
/* do the hard work of cte inlining */
inline_cte(query, cte);
/* Standard PostgreSQL inline condition */
if (PostgreSQLCTEInlineCondition(cte, query->commandType))
{
elog(DEBUG1, "CTE \"%s\" is going to be inlined via distributed planning", cte->ctename);
inline_cte(query, cte);
/* clean-up the necessary fields for distributed planning */
cte->cterefcount = 0;
query->cteList = list_delete_ptr(query->cteList, cte);
}
}
/* Reset reference count and remove from cteList */
cte->cterefcount = 0;
query->cteList = list_delete_ptr(query->cteList, cte);
}
}
}
static bool
QueryContainsNextval2(Query *q)
{
ListCell *lc = NULL;
/* Check the target list */
foreach(lc, q->targetList)
{
TargetEntry *tle = (TargetEntry *) lfirst(lc);
if (contain_nextval_expression_walker((Node *) tle->expr, NULL))
return true;
}
/* Check the WHERE clause (jointree quals) */
if (q->jointree && q->jointree->quals &&
contain_nextval_expression_walker((Node *) q->jointree->quals, NULL))
return true;
/* Recursively check subqueries in the range table */
foreach(lc, q->rtable)
{
RangeTblEntry *rte = (RangeTblEntry *) lfirst(lc);
if (rte->rtekind == RTE_SUBQUERY && rte->subquery != NULL)
{
if (QueryContainsNextval2(rte->subquery))
return true;
}
}
/* Optionally, check havingQual if needed */
if (q->havingQual &&
contain_nextval_expression_walker((Node *) q->havingQual, NULL))
return true;
return false;
}
/*
* QueryTreeContainsInlinableCTE recursively traverses the queryTree, and returns true
* if any of the (sub)queries in the queryTree contains at least one CTE.