mirror of https://github.com/citusdata/citus.git
Copy & paste code from Postgres source
All the code in this commit is direct copy & paste from Postgres
source code.
We can classify the copy&paste code into two:
- Copy paste from CTE inline patch from postgres
(https://git.postgresql.org/gitweb/?p=postgresql.git;a=commitdiff;h=608b167f9f9c4553c35bb1ec0eab9ddae643989b)
These include the functions inline_cte(), inline_cte_walker(),
contain_dml(), contain_dml_walker().
It also include the code in function PostgreSQLCTEInlineCondition().
We prefer to extract that code into a seperate function, because
(a) we'll re-use the logic later (b) we added one check for PG_11
Finally, the struct "inline_cte_walker_context" is also copied from
the same Postgres commit.
- Copy paste from the other parts of the Postgres code
In order to implement CTE inlining in Postgres 12, the hackers
modified the query_tree_walker()/range_table_walker() with the
18c0da88a5
Since Citus needs to support the same logic in PG 11, we copy & pasted
that functions (and related flags) with the names pg_12_query_tree_walker()
and pg_12_range_table_walker()
pull/3161/head
parent
1cfebf9f41
commit
1856ab6cdd
|
@ -0,0 +1,366 @@
|
||||||
|
/*-------------------------------------------------------------------------
|
||||||
|
*
|
||||||
|
* cte_inline.c
|
||||||
|
* For multi-shard queries, Citus can only recursively plan CTEs. Instead,
|
||||||
|
* with the functions defined in this file, the certain CTEs can be inlined
|
||||||
|
* as subqueries in the query tree. In that case, more optimal distributed
|
||||||
|
* planning, the query pushdown planning, kicks in and the CTEs can actually
|
||||||
|
* be pushed down as long as it is safe to pushdown as a subquery.
|
||||||
|
*
|
||||||
|
* Most of the logic in this function is inspired (and some is copy & pasted)
|
||||||
|
* from PostgreSQL 12's CTE inlining feature.
|
||||||
|
*
|
||||||
|
* Copyright (c) Citus Data, Inc.
|
||||||
|
*-------------------------------------------------------------------------
|
||||||
|
*/
|
||||||
|
#include "postgres.h"
|
||||||
|
|
||||||
|
#include "nodes/nodeFuncs.h"
|
||||||
|
#if PG_VERSION_NUM >= 120000
|
||||||
|
#include "optimizer/optimizer.h"
|
||||||
|
#else
|
||||||
|
#include "optimizer/cost.h"
|
||||||
|
#include "optimizer/clauses.h"
|
||||||
|
#endif
|
||||||
|
#include "rewrite/rewriteManip.h"
|
||||||
|
|
||||||
|
#if PG_VERSION_NUM < 120000
|
||||||
|
|
||||||
|
/* copy & paste from PG 12 */
|
||||||
|
#define PG_12_QTW_EXAMINE_RTES_BEFORE 0x10
|
||||||
|
#define PG_12_QTW_EXAMINE_RTES_AFTER 0x20
|
||||||
|
bool pg_12_query_tree_walker(Query *query,
|
||||||
|
bool (*walker)(),
|
||||||
|
void *context,
|
||||||
|
int flags);
|
||||||
|
bool pg_12_range_table_walker(List *rtable,
|
||||||
|
bool (*walker)(),
|
||||||
|
void *context,
|
||||||
|
int flags);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
typedef struct inline_cte_walker_context
|
||||||
|
{
|
||||||
|
const char *ctename; /* name and relative level of target CTE */
|
||||||
|
int levelsup;
|
||||||
|
int refcount; /* number of remaining references */
|
||||||
|
Query *ctequery; /* query to substitute */
|
||||||
|
} inline_cte_walker_context;
|
||||||
|
|
||||||
|
/* copy & paste from Postgres source, moved into a function for readability */
|
||||||
|
static bool PostgreSQLCTEInlineCondition(CommonTableExpr *cte, CmdType cmdType);
|
||||||
|
|
||||||
|
/* the following utility functions are copy & paste from PostgreSQL code */
|
||||||
|
static void inline_cte(Query *mainQuery, CommonTableExpr *cte);
|
||||||
|
static bool inline_cte_walker(Node *node, inline_cte_walker_context *context);
|
||||||
|
static bool contain_dml(Node *node);
|
||||||
|
static bool contain_dml_walker(Node *node, void *context);
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* PostgreSQLCTEInlineCondition returns true if the CTE is considered
|
||||||
|
* safe to inline by Postgres.
|
||||||
|
*/
|
||||||
|
static bool
|
||||||
|
PostgreSQLCTEInlineCondition(CommonTableExpr *cte, CmdType cmdType)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* Consider inlining the CTE (creating RTE_SUBQUERY RTE(s)) instead of
|
||||||
|
* implementing it as a separately-planned CTE.
|
||||||
|
*
|
||||||
|
* We cannot inline if any of these conditions hold:
|
||||||
|
*
|
||||||
|
* 1. The user said not to (the CTEMaterializeAlways option).
|
||||||
|
*
|
||||||
|
* 2. The CTE is recursive.
|
||||||
|
*
|
||||||
|
* 3. The CTE has side-effects; this includes either not being a plain
|
||||||
|
* SELECT, or containing volatile functions. Inlining might change
|
||||||
|
* the side-effects, which would be bad.
|
||||||
|
*
|
||||||
|
* Otherwise, we have an option whether to inline or not. That should
|
||||||
|
* always be a win if there's just a single reference, but if the CTE
|
||||||
|
* is multiply-referenced then it's unclear: inlining adds duplicate
|
||||||
|
* computations, but the ability to absorb restrictions from the outer
|
||||||
|
* query level could outweigh that. We do not have nearly enough
|
||||||
|
* information at this point to tell whether that's true, so we let
|
||||||
|
* the user express a preference. Our default behavior is to inline
|
||||||
|
* only singly-referenced CTEs, but a CTE marked CTEMaterializeNever
|
||||||
|
* will be inlined even if multiply referenced.
|
||||||
|
*/
|
||||||
|
if (
|
||||||
|
#if PG_VERSION_NUM >= 120000
|
||||||
|
(cte->ctematerialized == CTEMaterializeNever ||
|
||||||
|
(cte->ctematerialized == CTEMaterializeDefault &&
|
||||||
|
cte->cterefcount == 1)) &&
|
||||||
|
#else
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If referenced only once inlining would probably perform
|
||||||
|
* better, so for pg < 12, try inlining
|
||||||
|
*/
|
||||||
|
cte->cterefcount == 1 &&
|
||||||
|
#endif
|
||||||
|
!cte->cterecursive &&
|
||||||
|
cmdType == CMD_SELECT &&
|
||||||
|
!contain_dml(cte->ctequery) &&
|
||||||
|
!contain_volatile_functions(cte->ctequery))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* *INDENT-OFF* */
|
||||||
|
/*
|
||||||
|
* inline_cte: convert RTE_CTE references to given CTE into RTE_SUBQUERYs
|
||||||
|
*/
|
||||||
|
static void
|
||||||
|
inline_cte(Query *mainQuery, CommonTableExpr *cte)
|
||||||
|
{
|
||||||
|
struct inline_cte_walker_context context;
|
||||||
|
|
||||||
|
context.ctename = cte->ctename;
|
||||||
|
/* Start at levelsup = -1 because we'll immediately increment it */
|
||||||
|
context.levelsup = -1;
|
||||||
|
context.refcount = cte->cterefcount;
|
||||||
|
context.ctequery = castNode(Query, cte->ctequery);
|
||||||
|
|
||||||
|
(void) inline_cte_walker((Node *) mainQuery, &context);
|
||||||
|
|
||||||
|
/* Assert we replaced all references */
|
||||||
|
Assert(context.refcount == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* See PostgreSQL's source code at src/backend/optimizer/plan/subselect.c.
|
||||||
|
*/
|
||||||
|
static bool
|
||||||
|
inline_cte_walker(Node *node, inline_cte_walker_context *context)
|
||||||
|
{
|
||||||
|
if (node == NULL)
|
||||||
|
return false;
|
||||||
|
if (IsA(node, Query))
|
||||||
|
{
|
||||||
|
Query *query = (Query *) node;
|
||||||
|
|
||||||
|
context->levelsup++;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Visit the query's RTE nodes after their contents; otherwise
|
||||||
|
* query_tree_walker would descend into the newly inlined CTE query,
|
||||||
|
* which we don't want.
|
||||||
|
*/
|
||||||
|
#if PG_VERSION_NUM < 120000
|
||||||
|
(void) pg_12_query_tree_walker(query, inline_cte_walker, context,
|
||||||
|
PG_12_QTW_EXAMINE_RTES_AFTER);
|
||||||
|
#else
|
||||||
|
(void) query_tree_walker(query, inline_cte_walker, context,
|
||||||
|
QTW_EXAMINE_RTES_AFTER);
|
||||||
|
#endif
|
||||||
|
context->levelsup--;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else if (IsA(node, RangeTblEntry))
|
||||||
|
{
|
||||||
|
RangeTblEntry *rte = (RangeTblEntry *) node;
|
||||||
|
|
||||||
|
if (rte->rtekind == RTE_CTE &&
|
||||||
|
strcmp(rte->ctename, context->ctename) == 0 &&
|
||||||
|
rte->ctelevelsup == context->levelsup)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* Found a reference to replace. Generate a copy of the CTE query
|
||||||
|
* with appropriate level adjustment for outer references (e.g.,
|
||||||
|
* to other CTEs).
|
||||||
|
*/
|
||||||
|
Query *newquery = copyObject(context->ctequery);
|
||||||
|
|
||||||
|
if (context->levelsup > 0)
|
||||||
|
IncrementVarSublevelsUp((Node *) newquery, context->levelsup, 1);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Convert the RTE_CTE RTE into a RTE_SUBQUERY.
|
||||||
|
*
|
||||||
|
* Historically, a FOR UPDATE clause has been treated as extending
|
||||||
|
* into views and subqueries, but not into CTEs. We preserve this
|
||||||
|
* distinction by not trying to push rowmarks into the new
|
||||||
|
* subquery.
|
||||||
|
*/
|
||||||
|
rte->rtekind = RTE_SUBQUERY;
|
||||||
|
rte->subquery = newquery;
|
||||||
|
rte->security_barrier = false;
|
||||||
|
|
||||||
|
/* Zero out CTE-specific fields */
|
||||||
|
rte->ctename = NULL;
|
||||||
|
rte->ctelevelsup = 0;
|
||||||
|
rte->self_reference = false;
|
||||||
|
rte->coltypes = NIL;
|
||||||
|
rte->coltypmods = NIL;
|
||||||
|
rte->colcollations = NIL;
|
||||||
|
|
||||||
|
/* Count the number of replacements we've done */
|
||||||
|
context->refcount--;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return expression_tree_walker(node, inline_cte_walker, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* contain_dml: is any subquery not a plain SELECT?
|
||||||
|
*
|
||||||
|
* We reject SELECT FOR UPDATE/SHARE as well as INSERT etc.
|
||||||
|
*/
|
||||||
|
static bool
|
||||||
|
contain_dml(Node *node)
|
||||||
|
{
|
||||||
|
return contain_dml_walker(node, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static bool
|
||||||
|
contain_dml_walker(Node *node, void *context)
|
||||||
|
{
|
||||||
|
if (node == NULL)
|
||||||
|
return false;
|
||||||
|
if (IsA(node, Query))
|
||||||
|
{
|
||||||
|
Query *query = (Query *) node;
|
||||||
|
|
||||||
|
if (query->commandType != CMD_SELECT ||
|
||||||
|
query->rowMarks != NIL)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
return query_tree_walker(query, contain_dml_walker, context, 0);
|
||||||
|
}
|
||||||
|
return expression_tree_walker(node, contain_dml_walker, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#if PG_VERSION_NUM < 120000
|
||||||
|
/*
|
||||||
|
* pg_12_query_tree_walker is copied from Postgres 12's source
|
||||||
|
* code. The only difference between query_tree_walker the new
|
||||||
|
* two flags added in range_table_walker: QTW_EXAMINE_RTES_AFTER
|
||||||
|
* and QTW_EXAMINE_RTES_BEFORE.
|
||||||
|
*/
|
||||||
|
bool
|
||||||
|
pg_12_query_tree_walker(Query *query,
|
||||||
|
bool (*walker) (),
|
||||||
|
void *context,
|
||||||
|
int flags)
|
||||||
|
{
|
||||||
|
Assert(query != NULL && IsA(query, Query));
|
||||||
|
|
||||||
|
if (walker((Node *) query->targetList, context))
|
||||||
|
return true;
|
||||||
|
if (walker((Node *) query->withCheckOptions, context))
|
||||||
|
return true;
|
||||||
|
if (walker((Node *) query->onConflict, context))
|
||||||
|
return true;
|
||||||
|
if (walker((Node *) query->returningList, context))
|
||||||
|
return true;
|
||||||
|
if (walker((Node *) query->jointree, context))
|
||||||
|
return true;
|
||||||
|
if (walker(query->setOperations, context))
|
||||||
|
return true;
|
||||||
|
if (walker(query->havingQual, context))
|
||||||
|
return true;
|
||||||
|
if (walker(query->limitOffset, context))
|
||||||
|
return true;
|
||||||
|
if (walker(query->limitCount, context))
|
||||||
|
return true;
|
||||||
|
if (!(flags & QTW_IGNORE_CTE_SUBQUERIES))
|
||||||
|
{
|
||||||
|
if (walker((Node *) query->cteList, context))
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (!(flags & QTW_IGNORE_RANGE_TABLE))
|
||||||
|
{
|
||||||
|
if (pg_12_range_table_walker(query->rtable, walker, context, flags))
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* pg_12_range_table_walker is copied from Postgres 12's source
|
||||||
|
* code. The only difference between range_table_walker the new
|
||||||
|
* two flags added in range_table_walker: QTW_EXAMINE_RTES_AFTER
|
||||||
|
* and QTW_EXAMINE_RTES_BEFORE.
|
||||||
|
*/
|
||||||
|
bool
|
||||||
|
pg_12_range_table_walker(List *rtable,
|
||||||
|
bool (*walker) (),
|
||||||
|
void *context,
|
||||||
|
int flags)
|
||||||
|
{
|
||||||
|
ListCell *rt;
|
||||||
|
|
||||||
|
foreach(rt, rtable)
|
||||||
|
{
|
||||||
|
RangeTblEntry *rte = (RangeTblEntry *) lfirst(rt);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Walkers might need to examine the RTE node itself either before or
|
||||||
|
* after visiting its contents (or, conceivably, both). Note that if
|
||||||
|
* you specify neither flag, the walker won't visit the RTE at all.
|
||||||
|
*/
|
||||||
|
if (flags & PG_12_QTW_EXAMINE_RTES_BEFORE)
|
||||||
|
if (walker(rte, context))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
switch (rte->rtekind)
|
||||||
|
{
|
||||||
|
case RTE_RELATION:
|
||||||
|
if (walker(rte->tablesample, context))
|
||||||
|
return true;
|
||||||
|
break;
|
||||||
|
case RTE_CTE:
|
||||||
|
case RTE_NAMEDTUPLESTORE:
|
||||||
|
/* nothing to do */
|
||||||
|
break;
|
||||||
|
case RTE_SUBQUERY:
|
||||||
|
if (!(flags & QTW_IGNORE_RT_SUBQUERIES))
|
||||||
|
if (walker(rte->subquery, context))
|
||||||
|
return true;
|
||||||
|
break;
|
||||||
|
case RTE_JOIN:
|
||||||
|
if (!(flags & QTW_IGNORE_JOINALIASES))
|
||||||
|
if (walker(rte->joinaliasvars, context))
|
||||||
|
return true;
|
||||||
|
break;
|
||||||
|
case RTE_FUNCTION:
|
||||||
|
if (walker(rte->functions, context))
|
||||||
|
return true;
|
||||||
|
break;
|
||||||
|
case RTE_TABLEFUNC:
|
||||||
|
if (walker(rte->tablefunc, context))
|
||||||
|
return true;
|
||||||
|
break;
|
||||||
|
case RTE_VALUES:
|
||||||
|
if (walker(rte->values_lists, context))
|
||||||
|
return true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (walker(rte->securityQuals, context))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
if (flags & PG_12_QTW_EXAMINE_RTES_AFTER)
|
||||||
|
if (walker(rte, context))
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* *INDENT-ON* */
|
Loading…
Reference in New Issue