mirror of https://github.com/citusdata/citus.git
405 lines
9.9 KiB
C
405 lines
9.9 KiB
C
/*
|
|
* citus_clauses.c
|
|
*
|
|
* Routines roughly equivalent to postgres' util/clauses.
|
|
*
|
|
* Copyright (c) 2016-2016, Citus Data, Inc.
|
|
*/
|
|
|
|
#include "postgres.h"
|
|
|
|
#include "distributed/citus_clauses.h"
|
|
#include "distributed/multi_router_planner.h"
|
|
|
|
#include "catalog/pg_type.h"
|
|
#include "executor/executor.h"
|
|
#include "nodes/makefuncs.h"
|
|
#include "nodes/nodeFuncs.h"
|
|
#include "nodes/nodes.h"
|
|
#include "nodes/primnodes.h"
|
|
#include "optimizer/clauses.h"
|
|
#include "optimizer/planmain.h"
|
|
#include "utils/datum.h"
|
|
#include "utils/lsyscache.h"
|
|
|
|
|
|
typedef struct FunctionEvaluationContext
|
|
{
|
|
PlanState *planState;
|
|
bool containsVar;
|
|
} FunctionEvaluationContext;
|
|
|
|
|
|
/* private function declarations */
|
|
static Node * PartiallyEvaluateExpression(Node *expression, PlanState *planState);
|
|
static Node * EvaluateNodeIfReferencesFunction(Node *expression, PlanState *planState);
|
|
static Node * PartiallyEvaluateExpressionMutator(Node *expression,
|
|
FunctionEvaluationContext *context);
|
|
static Expr * citus_evaluate_expr(Expr *expr, Oid result_type, int32 result_typmod,
|
|
Oid result_collation, PlanState *planState);
|
|
|
|
|
|
/*
|
|
* Whether the executor needs to reparse and try to execute this query.
|
|
*/
|
|
bool
|
|
RequiresMasterEvaluation(Query *query)
|
|
{
|
|
ListCell *targetEntryCell = NULL;
|
|
ListCell *rteCell = NULL;
|
|
ListCell *cteCell = NULL;
|
|
|
|
foreach(targetEntryCell, query->targetList)
|
|
{
|
|
TargetEntry *targetEntry = (TargetEntry *) lfirst(targetEntryCell);
|
|
|
|
if (contain_mutable_functions((Node *) targetEntry->expr))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
foreach(rteCell, query->rtable)
|
|
{
|
|
RangeTblEntry *rte = (RangeTblEntry *) lfirst(rteCell);
|
|
|
|
if (rte->rtekind != RTE_SUBQUERY)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (RequiresMasterEvaluation(rte->subquery))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
foreach(cteCell, query->cteList)
|
|
{
|
|
CommonTableExpr *expr = (CommonTableExpr *) lfirst(cteCell);
|
|
|
|
if (RequiresMasterEvaluation((Query *) expr->ctequery))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
if (query->jointree && query->jointree->quals)
|
|
{
|
|
return contain_mutable_functions((Node *) query->jointree->quals);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
/*
|
|
* Looks at each TargetEntry of the query and the jointree quals, evaluating
|
|
* any sub-expressions which don't include Vars.
|
|
*/
|
|
void
|
|
ExecuteMasterEvaluableFunctions(Query *query, PlanState *planState)
|
|
{
|
|
CmdType commandType = query->commandType;
|
|
ListCell *targetEntryCell = NULL;
|
|
ListCell *rteCell = NULL;
|
|
ListCell *cteCell = NULL;
|
|
Node *modifiedNode = NULL;
|
|
bool insertSelectQuery = InsertSelectQuery(query);
|
|
|
|
if (query->jointree && query->jointree->quals)
|
|
{
|
|
query->jointree->quals = PartiallyEvaluateExpression(query->jointree->quals,
|
|
planState);
|
|
}
|
|
|
|
foreach(targetEntryCell, query->targetList)
|
|
{
|
|
TargetEntry *targetEntry = (TargetEntry *) lfirst(targetEntryCell);
|
|
|
|
/* performance optimization for the most common cases */
|
|
if (IsA(targetEntry->expr, Const) || IsA(targetEntry->expr, Var))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (commandType == CMD_INSERT && !insertSelectQuery)
|
|
{
|
|
modifiedNode = EvaluateNodeIfReferencesFunction((Node *) targetEntry->expr,
|
|
planState);
|
|
}
|
|
else
|
|
{
|
|
modifiedNode = PartiallyEvaluateExpression((Node *) targetEntry->expr,
|
|
planState);
|
|
}
|
|
|
|
targetEntry->expr = (Expr *) modifiedNode;
|
|
}
|
|
|
|
foreach(rteCell, query->rtable)
|
|
{
|
|
RangeTblEntry *rte = (RangeTblEntry *) lfirst(rteCell);
|
|
|
|
if (rte->rtekind != RTE_SUBQUERY)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
ExecuteMasterEvaluableFunctions(rte->subquery, planState);
|
|
}
|
|
|
|
foreach(cteCell, query->cteList)
|
|
{
|
|
CommonTableExpr *expr = (CommonTableExpr *) lfirst(cteCell);
|
|
|
|
ExecuteMasterEvaluableFunctions((Query *) expr->ctequery, planState);
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* Walks the expression evaluating any node which invokes a function as long as a Var
|
|
* doesn't show up in the parameter list.
|
|
*/
|
|
static Node *
|
|
PartiallyEvaluateExpression(Node *expression, PlanState *planState)
|
|
{
|
|
FunctionEvaluationContext globalContext = { planState, false };
|
|
|
|
return PartiallyEvaluateExpressionMutator(expression, &globalContext);
|
|
}
|
|
|
|
|
|
/*
|
|
* When you find a function call evaluate it, the planner made sure there were no Vars.
|
|
*
|
|
* Tell your parent if either you or one if your children is a Var.
|
|
*
|
|
* A little inefficient. It goes to the bottom of the tree then calls EvaluateExpression
|
|
* on each function on the way back up. Say we had an expression with no Vars, we could
|
|
* only call EvaluateExpression on the top-most level and get the same result.
|
|
*/
|
|
static Node *
|
|
PartiallyEvaluateExpressionMutator(Node *expression, FunctionEvaluationContext *context)
|
|
{
|
|
Node *copy = NULL;
|
|
FunctionEvaluationContext localContext = { context->planState, false };
|
|
|
|
if (expression == NULL)
|
|
{
|
|
return expression;
|
|
}
|
|
|
|
/* pass any argument lists back to the mutator to copy and recurse for us */
|
|
if (IsA(expression, List))
|
|
{
|
|
return expression_tree_mutator(expression,
|
|
PartiallyEvaluateExpressionMutator,
|
|
context);
|
|
}
|
|
|
|
if (IsA(expression, Var))
|
|
{
|
|
context->containsVar = true;
|
|
|
|
/* makes a copy for us */
|
|
return expression_tree_mutator(expression,
|
|
PartiallyEvaluateExpressionMutator,
|
|
context);
|
|
}
|
|
|
|
copy = expression_tree_mutator(expression,
|
|
PartiallyEvaluateExpressionMutator,
|
|
&localContext);
|
|
|
|
if (localContext.containsVar)
|
|
{
|
|
context->containsVar = true;
|
|
}
|
|
else
|
|
{
|
|
copy = EvaluateNodeIfReferencesFunction(copy, context->planState);
|
|
}
|
|
|
|
return copy;
|
|
}
|
|
|
|
|
|
/*
|
|
* Used to evaluate functions during queries on the master before sending them to workers
|
|
*
|
|
* The idea isn't to evaluate every kind of expression, just the kinds whoes result might
|
|
* change between invocations (the idea is to allow users to use functions but still have
|
|
* consistent shard replicas, since we use statement replication). This means evaluating
|
|
* all nodes which invoke functions which might not be IMMUTABLE.
|
|
*/
|
|
static Node *
|
|
EvaluateNodeIfReferencesFunction(Node *expression, PlanState *planState)
|
|
{
|
|
if (IsA(expression, FuncExpr))
|
|
{
|
|
FuncExpr *expr = (FuncExpr *) expression;
|
|
|
|
return (Node *) citus_evaluate_expr((Expr *) expr,
|
|
expr->funcresulttype,
|
|
exprTypmod((Node *) expr),
|
|
expr->funccollid,
|
|
planState);
|
|
}
|
|
|
|
if (IsA(expression, OpExpr) ||
|
|
IsA(expression, DistinctExpr) ||
|
|
IsA(expression, NullIfExpr))
|
|
{
|
|
/* structural equivalence */
|
|
OpExpr *expr = (OpExpr *) expression;
|
|
|
|
return (Node *) citus_evaluate_expr((Expr *) expr,
|
|
expr->opresulttype, -1,
|
|
expr->opcollid,
|
|
planState);
|
|
}
|
|
|
|
if (IsA(expression, CoerceViaIO))
|
|
{
|
|
CoerceViaIO *expr = (CoerceViaIO *) expression;
|
|
|
|
return (Node *) citus_evaluate_expr((Expr *) expr,
|
|
expr->resulttype, -1,
|
|
expr->resultcollid,
|
|
planState);
|
|
}
|
|
|
|
if (IsA(expression, ArrayCoerceExpr))
|
|
{
|
|
ArrayCoerceExpr *expr = (ArrayCoerceExpr *) expression;
|
|
|
|
return (Node *) citus_evaluate_expr((Expr *) expr,
|
|
expr->resulttype,
|
|
expr->resulttypmod,
|
|
expr->resultcollid,
|
|
planState);
|
|
}
|
|
|
|
if (IsA(expression, ScalarArrayOpExpr))
|
|
{
|
|
ScalarArrayOpExpr *expr = (ScalarArrayOpExpr *) expression;
|
|
|
|
return (Node *) citus_evaluate_expr((Expr *) expr, BOOLOID, -1, InvalidOid,
|
|
planState);
|
|
}
|
|
|
|
if (IsA(expression, RowCompareExpr))
|
|
{
|
|
RowCompareExpr *expr = (RowCompareExpr *) expression;
|
|
|
|
return (Node *) citus_evaluate_expr((Expr *) expr, BOOLOID, -1, InvalidOid,
|
|
planState);
|
|
}
|
|
|
|
if (IsA(expression, Param))
|
|
{
|
|
Param *param = (Param *) expression;
|
|
|
|
return (Node *) citus_evaluate_expr((Expr *) param,
|
|
param->paramtype,
|
|
param->paramtypmod,
|
|
param->paramcollid,
|
|
planState);
|
|
}
|
|
|
|
return expression;
|
|
}
|
|
|
|
|
|
/*
|
|
* a copy of pg's evaluate_expr, pre-evaluate a constant expression
|
|
*
|
|
* We use the executor's routine ExecEvalExpr() to avoid duplication of
|
|
* code and ensure we get the same result as the executor would get.
|
|
*
|
|
* *INDENT-OFF*
|
|
*/
|
|
static Expr *
|
|
citus_evaluate_expr(Expr *expr, Oid result_type, int32 result_typmod,
|
|
Oid result_collation, PlanState *planState)
|
|
{
|
|
EState *estate;
|
|
ExprState *exprstate;
|
|
ExprContext *econtext;
|
|
MemoryContext oldcontext;
|
|
Datum const_val;
|
|
bool const_is_null;
|
|
int16 resultTypLen;
|
|
bool resultTypByVal;
|
|
|
|
/*
|
|
* To use the executor, we need an EState.
|
|
*/
|
|
estate = CreateExecutorState();
|
|
|
|
/* We can use the estate's working context to avoid memory leaks. */
|
|
oldcontext = MemoryContextSwitchTo(estate->es_query_cxt);
|
|
|
|
/* Make sure any opfuncids are filled in. */
|
|
fix_opfuncids((Node *) expr);
|
|
|
|
/*
|
|
* Prepare expr for execution. (Note: we can't use ExecPrepareExpr
|
|
* because it'd result in recursively invoking eval_const_expressions.)
|
|
*/
|
|
exprstate = ExecInitExpr(expr, planState);
|
|
|
|
if (planState != NULL)
|
|
{
|
|
/* use executor's context to pass down parameters */
|
|
econtext = planState->ps_ExprContext;
|
|
}
|
|
else
|
|
{
|
|
/* when called from a function, use a default context */
|
|
econtext = GetPerTupleExprContext(estate);
|
|
}
|
|
|
|
/*
|
|
* And evaluate it.
|
|
*/
|
|
const_val = ExecEvalExprSwitchContext(exprstate, econtext, &const_is_null, NULL);
|
|
|
|
/* Get info needed about result datatype */
|
|
get_typlenbyval(result_type, &resultTypLen, &resultTypByVal);
|
|
|
|
/* Get back to outer memory context */
|
|
MemoryContextSwitchTo(oldcontext);
|
|
|
|
/*
|
|
* Must copy result out of sub-context used by expression eval.
|
|
*
|
|
* Also, if it's varlena, forcibly detoast it. This protects us against
|
|
* storing TOAST pointers into plans that might outlive the referenced
|
|
* data. (makeConst would handle detoasting anyway, but it's worth a few
|
|
* extra lines here so that we can do the copy and detoast in one step.)
|
|
*/
|
|
if (!const_is_null)
|
|
{
|
|
if (resultTypLen == -1)
|
|
const_val = PointerGetDatum(PG_DETOAST_DATUM_COPY(const_val));
|
|
else
|
|
const_val = datumCopy(const_val, resultTypByVal, resultTypLen);
|
|
}
|
|
|
|
/* Release all the junk we just created */
|
|
FreeExecutorState(estate);
|
|
|
|
/*
|
|
* Make the constant result node.
|
|
*/
|
|
return (Expr *) makeConst(result_type, result_typmod, result_collation,
|
|
resultTypLen,
|
|
const_val, const_is_null,
|
|
resultTypByVal);
|
|
}
|
|
|
|
/* *INDENT-ON* */
|