mirror of https://github.com/citusdata/citus.git
881 lines
25 KiB
C
881 lines
25 KiB
C
/*-------------------------------------------------------------------------
|
|
*
|
|
* recursive_planning.c
|
|
*
|
|
* Logic for calling the postgres planner recursively for CTEs and
|
|
* non-pushdownable subqueries in distributed queries.
|
|
*
|
|
* PostgreSQL with Citus can execute 4 types of queries:
|
|
*
|
|
* - Postgres queries on local tables and functions.
|
|
*
|
|
* These queries can use all SQL features, but they may not reference
|
|
* distributed tables.
|
|
*
|
|
* - Router queries that can be executed on a single by node by replacing
|
|
* table names with shard names.
|
|
*
|
|
* These queries can use nearly all SQL features, but only if they have
|
|
* a single-valued filter on the distribution column.
|
|
*
|
|
* - Real-time queries that can be executed by performing a task for each
|
|
* shard in a distributed table and performing a merge step.
|
|
*
|
|
* These queries have limited SQL support. They may only include
|
|
* subqueries if the subquery can be executed on each shard by replacing
|
|
* table names with shard names and concatenating the result.
|
|
*
|
|
* - Task-tracker queries that can be executed through a tree of
|
|
* re-partitioning operations.
|
|
*
|
|
* These queries have very limited SQL support and only support basic
|
|
* inner joins and subqueries without joins.
|
|
*
|
|
* To work around the limitations of these planners, we recursively call
|
|
* the planner for CTEs and unsupported subqueries to obtain a list of
|
|
* subplans.
|
|
*
|
|
* During execution, each subplan is executed separately through the method
|
|
* that is appropriate for that query. The results are written to temporary
|
|
* files on the workers. In the original query, the CTEs and subqueries are
|
|
* replaced by mini-subqueries that read from the temporary files.
|
|
*
|
|
* This allows almost all SQL to be directly or indirectly supported,
|
|
* because if all subqueries that contain distributed tables have been
|
|
* replaced then what remains is a router query which can use nearly all
|
|
* SQL features.
|
|
*
|
|
* Copyright (c) 2017, Citus Data, Inc.
|
|
*-------------------------------------------------------------------------
|
|
*/
|
|
|
|
#include "postgres.h"
|
|
|
|
#include "catalog/pg_type.h"
|
|
#include "catalog/pg_class.h"
|
|
#include "distributed/citus_nodes.h"
|
|
#include "distributed/citus_ruleutils.h"
|
|
#include "distributed/distributed_planner.h"
|
|
#include "distributed/errormessage.h"
|
|
#include "distributed/metadata_cache.h"
|
|
#include "distributed/multi_copy.h"
|
|
#include "distributed/multi_logical_planner.h"
|
|
#include "distributed/multi_router_planner.h"
|
|
#include "distributed/multi_physical_planner.h"
|
|
#include "distributed/recursive_planning.h"
|
|
#include "distributed/multi_server_executor.h"
|
|
#include "distributed/relation_restriction_equivalence.h"
|
|
#include "lib/stringinfo.h"
|
|
#include "optimizer/planner.h"
|
|
#include "nodes/nodeFuncs.h"
|
|
#include "nodes/nodes.h"
|
|
#include "nodes/pg_list.h"
|
|
#include "nodes/primnodes.h"
|
|
#include "utils/builtins.h"
|
|
#include "utils/guc.h"
|
|
|
|
|
|
/*
|
|
* RecursivePlanningContext is used to recursively plan subqueries
|
|
* and CTEs, pull results to the coordinator, and push it back into
|
|
* the workers.
|
|
*/
|
|
typedef struct RecursivePlanningContext
|
|
{
|
|
int level;
|
|
uint64 planId;
|
|
List *subPlanList;
|
|
PlannerRestrictionContext *plannerRestrictionContext;
|
|
} RecursivePlanningContext;
|
|
|
|
|
|
/*
|
|
* CteReferenceWalkerContext is used to collect CTE references in
|
|
* CteReferenceListWalker.
|
|
*/
|
|
typedef struct CteReferenceWalkerContext
|
|
{
|
|
int level;
|
|
List *cteReferenceList;
|
|
} CteReferenceWalkerContext;
|
|
|
|
/*
|
|
* VarLevelsUpWalkerContext is used to find Vars in a (sub)query that
|
|
* refer to upper levels and therefore cannot be planned separately.
|
|
*/
|
|
typedef struct VarLevelsUpWalkerContext
|
|
{
|
|
int level;
|
|
} VarLevelsUpWalkerContext;
|
|
|
|
|
|
/* local function forward declarations */
|
|
static DeferredErrorMessage * RecursivelyPlanSubqueriesAndCTEs(Query *query,
|
|
RecursivePlanningContext *
|
|
context);
|
|
static DeferredErrorMessage * RecursivelyPlanCTEs(Query *query,
|
|
RecursivePlanningContext *context);
|
|
static bool RecursivelyPlanSubqueryWalker(Node *node, RecursivePlanningContext *context);
|
|
static bool ShouldRecursivelyPlanSubquery(Query *subquery);
|
|
static bool IsLocalTableRTE(Node *node);
|
|
static void RecursivelyPlanSubquery(Query *subquery,
|
|
RecursivePlanningContext *planningContext);
|
|
static DistributedSubPlan * CreateDistributedSubPlan(uint32 subPlanId,
|
|
Query *subPlanQuery);
|
|
static bool CteReferenceListWalker(Node *node, CteReferenceWalkerContext *context);
|
|
static bool ContainsReferencesToOuterQuery(Query *query);
|
|
static bool ContainsReferencesToOuterQueryWalker(Node *node,
|
|
VarLevelsUpWalkerContext *context);
|
|
static Query * BuildSubPlanResultQuery(Query *subquery, uint64 planId, uint32 subPlanId);
|
|
|
|
|
|
/*
|
|
* GenerateSubplansForSubqueriesAndCTEs is a wrapper around RecursivelyPlanSubqueriesAndCTEs.
|
|
* The function returns the subplans if necessary. For the details of when/how subplans are
|
|
* generated, see RecursivelyPlanSubqueriesAndCTEs().
|
|
*
|
|
* Note that the input originalQuery query is modified if any subplans are generated.
|
|
*/
|
|
List *
|
|
GenerateSubplansForSubqueriesAndCTEs(uint64 planId, Query *originalQuery,
|
|
PlannerRestrictionContext *plannerRestrictionContext)
|
|
{
|
|
RecursivePlanningContext context;
|
|
DeferredErrorMessage *error = NULL;
|
|
|
|
/*
|
|
* Plan subqueries and CTEs that cannot be pushed down by recursively
|
|
* calling the planner and add the resulting plans to subPlanList.
|
|
*/
|
|
context.level = 0;
|
|
context.planId = planId;
|
|
context.subPlanList = NIL;
|
|
context.plannerRestrictionContext = plannerRestrictionContext;
|
|
|
|
error = RecursivelyPlanSubqueriesAndCTEs(originalQuery, &context);
|
|
if (error != NULL)
|
|
{
|
|
RaiseDeferredError(error, ERROR);
|
|
}
|
|
|
|
return context.subPlanList;
|
|
}
|
|
|
|
|
|
/*
|
|
* RecursivelyPlanSubqueriesAndCTEs finds subqueries and CTEs that cannot be pushed down to
|
|
* workers directly and instead plans them by recursively calling the planner and
|
|
* adding the subplan to subPlanList.
|
|
*
|
|
* Subplans are executed prior to the distributed plan and the results are written
|
|
* to temporary files on workers.
|
|
*
|
|
* CTE references are replaced by a subquery on the read_intermediate_result
|
|
* function, which reads from the temporary file.
|
|
*
|
|
* If recursive planning results in an error then the error is returned. Otherwise, the
|
|
* subplans will be added to subPlanList.
|
|
*/
|
|
static DeferredErrorMessage *
|
|
RecursivelyPlanSubqueriesAndCTEs(Query *query, RecursivePlanningContext *context)
|
|
{
|
|
DeferredErrorMessage *error = NULL;
|
|
|
|
error = RecursivelyPlanCTEs(query, context);
|
|
if (error != NULL)
|
|
{
|
|
return error;
|
|
}
|
|
|
|
if (SubqueryPushdown)
|
|
{
|
|
/*
|
|
* When the subquery_pushdown flag is enabled we make some hacks
|
|
* to push down subqueries with LIMIT. Recursive planning would
|
|
* valiantly do the right thing and try to recursively plan the
|
|
* inner subqueries, but we don't really want it to because those
|
|
* subqueries might not be supported and would be much slower.
|
|
*
|
|
* Instead, we skip recursive planning altogether when
|
|
* subquery_pushdown is enabled.
|
|
*/
|
|
return NULL;
|
|
}
|
|
|
|
/* descend into subqueries */
|
|
query_tree_walker(query, RecursivelyPlanSubqueryWalker, context, 0);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
|
|
/*
|
|
* RecursivelyPlanCTEs plans all CTEs in the query by recursively calling the planner
|
|
* The resulting plan is added to planningContext->subPlanList and CTE references
|
|
* are replaced by subqueries that call read_intermediate_result, which reads the
|
|
* intermediate result of the CTE after it is executed.
|
|
*
|
|
* Recursive and modifying CTEs are not yet supported and return an error.
|
|
*/
|
|
static DeferredErrorMessage *
|
|
RecursivelyPlanCTEs(Query *query, RecursivePlanningContext *planningContext)
|
|
{
|
|
ListCell *cteCell = NULL;
|
|
CteReferenceWalkerContext context = { -1, NIL };
|
|
|
|
if (query->cteList == NIL)
|
|
{
|
|
/* no CTEs, nothing to do */
|
|
return NULL;
|
|
}
|
|
|
|
if (query->hasModifyingCTE)
|
|
{
|
|
/* we could easily support these, but it's a little scary */
|
|
return DeferredError(ERRCODE_FEATURE_NOT_SUPPORTED,
|
|
"data-modifying statements are not supported in "
|
|
"the WITH clauses of distributed queries",
|
|
NULL, NULL);
|
|
}
|
|
|
|
if (query->hasRecursive)
|
|
{
|
|
return DeferredError(ERRCODE_FEATURE_NOT_SUPPORTED,
|
|
"recursive CTEs are not supported in distributed "
|
|
"queries",
|
|
NULL, NULL);
|
|
}
|
|
|
|
/* get all RTE_CTEs that point to CTEs from cteList */
|
|
CteReferenceListWalker((Node *) query, &context);
|
|
|
|
foreach(cteCell, query->cteList)
|
|
{
|
|
CommonTableExpr *cte = (CommonTableExpr *) lfirst(cteCell);
|
|
char *cteName = cte->ctename;
|
|
Query *subquery = (Query *) cte->ctequery;
|
|
uint64 planId = planningContext->planId;
|
|
uint32 subPlanId = 0;
|
|
Query *resultQuery = NULL;
|
|
DistributedSubPlan *subPlan = NULL;
|
|
ListCell *rteCell = NULL;
|
|
int replacedCtesCount = 0;
|
|
|
|
if (ContainsReferencesToOuterQuery(subquery))
|
|
{
|
|
return DeferredError(ERRCODE_FEATURE_NOT_SUPPORTED,
|
|
"CTEs that refer to other subqueries are not "
|
|
"supported in multi-shard queries",
|
|
NULL, NULL);
|
|
}
|
|
|
|
if (cte->cterefcount == 0)
|
|
{
|
|
/*
|
|
* CTEs that aren't referenced aren't executed in postgres. We
|
|
* don't need to generate a subplan for it and can take the rest
|
|
* of this iteration off.
|
|
*/
|
|
continue;
|
|
}
|
|
|
|
subPlanId = list_length(planningContext->subPlanList) + 1;
|
|
|
|
if (log_min_messages <= DEBUG1 || client_min_messages <= DEBUG1)
|
|
{
|
|
StringInfo subPlanString = makeStringInfo();
|
|
pg_get_query_def(subquery, subPlanString);
|
|
ereport(DEBUG1, (errmsg("generating subplan " UINT64_FORMAT "_%u for "
|
|
"CTE %s: %s",
|
|
planId, subPlanId, cteName, subPlanString->data)));
|
|
}
|
|
|
|
/* build a sub plan for the CTE */
|
|
subPlan = CreateDistributedSubPlan(subPlanId, subquery);
|
|
planningContext->subPlanList = lappend(planningContext->subPlanList, subPlan);
|
|
|
|
/* replace references to the CTE with a subquery that reads results */
|
|
resultQuery = BuildSubPlanResultQuery(subquery, planId, subPlanId);
|
|
|
|
foreach(rteCell, context.cteReferenceList)
|
|
{
|
|
RangeTblEntry *rangeTableEntry = (RangeTblEntry *) lfirst(rteCell);
|
|
|
|
if (rangeTableEntry->rtekind != RTE_CTE)
|
|
{
|
|
/*
|
|
* This RTE pointed to a preceding CTE that was already replaced
|
|
* by a subplan.
|
|
*/
|
|
continue;
|
|
}
|
|
|
|
if (strncmp(rangeTableEntry->ctename, cteName, NAMEDATALEN) == 0)
|
|
{
|
|
/* change the RTE_CTE into an RTE_SUBQUERY */
|
|
rangeTableEntry->rtekind = RTE_SUBQUERY;
|
|
rangeTableEntry->ctename = NULL;
|
|
rangeTableEntry->ctelevelsup = 0;
|
|
|
|
if (replacedCtesCount == 0)
|
|
{
|
|
/*
|
|
* Replace the first CTE reference with the result query directly.
|
|
*/
|
|
rangeTableEntry->subquery = resultQuery;
|
|
}
|
|
else
|
|
{
|
|
/*
|
|
* Replace subsequent CTE references with a copy of the result
|
|
* query.
|
|
*/
|
|
rangeTableEntry->subquery = copyObject(resultQuery);
|
|
}
|
|
|
|
replacedCtesCount++;
|
|
}
|
|
}
|
|
|
|
Assert(cte->cterefcount == replacedCtesCount);
|
|
}
|
|
|
|
/*
|
|
* All CTEs are now executed through subplans and RTE_CTEs pointing
|
|
* to the CTE list have been replaced with subqueries. We can now
|
|
* clear the cteList.
|
|
*/
|
|
query->cteList = NIL;
|
|
|
|
return NULL;
|
|
}
|
|
|
|
|
|
/*
|
|
* RecursivelyPlanSubqueryWalker recursively finds all the Query nodes and
|
|
* recursively plans if necessary.
|
|
*/
|
|
static bool
|
|
RecursivelyPlanSubqueryWalker(Node *node, RecursivePlanningContext *context)
|
|
{
|
|
if (node == NULL)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (IsA(node, Query))
|
|
{
|
|
Query *query = (Query *) node;
|
|
DeferredErrorMessage *error = NULL;
|
|
|
|
context->level += 1;
|
|
|
|
/*
|
|
* First, make sure any subqueries and CTEs within this subquery
|
|
* are recursively planned if necessary.
|
|
*/
|
|
error = RecursivelyPlanSubqueriesAndCTEs(query, context);
|
|
if (error != NULL)
|
|
{
|
|
RaiseDeferredError(error, ERROR);
|
|
}
|
|
context->level -= 1;
|
|
|
|
/*
|
|
* Recursively plan this subquery if it cannot be pushed down and is
|
|
* eligible for recursive planning.
|
|
*/
|
|
if (ShouldRecursivelyPlanSubquery(query))
|
|
{
|
|
RecursivelyPlanSubquery(query, context);
|
|
}
|
|
|
|
/* we're done, no need to recurse anymore for this query */
|
|
return false;
|
|
}
|
|
|
|
return expression_tree_walker(node, RecursivelyPlanSubqueryWalker, context);
|
|
}
|
|
|
|
|
|
/*
|
|
* ShouldRecursivelyPlanSubquery decides whether the input subquery should be recursively
|
|
* planned or not.
|
|
*
|
|
* For the details, see the cases in the function.
|
|
*/
|
|
static bool
|
|
ShouldRecursivelyPlanSubquery(Query *subquery)
|
|
{
|
|
if (FindNodeCheckInRangeTableList(subquery->rtable, IsLocalTableRTE))
|
|
{
|
|
/*
|
|
* Postgres can always plan queries that don't require distributed planning.
|
|
* Note that we need to check this first, otherwise the calls to the many other
|
|
* Citus planner functions would error our due to local relations.
|
|
*
|
|
* TODO: We could only successfully create distributed plans with local tables
|
|
* when the local tables are on the leaf queries and the upper level queries
|
|
* do not contain any other local tables.
|
|
*/
|
|
}
|
|
else if (DeferErrorIfCannotPushdownSubquery(subquery, false) == NULL)
|
|
{
|
|
/*
|
|
* Citus can pushdown this subquery, no need to recursively
|
|
* plan which is much expensive than pushdown.
|
|
*/
|
|
return false;
|
|
}
|
|
else if (TaskExecutorType == MULTI_EXECUTOR_TASK_TRACKER &&
|
|
SingleRelationRepartitionSubquery(subquery))
|
|
{
|
|
/*
|
|
* Citus can plan this and execute via repartitioning. Thus,
|
|
* no need to recursively plan.
|
|
*/
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
* Even if we could recursively plan the subquery, we should ensure
|
|
* that the subquery doesn't contain any references to the outer
|
|
* queries.
|
|
*/
|
|
if (ContainsReferencesToOuterQuery(subquery))
|
|
{
|
|
elog(DEBUG2, "skipping recursive planning for the subquery since it "
|
|
"contains references to outer queries");
|
|
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
/*
|
|
* IsLocalTableRTE gets a node and returns true if the node
|
|
* is a range table relation entry that points to a local
|
|
* relation (i.e., not a distributed relation).
|
|
*/
|
|
static bool
|
|
IsLocalTableRTE(Node *node)
|
|
{
|
|
RangeTblEntry *rangeTableEntry = NULL;
|
|
Oid relationId = InvalidOid;
|
|
|
|
if (node == NULL)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (!IsA(node, RangeTblEntry))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
rangeTableEntry = (RangeTblEntry *) node;
|
|
if (rangeTableEntry->rtekind != RTE_RELATION)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (rangeTableEntry->relkind == RELKIND_VIEW)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
relationId = rangeTableEntry->relid;
|
|
if (IsDistributedTable(relationId))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
/* local table found */
|
|
return true;
|
|
}
|
|
|
|
|
|
/*
|
|
* RecursivelyPlanQuery recursively plans a query, replaces it with a
|
|
* result query and returns the subplan.
|
|
*/
|
|
static void
|
|
RecursivelyPlanSubquery(Query *subquery, RecursivePlanningContext *planningContext)
|
|
{
|
|
DistributedSubPlan *subPlan = NULL;
|
|
uint64 planId = planningContext->planId;
|
|
int subPlanId = 0;
|
|
|
|
Query *resultQuery = NULL;
|
|
Query *debugQuery = NULL;
|
|
|
|
/*
|
|
* Subquery will go through the standard planner, thus to properly deparse it
|
|
* we keep its copy: debugQuery.
|
|
*/
|
|
if (log_min_messages <= DEBUG1 || client_min_messages <= DEBUG1)
|
|
{
|
|
debugQuery = copyObject(subquery);
|
|
}
|
|
|
|
/*
|
|
* Create the subplan and append it to the list in the planning context.
|
|
*/
|
|
subPlanId = list_length(planningContext->subPlanList) + 1;
|
|
|
|
subPlan = CreateDistributedSubPlan(subPlanId, subquery);
|
|
planningContext->subPlanList = lappend(planningContext->subPlanList, subPlan);
|
|
|
|
resultQuery = BuildSubPlanResultQuery(subquery, planId, subPlanId);
|
|
|
|
if (log_min_messages <= DEBUG1 || client_min_messages <= DEBUG1)
|
|
{
|
|
StringInfo subqueryString = makeStringInfo();
|
|
|
|
pg_get_query_def(debugQuery, subqueryString);
|
|
|
|
ereport(DEBUG1, (errmsg("generating subplan " UINT64_FORMAT "_%u for "
|
|
"subquery %s",
|
|
planId, subPlanId, subqueryString->data)));
|
|
}
|
|
|
|
/* finally update the input subquery to point the result query */
|
|
memcpy(subquery, resultQuery, sizeof(Query));
|
|
}
|
|
|
|
|
|
/*
|
|
* CreateDistributedSubPlan creates a distributed subplan by recursively calling
|
|
* the planner from the top, which may either generate a local plan or another
|
|
* distributed plan, which can itself contain subplans.
|
|
*/
|
|
static DistributedSubPlan *
|
|
CreateDistributedSubPlan(uint32 subPlanId, Query *subPlanQuery)
|
|
{
|
|
DistributedSubPlan *subPlan = NULL;
|
|
int cursorOptions = 0;
|
|
|
|
if (ContainsReadIntermediateResultFunction((Node *) subPlanQuery))
|
|
{
|
|
/*
|
|
* Make sure we go through distributed planning if there are
|
|
* read_intermediate_result calls, even if there are no distributed
|
|
* tables in the query anymore.
|
|
*
|
|
* We cannot perform this check in the planner itself, since that
|
|
* would also cause the workers to attempt distributed planning.
|
|
*/
|
|
cursorOptions |= CURSOR_OPT_FORCE_DISTRIBUTED;
|
|
}
|
|
|
|
subPlan = CitusMakeNode(DistributedSubPlan);
|
|
subPlan->plan = planner(subPlanQuery, cursorOptions, NULL);
|
|
subPlan->subPlanId = subPlanId;
|
|
|
|
return subPlan;
|
|
}
|
|
|
|
|
|
/*
|
|
* CteReferenceListWalker finds all references to CTEs in the top level of a query
|
|
* and adds them to context->cteReferenceList.
|
|
*/
|
|
static bool
|
|
CteReferenceListWalker(Node *node, CteReferenceWalkerContext *context)
|
|
{
|
|
if (node == NULL)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (IsA(node, RangeTblEntry))
|
|
{
|
|
RangeTblEntry *rangeTableEntry = (RangeTblEntry *) node;
|
|
|
|
if (rangeTableEntry->rtekind == RTE_CTE &&
|
|
rangeTableEntry->ctelevelsup == context->level)
|
|
{
|
|
context->cteReferenceList = lappend(context->cteReferenceList,
|
|
rangeTableEntry);
|
|
}
|
|
|
|
/* caller will descend into range table entry */
|
|
return false;
|
|
}
|
|
else if (IsA(node, Query))
|
|
{
|
|
Query *query = (Query *) node;
|
|
|
|
context->level += 1;
|
|
query_tree_walker(query, CteReferenceListWalker, context, QTW_EXAMINE_RTES);
|
|
context->level -= 1;
|
|
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
return expression_tree_walker(node, CteReferenceListWalker, context);
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* ContainsReferencesToOuterQuery determines whether the given query contains
|
|
* any Vars that point outside of the query itself. Such queries cannot be
|
|
* planned recursively.
|
|
*/
|
|
static bool
|
|
ContainsReferencesToOuterQuery(Query *query)
|
|
{
|
|
VarLevelsUpWalkerContext context = { 0 };
|
|
int flags = 0;
|
|
|
|
return query_tree_walker(query, ContainsReferencesToOuterQueryWalker,
|
|
&context, flags);
|
|
}
|
|
|
|
|
|
/*
|
|
* ContainsReferencesToOuterQueryWalker determines whether the given query
|
|
* contains any Vars that point more than context->level levels up.
|
|
*
|
|
* ContainsReferencesToOuterQueryWalker recursively descends into subqueries
|
|
* and increases the level by 1 before recursing.
|
|
*/
|
|
static bool
|
|
ContainsReferencesToOuterQueryWalker(Node *node, VarLevelsUpWalkerContext *context)
|
|
{
|
|
if (node == NULL)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (IsA(node, Var))
|
|
{
|
|
if (((Var *) node)->varlevelsup > context->level)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
else if (IsA(node, Aggref))
|
|
{
|
|
if (((Aggref *) node)->agglevelsup > context->level)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
else if (IsA(node, GroupingFunc))
|
|
{
|
|
if (((GroupingFunc *) node)->agglevelsup > context->level)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
else if (IsA(node, PlaceHolderVar))
|
|
{
|
|
if (((PlaceHolderVar *) node)->phlevelsup > context->level)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
else if (IsA(node, Query))
|
|
{
|
|
Query *query = (Query *) node;
|
|
bool found = false;
|
|
int flags = 0;
|
|
|
|
context->level += 1;
|
|
found = query_tree_walker(query, ContainsReferencesToOuterQueryWalker,
|
|
context, flags);
|
|
context->level -= 1;
|
|
|
|
return found;
|
|
}
|
|
|
|
return expression_tree_walker(node, ContainsReferencesToOuterQueryWalker,
|
|
context);
|
|
}
|
|
|
|
|
|
/*
|
|
* BuildSubPlanResultQuery returns a query of the form:
|
|
*
|
|
* SELECT
|
|
* <target list>
|
|
* FROM
|
|
* read_intermediate_result('<planId>_<subPlanId>', '<copy format'>)
|
|
* AS res (<column definition list>);
|
|
*
|
|
* The target list and column definition list are derived from the given subquery.
|
|
*
|
|
* If any of the types in the target list cannot be used in the binary copy format,
|
|
* then the copy format 'text' is used, otherwise 'binary' is used.
|
|
*/
|
|
static Query *
|
|
BuildSubPlanResultQuery(Query *subquery, uint64 planId, uint32 subPlanId)
|
|
{
|
|
Query *resultQuery = NULL;
|
|
char *resultIdString = NULL;
|
|
Const *resultIdConst = NULL;
|
|
Const *resultFormatConst = NULL;
|
|
FuncExpr *funcExpr = NULL;
|
|
Alias *funcAlias = NULL;
|
|
List *funcColNames = NIL;
|
|
List *funcColTypes = NIL;
|
|
List *funcColTypMods = NIL;
|
|
List *funcColCollations = NIL;
|
|
RangeTblFunction *rangeTableFunction = NULL;
|
|
RangeTblEntry *rangeTableEntry = NULL;
|
|
RangeTblRef *rangeTableRef = NULL;
|
|
FromExpr *joinTree = NULL;
|
|
ListCell *targetEntryCell = NULL;
|
|
List *targetList = NIL;
|
|
int columnNumber = 1;
|
|
bool useBinaryCopyFormat = true;
|
|
Oid copyFormatId = BinaryCopyFormatId();
|
|
|
|
/* build the target list and column definition list */
|
|
foreach(targetEntryCell, subquery->targetList)
|
|
{
|
|
TargetEntry *targetEntry = (TargetEntry *) lfirst(targetEntryCell);
|
|
Node *targetExpr = (Node *) targetEntry->expr;
|
|
char *columnName = targetEntry->resname;
|
|
Oid columnType = exprType(targetExpr);
|
|
Oid columnTypMod = exprTypmod(targetExpr);
|
|
Oid columnCollation = exprCollation(targetExpr);
|
|
Var *functionColumnVar = NULL;
|
|
TargetEntry *newTargetEntry = NULL;
|
|
|
|
if (targetEntry->resjunk)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
funcColNames = lappend(funcColNames, makeString(columnName));
|
|
funcColTypes = lappend_int(funcColTypes, columnType);
|
|
funcColTypMods = lappend_int(funcColTypMods, columnTypMod);
|
|
funcColCollations = lappend_int(funcColCollations, columnCollation);
|
|
|
|
functionColumnVar = makeNode(Var);
|
|
functionColumnVar->varno = 1;
|
|
functionColumnVar->varattno = columnNumber;
|
|
functionColumnVar->vartype = columnType;
|
|
functionColumnVar->vartypmod = columnTypMod;
|
|
functionColumnVar->varcollid = columnCollation;
|
|
functionColumnVar->varlevelsup = 0;
|
|
functionColumnVar->varnoold = 1;
|
|
functionColumnVar->varoattno = columnNumber;
|
|
functionColumnVar->location = -1;
|
|
|
|
newTargetEntry = makeNode(TargetEntry);
|
|
newTargetEntry->expr = (Expr *) functionColumnVar;
|
|
newTargetEntry->resno = columnNumber;
|
|
newTargetEntry->resname = columnName;
|
|
newTargetEntry->resjunk = false;
|
|
|
|
targetList = lappend(targetList, newTargetEntry);
|
|
|
|
if (useBinaryCopyFormat && !CanUseBinaryCopyFormatForType(columnType))
|
|
{
|
|
useBinaryCopyFormat = false;
|
|
}
|
|
|
|
columnNumber++;
|
|
}
|
|
|
|
/* build the result_id parameter for the call to read_intermediate_result */
|
|
resultIdString = GenerateResultId(planId, subPlanId);
|
|
|
|
resultIdConst = makeNode(Const);
|
|
resultIdConst->consttype = TEXTOID;
|
|
resultIdConst->consttypmod = -1;
|
|
resultIdConst->constlen = -1;
|
|
resultIdConst->constvalue = CStringGetTextDatum(resultIdString);
|
|
resultIdConst->constbyval = false;
|
|
resultIdConst->constisnull = false;
|
|
resultIdConst->location = -1;
|
|
|
|
/* build the citus_copy_format parameter for the call to read_intermediate_result */
|
|
if (!useBinaryCopyFormat)
|
|
{
|
|
copyFormatId = TextCopyFormatId();
|
|
}
|
|
|
|
resultFormatConst = makeNode(Const);
|
|
resultFormatConst->consttype = CitusCopyFormatTypeId();
|
|
resultFormatConst->consttypmod = -1;
|
|
resultFormatConst->constlen = 4;
|
|
resultFormatConst->constvalue = ObjectIdGetDatum(copyFormatId);
|
|
resultFormatConst->constbyval = true;
|
|
resultFormatConst->constisnull = false;
|
|
resultFormatConst->location = -1;
|
|
|
|
/* build the call to read_intermediate_result */
|
|
funcExpr = makeNode(FuncExpr);
|
|
funcExpr->funcid = CitusReadIntermediateResultFuncId();
|
|
funcExpr->funcretset = true;
|
|
funcExpr->funcvariadic = false;
|
|
funcExpr->funcformat = 0;
|
|
funcExpr->funccollid = 0;
|
|
funcExpr->inputcollid = 0;
|
|
funcExpr->location = -1;
|
|
funcExpr->args = list_make2(resultIdConst, resultFormatConst);
|
|
|
|
/* build the RTE for the call to read_intermediate_result */
|
|
rangeTableFunction = makeNode(RangeTblFunction);
|
|
rangeTableFunction->funccolcount = list_length(funcColNames);
|
|
rangeTableFunction->funccolnames = funcColNames;
|
|
rangeTableFunction->funccoltypes = funcColTypes;
|
|
rangeTableFunction->funccoltypmods = funcColTypMods;
|
|
rangeTableFunction->funccolcollations = funcColCollations;
|
|
rangeTableFunction->funcparams = NULL;
|
|
rangeTableFunction->funcexpr = (Node *) funcExpr;
|
|
|
|
funcAlias = makeNode(Alias);
|
|
funcAlias->aliasname = "intermediate_result";
|
|
funcAlias->colnames = funcColNames;
|
|
|
|
rangeTableEntry = makeNode(RangeTblEntry);
|
|
rangeTableEntry->rtekind = RTE_FUNCTION;
|
|
rangeTableEntry->functions = list_make1(rangeTableFunction);
|
|
rangeTableEntry->inFromCl = true;
|
|
rangeTableEntry->eref = funcAlias;
|
|
|
|
/* build the join tree using the read_intermediate_result RTE */
|
|
rangeTableRef = makeNode(RangeTblRef);
|
|
rangeTableRef->rtindex = 1;
|
|
|
|
joinTree = makeNode(FromExpr);
|
|
joinTree->fromlist = list_make1(rangeTableRef);
|
|
|
|
/* build the SELECT query */
|
|
resultQuery = makeNode(Query);
|
|
resultQuery->commandType = CMD_SELECT;
|
|
resultQuery->rtable = list_make1(rangeTableEntry);
|
|
resultQuery->jointree = joinTree;
|
|
resultQuery->targetList = targetList;
|
|
|
|
return resultQuery;
|
|
}
|
|
|
|
|
|
/*
|
|
* GenerateResultId generates the result ID that is used to identify an intermediate
|
|
* result of the subplan with the given plan ID and subplan ID.
|
|
*/
|
|
char *
|
|
GenerateResultId(uint64 planId, uint32 subPlanId)
|
|
{
|
|
StringInfo resultId = makeStringInfo();
|
|
|
|
appendStringInfo(resultId, UINT64_FORMAT "_%u", planId, subPlanId);
|
|
|
|
return resultId->data;
|
|
}
|