citus/src/backend/distributed/utils/citus_clauses.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* */