mirror of https://github.com/citusdata/citus.git
Merge pull request #2358 from citusdata/fix_func_eval
Evaluate functions and parameters anywhere in query cr: @jasonmp85pull/2356/head
commit
26178f72d3
|
@ -26,18 +26,8 @@
|
||||||
#include "utils/lsyscache.h"
|
#include "utils/lsyscache.h"
|
||||||
|
|
||||||
|
|
||||||
typedef struct FunctionEvaluationContext
|
|
||||||
{
|
|
||||||
PlanState *planState;
|
|
||||||
bool containsVar;
|
|
||||||
} FunctionEvaluationContext;
|
|
||||||
|
|
||||||
|
|
||||||
/* private function declarations */
|
/* private function declarations */
|
||||||
static void EvaluateValuesListsItems(List *valuesLists, PlanState *planState);
|
static bool IsVarNode(Node *node);
|
||||||
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,
|
static Expr * citus_evaluate_expr(Expr *expr, Oid result_type, int32 result_typmod,
|
||||||
Oid result_collation, PlanState *planState);
|
Oid result_collation, PlanState *planState);
|
||||||
static bool CitusIsVolatileFunctionIdChecker(Oid func_id, void *context);
|
static bool CitusIsVolatileFunctionIdChecker(Oid func_id, void *context);
|
||||||
|
@ -45,250 +35,36 @@ static bool CitusIsMutableFunctionIdChecker(Oid func_id, void *context);
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Whether the executor needs to reparse and try to execute this query.
|
* RequiresMastereEvaluation returns the executor needs to reparse and
|
||||||
|
* try to execute this query, which is the case if the query contains
|
||||||
|
* any stable or volatile function.
|
||||||
*/
|
*/
|
||||||
bool
|
bool
|
||||||
RequiresMasterEvaluation(Query *query)
|
RequiresMasterEvaluation(Query *query)
|
||||||
{
|
{
|
||||||
ListCell *targetEntryCell = NULL;
|
return FindNodeCheck((Node *) query, CitusIsMutableFunction);
|
||||||
ListCell *rteCell = NULL;
|
|
||||||
ListCell *cteCell = NULL;
|
|
||||||
|
|
||||||
foreach(targetEntryCell, query->targetList)
|
|
||||||
{
|
|
||||||
TargetEntry *targetEntry = (TargetEntry *) lfirst(targetEntryCell);
|
|
||||||
|
|
||||||
if (FindNodeCheck((Node *) targetEntry->expr, CitusIsMutableFunction))
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach(rteCell, query->rtable)
|
|
||||||
{
|
|
||||||
RangeTblEntry *rte = (RangeTblEntry *) lfirst(rteCell);
|
|
||||||
|
|
||||||
if (rte->rtekind == RTE_SUBQUERY)
|
|
||||||
{
|
|
||||||
if (RequiresMasterEvaluation(rte->subquery))
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (rte->rtekind == RTE_VALUES)
|
|
||||||
{
|
|
||||||
if (FindNodeCheck((Node *) rte->values_lists, CitusIsMutableFunction))
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach(cteCell, query->cteList)
|
|
||||||
{
|
|
||||||
CommonTableExpr *expr = (CommonTableExpr *) lfirst(cteCell);
|
|
||||||
|
|
||||||
if (RequiresMasterEvaluation((Query *) expr->ctequery))
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (query->jointree && query->jointree->quals)
|
|
||||||
{
|
|
||||||
if (FindNodeCheck((Node *) query->jointree->quals, CitusIsMutableFunction))
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Looks at each TargetEntry of the query and the jointree quals, evaluating
|
* ExecuteMasterEvaluableFunctions evaluates expressions that can be resolved
|
||||||
* any sub-expressions which don't include Vars.
|
* to a constant.
|
||||||
*/
|
*/
|
||||||
void
|
void
|
||||||
ExecuteMasterEvaluableFunctions(Query *query, PlanState *planState)
|
ExecuteMasterEvaluableFunctions(Query *query, PlanState *planState)
|
||||||
{
|
{
|
||||||
ListCell *targetEntryCell = NULL;
|
PartiallyEvaluateExpression((Node *) query, planState);
|
||||||
ListCell *rteCell = NULL;
|
|
||||||
ListCell *cteCell = NULL;
|
|
||||||
Node *modifiedNode = NULL;
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
modifiedNode = PartiallyEvaluateExpression((Node *) targetEntry->expr,
|
|
||||||
planState);
|
|
||||||
|
|
||||||
targetEntry->expr = (Expr *) modifiedNode;
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach(rteCell, query->rtable)
|
|
||||||
{
|
|
||||||
RangeTblEntry *rte = (RangeTblEntry *) lfirst(rteCell);
|
|
||||||
|
|
||||||
if (rte->rtekind == RTE_SUBQUERY)
|
|
||||||
{
|
|
||||||
ExecuteMasterEvaluableFunctions(rte->subquery, planState);
|
|
||||||
}
|
|
||||||
else if (rte->rtekind == RTE_VALUES)
|
|
||||||
{
|
|
||||||
EvaluateValuesListsItems(rte->values_lists, planState);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach(cteCell, query->cteList)
|
|
||||||
{
|
|
||||||
CommonTableExpr *expr = (CommonTableExpr *) lfirst(cteCell);
|
|
||||||
|
|
||||||
ExecuteMasterEvaluableFunctions((Query *) expr->ctequery, planState);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* EvaluateValuesListsItems siply does the work of walking over each expression
|
* PartiallyEvaluateExpression descend into an expression tree to evaluate
|
||||||
* in each value list contained in a multi-row INSERT's VALUES RTE. Basically
|
* expressions that can be resolved to a constant on the master. Expressions
|
||||||
* a nested for loop to perform an in-place replacement of expressions with
|
* containing a Var are skipped, since the value of the Var is not known
|
||||||
* their ultimate values, should evaluation be necessary.
|
* on the master.
|
||||||
*/
|
|
||||||
static void
|
|
||||||
EvaluateValuesListsItems(List *valuesLists, PlanState *planState)
|
|
||||||
{
|
|
||||||
ListCell *exprListCell = NULL;
|
|
||||||
|
|
||||||
foreach(exprListCell, valuesLists)
|
|
||||||
{
|
|
||||||
List *exprList = (List *) lfirst(exprListCell);
|
|
||||||
ListCell *exprCell = NULL;
|
|
||||||
|
|
||||||
foreach(exprCell, exprList)
|
|
||||||
{
|
|
||||||
Expr *expr = (Expr *) lfirst(exprCell);
|
|
||||||
Node *modifiedNode = NULL;
|
|
||||||
|
|
||||||
modifiedNode = PartiallyEvaluateExpression((Node *) expr, planState);
|
|
||||||
|
|
||||||
exprCell->data.ptr_value = (void *) modifiedNode;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Walks the expression evaluating any node which invokes a function as long as a Var
|
|
||||||
* doesn't show up in the parameter list.
|
|
||||||
*/
|
*/
|
||||||
Node *
|
Node *
|
||||||
PartiallyEvaluateExpression(Node *expression, PlanState *planState)
|
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ExecInitExpr cannot handle PARAM_SUBLINK */
|
|
||||||
if (IsA(expression, Param))
|
|
||||||
{
|
|
||||||
Param *param = (Param *) expression;
|
|
||||||
if (param->paramkind == PARAM_SUBLINK)
|
|
||||||
{
|
|
||||||
return expression;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (IsA(expression, Var))
|
|
||||||
{
|
|
||||||
context->containsVar = true;
|
|
||||||
|
|
||||||
/* makes a copy for us */
|
|
||||||
return expression_tree_mutator(expression,
|
|
||||||
PartiallyEvaluateExpressionMutator,
|
|
||||||
context);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* expression_tree_mutator does not descend into Query trees */
|
|
||||||
if (IsA(expression, Query))
|
|
||||||
{
|
|
||||||
Query *query = (Query *) expression;
|
|
||||||
|
|
||||||
return (Node *) query_tree_mutator(query, PartiallyEvaluateExpressionMutator,
|
|
||||||
context, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
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 (expression == NULL || IsA(expression, Const))
|
if (expression == NULL || IsA(expression, Const))
|
||||||
{
|
{
|
||||||
|
@ -297,6 +73,16 @@ EvaluateNodeIfReferencesFunction(Node *expression, PlanState *planState)
|
||||||
|
|
||||||
switch (nodeTag(expression))
|
switch (nodeTag(expression))
|
||||||
{
|
{
|
||||||
|
case T_Param:
|
||||||
|
{
|
||||||
|
Param *param = (Param *) expression;
|
||||||
|
if (param->paramkind == PARAM_SUBLINK)
|
||||||
|
{
|
||||||
|
/* ExecInitExpr cannot handle PARAM_SUBLINK */
|
||||||
|
return expression;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
case T_FuncExpr:
|
case T_FuncExpr:
|
||||||
case T_OpExpr:
|
case T_OpExpr:
|
||||||
case T_DistinctExpr:
|
case T_DistinctExpr:
|
||||||
|
@ -305,10 +91,16 @@ EvaluateNodeIfReferencesFunction(Node *expression, PlanState *planState)
|
||||||
case T_ArrayCoerceExpr:
|
case T_ArrayCoerceExpr:
|
||||||
case T_ScalarArrayOpExpr:
|
case T_ScalarArrayOpExpr:
|
||||||
case T_RowCompareExpr:
|
case T_RowCompareExpr:
|
||||||
case T_Param:
|
|
||||||
case T_RelabelType:
|
case T_RelabelType:
|
||||||
case T_CoerceToDomain:
|
case T_CoerceToDomain:
|
||||||
{
|
{
|
||||||
|
if (FindNodeCheck(expression, IsVarNode))
|
||||||
|
{
|
||||||
|
return (Node *) expression_tree_mutator(expression,
|
||||||
|
PartiallyEvaluateExpression,
|
||||||
|
planState);
|
||||||
|
}
|
||||||
|
|
||||||
return (Node *) citus_evaluate_expr((Expr *) expression,
|
return (Node *) citus_evaluate_expr((Expr *) expression,
|
||||||
exprType(expression),
|
exprType(expression),
|
||||||
exprTypmod(expression),
|
exprTypmod(expression),
|
||||||
|
@ -316,9 +108,18 @@ EvaluateNodeIfReferencesFunction(Node *expression, PlanState *planState)
|
||||||
planState);
|
planState);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case T_Query:
|
||||||
|
{
|
||||||
|
return (Node *) query_tree_mutator((Query *) expression,
|
||||||
|
PartiallyEvaluateExpression,
|
||||||
|
planState, QTW_DONT_COPY_QUERY);
|
||||||
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
{
|
{
|
||||||
break;
|
return (Node *) expression_tree_mutator(expression,
|
||||||
|
PartiallyEvaluateExpression,
|
||||||
|
planState);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -326,6 +127,16 @@ EvaluateNodeIfReferencesFunction(Node *expression, PlanState *planState)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* IsVarNode returns whether a node is a Var (column reference).
|
||||||
|
*/
|
||||||
|
static bool
|
||||||
|
IsVarNode(Node *node)
|
||||||
|
{
|
||||||
|
return IsA(node, Var);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* a copy of pg's evaluate_expr, pre-evaluate a constant expression
|
* a copy of pg's evaluate_expr, pre-evaluate a constant expression
|
||||||
*
|
*
|
||||||
|
|
Loading…
Reference in New Issue