From 5a663d7aff4caf097c5fbac070dca0fc04b4b859 Mon Sep 17 00:00:00 2001 From: Zhang Mingli Date: Mon, 4 Dec 2023 18:08:01 +0800 Subject: [PATCH] [AQUMV] Refactor codes to new file aqumv.c Refactor to external func answer_query_using_materialized_views with some refinements. Rename GUC to enable_answer_query_using_materialized_views to avoid duplicated name with implementation function. New function aqumv_init_context() to init context of rewrite. Authored-by: Zhang Mingli avamingli@gmail.com --- src/backend/optimizer/plan/Makefile | 3 +- src/backend/optimizer/plan/aqumv.c | 796 ++++++++++++++++++ src/backend/optimizer/plan/planner.c | 745 +--------------- src/backend/utils/misc/guc_gp.c | 6 +- src/include/optimizer/planner.h | 4 + src/include/utils/guc.h | 2 +- src/include/utils/unsync_guc_name.h | 2 +- src/test/regress/expected/aqumv.out | 92 +- src/test/regress/expected/rangefuncs_cdb.out | 2 +- src/test/regress/expected/sysviews.out | 2 +- src/test/regress/sql/aqumv.sql | 42 +- src/test/regress/sql/rangefuncs_cdb.sql | 2 +- src/test/regress/sql/sysviews.sql | 2 +- .../singlenode_regress/expected/sysviews.out | 2 +- src/test/singlenode_regress/sql/sysviews.sql | 2 +- 15 files changed, 885 insertions(+), 819 deletions(-) create mode 100644 src/backend/optimizer/plan/aqumv.c diff --git a/src/backend/optimizer/plan/Makefile b/src/backend/optimizer/plan/Makefile index 2a1ceb0ba70..5d4c6b71802 100644 --- a/src/backend/optimizer/plan/Makefile +++ b/src/backend/optimizer/plan/Makefile @@ -25,7 +25,8 @@ OBJS = \ planner.o \ setrefs.o \ subselect.o \ - transform.o + transform.o \ + aqumv.o ifeq ($(enable_orca),yes) OBJS += orca.o diff --git a/src/backend/optimizer/plan/aqumv.c b/src/backend/optimizer/plan/aqumv.c new file mode 100644 index 00000000000..24e0a9f9bbc --- /dev/null +++ b/src/backend/optimizer/plan/aqumv.c @@ -0,0 +1,796 @@ + +/*------------------------------------------------------------------------- + * + * aqumv.c + * Answer Query Using Materialzed Views. + * + * Portions Copyright (c) 2023, HashData Technology Limited. + * + * Author: Zhang Mingli + * + * IDENTIFICATION + * src/backend/optimizer/plan/aqumv.c + * + *------------------------------------------------------------------------- + */ + +#include "c.h" +#include "access/genam.h" +#include "access/htup_details.h" +#include "access/table.h" +#include "catalog/catalog.h" +#include "catalog/pg_class_d.h" +#include "catalog/pg_inherits.h" +#include "catalog/pg_rewrite.h" +#include "cdb/cdbllize.h" +#include "optimizer/optimizer.h" +#include "optimizer/planmain.h" +#include "optimizer/planner.h" +#include "optimizer/prep.h" +#include "optimizer/tlist.h" +#include "parser/analyze.h" +#include "parser/parsetree.h" +#include "parser/parse_node.h" +#include "tcop/tcopprot.h" +#include "utils/builtins.h" +#include "utils/guc.h" +#include "utils/lsyscache.h" +#include "utils/rel.h" +#include "nodes/makefuncs.h" +#include "nodes/nodeFuncs.h" +#include "nodes/pathnodes.h" +#include "nodes/pg_list.h" + +RelOptInfo *answer_query_using_materialized_views(PlannerInfo *root, RelOptInfo *current_rel); + +typedef struct +{ + int varno; +} aqumv_adjust_varno_context; + +static bool aqumv_process_from_quals(Node *query_quals, Node *mv_quals, List** post_quals); +static void aqumv_adjust_varno(Query *parse, int delta); +static Node *aqumv_adjust_varno_mutator(Node *node, aqumv_adjust_varno_context *context); + +typedef struct +{ + List *mv_pure_vars; /* List of pure Vars expression. */ + List *mv_pure_vars_index; /* Index list of pure Vars. */ + List *mv_nonpure_vars_index; /* Index list of nonpure Vars. */ + List *mv_query_tlist; /* mv query target list. */ + List *mv_tlist; /* mv relation's target list. */ + bool has_unmatched; /* True if we fail to rewrite an expression. */ +} aqumv_equivalent_transformation_context; + +static aqumv_equivalent_transformation_context* aqumv_init_context(List *view_tlist, List *mv_tlist); +static bool aqumv_process_targetlist(aqumv_equivalent_transformation_context *context, List *query_tlist, List **mv_final_tlist); +static void aqumv_process_nonpure_vars_expr(aqumv_equivalent_transformation_context* context); +static Node *aqumv_adjust_sub_matched_expr_mutator(Node *node, aqumv_equivalent_transformation_context *context); + +typedef struct +{ + int complexity; +} node_complexity_context; + +typedef struct +{ + int tlist_index; /* Index of tlist, begin from 1 */ + int count; /* Count of subnodes in this expression */ +} expr_to_sort; + +static inline Var *copyVarFromTatgetList(List* tlist, int var_index); + +/* + * Answer Query Using Materialized Views(AQUMV). + * This function modifies root(parse and etc.), current_rel in-place. + */ +RelOptInfo* +answer_query_using_materialized_views(PlannerInfo *root, RelOptInfo *current_rel) +{ + Query *parse = root->parse; /* Query of origin SQL. */ + Query *mvQuery; /* Query of view. */ + Query *mvRelQueryTree; /* Query tree of select from mv itself. */ + RelOptInfo *mv_final_rel = current_rel; /* Final rel after rewritten. */ + ListCell *lc; + Node *jtnode; + Node *mvjtnode; + int varno; + RangeTblEntry *rte; + RangeTblEntry *mvrte; + Relation ruleDesc; + Relation matviewRel; + SysScanDesc rcscan; + HeapTuple tup; + RewriteRule *rule; + Form_pg_rewrite rewrite_tup; + List *actions; + bool need_close = false; + PlannerInfo *subroot; + List *mv_final_tlist = NIL; /* Final target list we want to rewrite to. */ + List *post_quals = NIL; + aqumv_equivalent_transformation_context *context; + + bool can_not_use_mv = (parse->commandType != CMD_SELECT) || + (parse->rowMarks != NIL) || + parse->hasAggs || + parse->hasWindowFuncs || + parse->hasDistinctOn || + /* Group By without agg could be possible though IMMV doesn't support it yet. */ + (parse->groupClause != NIL) || + parse->hasModifyingCTE || + parse->sortClause || + (parse->parentStmtType == PARENTSTMTTYPE_REFRESH_MATVIEW) || + (parse->parentStmtType == PARENTSTMTTYPE_CTAS) || + parse->hasSubLinks; + + if (can_not_use_mv) + return mv_final_rel; + + /* + * AQUMV_FIXME_MVP: + * Single relation, excluding catalog/inherit/partition tables. + */ + if (list_length(parse->jointree->fromlist) != 1) + return mv_final_rel; + + jtnode = (Node *) linitial(parse->jointree->fromlist); + if (!IsA(jtnode, RangeTblRef)) + return mv_final_rel; + + varno = ((RangeTblRef *) jtnode)->rtindex; + rte = root->simple_rte_array[varno]; + Assert(rte != NULL); + + if ((rte->rtekind != RTE_RELATION) || + IsSystemClassByRelid(rte->relid) || + has_superclass(rte->relid) || + has_subclass(rte->relid)) + return mv_final_rel; + + ruleDesc = table_open(RewriteRelationId, AccessShareLock); + + rcscan = systable_beginscan(ruleDesc, InvalidOid, false, + NULL, 0, NULL); + + while (HeapTupleIsValid(tup = systable_getnext(rcscan))) + { + CHECK_FOR_INTERRUPTS(); + if (need_close) + table_close(matviewRel, AccessShareLock); + + rewrite_tup = (Form_pg_rewrite) GETSTRUCT(tup); + + matviewRel = table_open(rewrite_tup->ev_class, AccessShareLock); + need_close = true; + + /* + * AQUMV + * Currently the data of IVM is always up-to-date if there were. + * Take care of this when IVM defered-fefresh is supported. + */ + if (!(RelationIsIVM(matviewRel) && RelationIsPopulated(matviewRel))) + continue; + + if (matviewRel->rd_rel->relhasrules == false || + matviewRel->rd_rules->numLocks != 1) + continue; + + rule = matviewRel->rd_rules->rules[0]; + + /* Filter a SELECT action, and not instead. */ + if ((rule->event != CMD_SELECT) || !(rule->isInstead)) + continue; + + actions = rule->actions; + if (list_length(actions) != 1) + continue; + + /* + * AQUMV + * We will do some Equivalet Transformation on the mvQuery which + * represents the mv's corresponding query. + * + * AQUMV_FIXME_MVP: mvQuery is a simple query too, like the parse query. + * mvQuery->sortClause is ok here, though we can't use the Order by + * clause of mvQuery, and we have disabled the parse->sortClause. + * The reason is: the Order by clause of materialized view's query is + * typically pointless. We can't rely on the order even we wrote the + * ordered data into mv, ex: some other access methods except heap. + * The Seqscan on a heap-storaged mv seems ordered, but it's a free lunch. + * A Parallel Seqscan breaks that hypothesis. + */ + mvQuery = copyObject(linitial_node(Query, actions)); + Assert(IsA(mvQuery, Query)); + if(mvQuery->hasAggs || + mvQuery->hasWindowFuncs || + mvQuery->hasDistinctOn || + mvQuery->hasModifyingCTE || + mvQuery->hasSubLinks) + continue; + + if (list_length(mvQuery->jointree->fromlist) != 1) + continue; + + mvjtnode = (Node *) linitial(mvQuery->jointree->fromlist); + if (!IsA(mvjtnode, RangeTblRef)) + continue; + + /* + * AQUMV + * Require that the relation of mvQuery is a simple query too. + * We haven't do sth like: pull up sublinks or subqueries yet. + */ + varno = ((RangeTblRef*) mvjtnode)->rtindex; + mvrte = rt_fetch(varno, mvQuery->rtable); + Assert(mvrte != NULL); + + if (mvrte->rtekind != RTE_RELATION) + continue; + + /* + * AQUMV_FIXME_MVP + * Must be same relation, recursiviely embeded mv is not supported now. + */ + if (mvrte->relid != rte->relid) + continue; + + /* + * AQUMV_FIXME_MVP + * mv's query tree itself is needed to do the final replacement + * when we found corresponding column expression from mvQuery's + * TargetList by Query's. + * + * A plain SELECT on all columns seems the easiest way, though + * some columns may not be needed. + * And we get a mvRelQueryTree represents SELECT * FROM mv. + */ + char *mvname = quote_qualified_identifier(get_namespace_name(RelationGetNamespace(matviewRel)), + RelationGetRelationName(matviewRel)); + StringInfoData query_mv; + initStringInfo(&query_mv); + appendStringInfo(&query_mv, "SELECT * FROM %s", mvname); + List *raw_parsetree_list = pg_parse_query(query_mv.data); + + /* + * AQUMV_FIXME_MVP + * We should drop the mv if it has rules. + * Because mv's rules shouldn't apply to origin query. + */ + if (list_length(raw_parsetree_list) != 1) + continue; + + ParseState *mv_pstate = make_parsestate(NULL); + mv_pstate->p_sourcetext = query_mv.data; + mvRelQueryTree = transformTopLevelStmt(mv_pstate, linitial(raw_parsetree_list)); + free_parsestate(mv_pstate); + /* AQUMV_FIXME_MVP: free mvRelQueryTree? */ + + subroot = (PlannerInfo *) palloc(sizeof(PlannerInfo)); + memcpy(subroot, root, sizeof(PlannerInfo)); + subroot->parent_root = root; + /* + * AQUMV_FIXME_MVP: + * TODO: keep ECs and adjust varno? + */ + subroot->eq_classes = NIL; + /* Reset subplan-related stuff */ + subroot->plan_params = NIL; + subroot->outer_params = NULL; + subroot->init_plans = NIL; + subroot->agginfos = NIL; + subroot->aggtransinfos = NIL; + subroot->parse = mvQuery; + + /* + * AQUMV + * We have to rewrite now before we do the real Equivalent + * Transformation 'rewrite'. + * Because actions sotored in rule is not a normal query tree, + * it can't be used directly, ex: new/old realtions used to + * refresh mv. + * Earse unused relatoins, keep the right one. + */ + foreach(lc, mvQuery->rtable) + { + RangeTblEntry* rtetmp = lfirst(lc); + if ((rtetmp->relkind == RELKIND_MATVIEW) && + (rtetmp->alias != NULL) && + (strcmp(rtetmp->alias->aliasname, "new") == 0 || + strcmp(rtetmp->alias->aliasname,"old") == 0)) + { + foreach_delete_current(mvQuery->rtable, lc); + } + } + + /* + * Now we have the right relation, adjust + * varnos in its query tree. + * AQUMV_FIXME_MVP: Only one single relation + * is supported now, we could assign varno + * to 1 opportunistically. + */ + aqumv_adjust_varno(mvQuery, 1); + + /* + * AQUMV_FIXME_MVP + * Are stable functions OK? + * A STABLE function cannot modify the database and is guaranteed to + * return the same results given the same arguments for all rows + * within a single statement. + * But AQUMV rewrites the query to a new SQL actually, though the same + * results is guaranteed. + * Its's unclear whether STABLE is OK, let's be conservative for now. + */ + if(contain_mutable_functions((Node *)mvQuery)) + continue; + + context = aqumv_init_context(mvQuery->targetList, mvRelQueryTree->targetList); + + /* Sort nonpure vars expression, prepare for Greedy Algorithm. */ + aqumv_process_nonpure_vars_expr(context); + + /* + * Process and rewrite target list, return false if failed. + */ + if(!aqumv_process_targetlist(context, parse->targetList, &mv_final_tlist)) + continue; + + /* + * AQUMV + * Process all quals to conjunctive normal form. + * + * We assume that the selection predicates of view and query expressions + * have been converted into conjunctive normal form(CNF) before we process + * them. + * AQUMV_MVP: no having quals now. + */ + preprocess_qual_conditions(subroot, (Node *) mvQuery->jointree); + + /* + * Process quals, return false if failed. + * Else, post_quals are filled if there were. + * Like process target list, post_quals is used later to see if we could + * rewrite and apply it to mv relation. + */ + if(!aqumv_process_from_quals(parse->jointree->quals, mvQuery->jointree->quals, &post_quals)) + continue; + + /* Rewrite post_quals, return false if failed. */ + post_quals = (List *)aqumv_adjust_sub_matched_expr_mutator((Node *)post_quals, context); + if (context->has_unmatched) + continue; + + /* + * Here! We succeed to rewrite a new SQL. + * Begin to replace all guts. + */ + mvQuery->targetList = mv_final_tlist; + + /* + * AQUMV + * NB: Update processed_tlist again in case that tlist has been changed. + */ + preprocess_targetlist(subroot); + + /* + * AQUMV + * NB: Correct the final_locus as we select from another realtion now. + */ + PathTarget *newtarget = make_pathtarget_from_tlist(subroot->processed_tlist); + subroot->final_locus = cdbllize_get_final_locus(subroot, newtarget); + + /* Rewrite with mv's query tree*/ + mvrte->relkind = RELKIND_MATVIEW; + mvrte->relid = matviewRel->rd_rel->oid; + /* + * AQUMV_FIXME_MVP + * Not sure where it's true from actions even it's not inherit tables. + */ + mvrte->inh = false; + mvQuery->rtable = list_make1(mvrte); /* rewrite to SELECT FROM mv itself. */ + mvQuery->jointree->quals = (Node *)post_quals; /* Could be NULL, but doesn'y matter for now. */ + + /* + * AQUMV + * Build a plan of new SQL. + * AQUMV is cost-based, let planner decide which is better. + * AQUMV_FIXME_MVP: + * no qp_callback function now. + * replcace one-by-one? + */ + mv_final_rel = query_planner(subroot, NULL, NULL); + + /* AQUMV_FIXME_MVP + * We don't use STD_FUZZ_FACTOR for cost comparisons like compare_path_costs_fuzzily here. + * The STD_FUZZ_FACTOR is used to reduce paths of a rel, and keep the significantly ones. + * But in AQUMV, we always have only one best path of rel at the last to compare. + * TODO: limit clause and startup_cost. + */ + if (mv_final_rel->cheapest_total_path->total_cost < current_rel->cheapest_total_path->total_cost) + { + root->parse = mvQuery; + root->processed_tlist = subroot->processed_tlist; + /* + * AQUMV_FIXME_MVP + * Use new query's ecs. + * Equivalence Class is not supported now, we may lost some ECs if the mv_query has + * equal quals or implicit ones. + * But keeping them also introduces more complex as we should process them like target list. + * Another flaw: the generated Filter expressions by keeping them are pointless as all + * rows of mv have matched the filter expressions. + * See more in README.cbdb.aqumv + */ + root->eq_classes = subroot->eq_classes; + /* Replace relation, don't close the right one. */ + current_rel = mv_final_rel; + table_close(matviewRel, AccessShareLock); + need_close = false; + } + } + if (need_close) + table_close(matviewRel, AccessShareLock); + systable_endscan(rcscan); + table_close(ruleDesc, AccessShareLock); + + return current_rel; +} + +/* + * AQUMV + * Since tlist and quals rewrite are both based on mv query's tlist, + * put all stuff into a context. + */ +static aqumv_equivalent_transformation_context* +aqumv_init_context(List *view_tlist, List *mv_tlist) +{ + aqumv_equivalent_transformation_context *context = palloc0(sizeof(aqumv_equivalent_transformation_context)); + List *mv_pure_vars = NIL; /* TargetEntry[Var] in mv query. */ + List *mv_pure_vars_index = NIL; /* Index of TargetEntry[Var] in mv query. */ + List *mv_nonpure_vars_index = NIL; /* Index of nonpure[Var] expression in mv query. */ + Expr *expr; + ListCell *lc; + + /* + * Process mv_query's tlist to pure-Var and no pure-Var expressions. + * See details in README.cbdb.aqumv + */ + int i = 0; + foreach(lc, view_tlist) + { + i++; + TargetEntry* tle = lfirst_node(TargetEntry, lc); + expr = tle->expr; + if(IsA(expr, Var)) + { + mv_pure_vars = lappend(mv_pure_vars, expr); + mv_pure_vars_index = lappend_int(mv_pure_vars_index, i); + } + else + mv_nonpure_vars_index = lappend_int(mv_nonpure_vars_index, i); + } + + context->mv_pure_vars = mv_pure_vars; + context->mv_pure_vars_index = mv_pure_vars_index; + context->mv_nonpure_vars_index = mv_nonpure_vars_index; + context->mv_tlist = mv_tlist; + context->mv_query_tlist = view_tlist; + context->has_unmatched = false; + return context; +} + + + +/* + * Process varno after we eliminate mv's actions("old" and "new" relation) + * Correct rindex and all varnos with a delta. + * + * MV's actions query tree: + * [rtable] + * RangeTblEntry [rtekind=RTE_RELATION] + * [alias] Alias [aliasname="old"] + * RangeTblEntry [rtekind=RTE_RELATION] + * [alias] Alias [aliasname="new"] + * RangeTblEntry [rtekind=RTE_RELATION] + * [jointree] + * FromExpr [] + * [fromlist] + * RangeTblRef [rtindex=3] + * [targetList] + * TargetEntry [resno=1 resname="c1"] + * Var [varno=3 varattno=1] + * TargetEntry [resno=2 resname="c2"] + * Var [varno=3 varattno=2] + *------------------------------------------------------------------------------------------ + * MV's query tree after rewrite: + * [rtable] + * RangeTblEntry [rtekind=RTE_RELATION] + * [jointree] + * FromExpr [] + * [fromlist] + * RangeTblRef [rtindex=3] + * [targetList] + * TargetEntry [resno=1 resname="c1"] + * Var [varno=3 varattno=1] + * TargetEntry [resno=2 resname="c2"] + * Var [varno=3 varattno=2] + *------------------------------------------------------------------------------------------ + * MV's query tree after varno adjust: + * [rtable] + * RangeTblEntry [rtekind=RTE_RELATION] + * [jointree] + * FromExpr [] + * [fromlist] + * RangeTblRef [rtindex=1] + * [targetList] + * TargetEntry [resno=1 resname="c1"] + * Var [varno=1 varattno=1] + * TargetEntry [resno=2 resname="c2"] + * Var [varno=1 varattno=2] + * + */ +static void +aqumv_adjust_varno(Query* parse, int varno) +{ + aqumv_adjust_varno_context context; + context.varno = varno; + parse = query_tree_mutator(parse, aqumv_adjust_varno_mutator, &context, QTW_DONT_COPY_QUERY); +} + +/* + * Only for plain select * from mv; + * All TargetEntrys are pure Var. + * var_index start from 1 + */ +static inline Var * +copyVarFromTatgetList(List* tlist, int var_index) +{ + TargetEntry * tle = (TargetEntry *) list_nth(tlist, var_index - 1); + Assert(IsA(tle->expr,Var)); + Var *var = copyObject((Var *)tle->expr); + return var; +} + +/* + * Adjust varno and rindex with delta. + */ +static Node *aqumv_adjust_varno_mutator(Node *node, aqumv_adjust_varno_context *context) +{ + if (node == NULL) + return NULL; + if (IsA(node, Var)) + ((Var *)node)->varno = context->varno; + else if (IsA(node, RangeTblRef)) + /* AQUMV_FIXME_MVP: currently we have only one relation */ + ((RangeTblRef*) node)->rtindex = context->varno; + return expression_tree_mutator(node, aqumv_adjust_varno_mutator, context); +} + +/* + * Compute a node complexity recursively. + * Complexity of a node is the total times we enter walker function after all + * subnodes are walked recursively. + * It's used to sort the expression in mv's tlist. + */ +static bool +compute_node_complexity_walker(Node *node, node_complexity_context *context) +{ + if (node == NULL) + return false; + context->complexity++; + return expression_tree_walker(node, compute_node_complexity_walker, (void *) context); +} + +static int +nonpure_vars_expr_compare(const ListCell *a, const ListCell *b) +{ + expr_to_sort *ets1 = (expr_to_sort *) lfirst(a); + expr_to_sort *ets2 = (expr_to_sort *) lfirst(b); + return (ets1->count < ets2->count) ? 1 : (ets1->count == ets2->count) ? 0 : -1; +} + +/* + * In-place update order of mv_nonpure_vars_index List + */ +static void +aqumv_process_nonpure_vars_expr(aqumv_equivalent_transformation_context* context) +{ + ListCell* lc; + List *expr_to_sort_list = NIL; + foreach(lc, context->mv_nonpure_vars_index) + { + int index = lfirst_int(lc); + Node *expr = lfirst(list_nth_cell(context->mv_query_tlist, index -1)); + node_complexity_context *subnode_context = palloc0(sizeof(node_complexity_context)); + (void) compute_node_complexity_walker(expr, subnode_context); + expr_to_sort *ets = palloc0(sizeof(expr_to_sort)); + ets->tlist_index = index; + ets->count = subnode_context->complexity; + expr_to_sort_list = lappend(expr_to_sort_list, ets); + } + + /* Sort the expr list */ + list_sort(expr_to_sort_list, nonpure_vars_expr_compare); + /* Reorder mv_nonpure_vars_index */ + context->mv_nonpure_vars_index = NIL; + foreach(lc, expr_to_sort_list) + { + expr_to_sort *ets = (expr_to_sort *) lfirst(lc); + context->mv_nonpure_vars_index = lappend_int(context->mv_nonpure_vars_index, ets->tlist_index); + } + return; +} + +/* + * Process query and materialized views' quals. + * Return true if all mv_quals are in query_quals, + * else return false. + * + * If return true, put quals in query_quals but not in mv_quals + * into post_quals. + * + * Ex: create materialized view mv0 as select * from t1 where c1 = 1; + * query: select * from t1 where c1 = 1 and c2 = 2; + * post_quals = {c2 = 2}. + * + * AQUMV_FIXME_MVP: only support one relation now, so we don't need to + * compare varno(both are 1 after aqumv_adjust_varno), + * mv's query tree has been processed into one relation too. + * + * Will return false if varattno in mv->query has different order with query's. + * Ex: create materialized view mv0 as select c2, c1 from t1 where c1 = 1; + * query: select c1, c2 from t1 where c1 = 1 and c2 = 2; + * + * The returned post_quals may be or may not be used later, it's up to mv's targetList. + * + */ +static bool +aqumv_process_from_quals(Node *query_quals, Node *mv_quals, List **post_quals) +{ + List *qlist = NIL; + List *mlist = NIL; + + if (query_quals == NULL) + return mv_quals == NULL; + + if(!IsA(query_quals, List)) + qlist = list_make1(query_quals); + else + qlist = (List *)query_quals; + + if (mv_quals == NULL) + { + *post_quals = qlist; + return true; + } + + if(!IsA(mv_quals, List)) + mlist = list_make1(mv_quals); + else + mlist = (List *)mv_quals; + + if (list_difference(mlist, qlist) != NIL) + return false; + *post_quals = list_difference(qlist, mlist); + return true; +} + +/* + * Adjust query expr's Vars + * Replace Vars with corresponding attribute in mv relation. + * Return a new expr after rewrite. + */ +static Node *aqumv_adjust_sub_matched_expr_mutator(Node *node, aqumv_equivalent_transformation_context *context) +{ + if (!node || context->has_unmatched) + return node; + + bool is_targetEntry = IsA(node, TargetEntry); + Expr *node_expr = is_targetEntry ? ((TargetEntry *)node)->expr : (Expr *)node; + + /* Don't select Const results form mv, bypass it to upper when projection. */ + if (IsA(node_expr, Const)) + return is_targetEntry ? node : (Node *)node_expr; + + ListCell *lc = NULL; + foreach(lc, context->mv_nonpure_vars_index) + { + int index = lfirst_int(lc); + TargetEntry *tle = list_nth_node(TargetEntry, context->mv_query_tlist, index - 1); + if(equal(node_expr, tle->expr)) + { + Var *newVar = copyVarFromTatgetList(context->mv_tlist, index); + newVar->location = -2; /* hack here, use -2 to indicate already rewrited by mv rel Vars. */ + if (is_targetEntry) + { + TargetEntry *qtle = (TargetEntry *) node; + /* + * AQUMV_FIXME_MVP: + * resorigtbl, resorigcol, resjunck in mv_query is also rejunck in mv table itself ? + */ + TargetEntry *mvtle = makeTargetEntry((Expr *)newVar, qtle->resno, qtle->resname, qtle->resjunk); + return (Node *) mvtle; + } + else + return (Node *) newVar; + } + } + + /* + * We didn't find matched nonpure-Var expr. + * And if expr doesn't have Vars, return it to upper. + */ + List *expr_vars = pull_var_clause((Node *)node_expr, + PVC_RECURSE_AGGREGATES | + PVC_RECURSE_WINDOWFUNCS | + PVC_INCLUDE_PLACEHOLDERS); + + if (expr_vars == NIL) + return (Node *)node_expr; + list_free(expr_vars); + + /* Try match with mv_pure_vars_index, but do not disturb already rewrited exprs(Var->location = -2) */ + if (IsA(node_expr, Var)) + { + Var *var = (Var *)node_expr; + if (var->location == -2) + return node; + lc = NULL; + int i = 0; + foreach(lc, context->mv_pure_vars) + { + Var *pure_var = lfirst_node(Var,lc); + if (equal(node_expr, pure_var)) + { + int j = list_nth_int(context->mv_pure_vars_index, i); + Var *newvar = copyVarFromTatgetList(context->mv_tlist, j); + if (is_targetEntry) + { + ((TargetEntry *)node)->expr = (Expr *)newvar; + return node; + } + else + return (Node *)newvar; + } + i++; + } + context->has_unmatched = true; + } + + return expression_tree_mutator(node, aqumv_adjust_sub_matched_expr_mutator, context); +} + +/* + * Process query and materialized views' target list. + * Return true if all query_tlist are in mv_tlist. + * else return false. + * + * If return true, put tlist in mv_quals but not in query_tlist + * into post_tlist. + * + * Ex: create materialized view mv0 as select c1, c2 from t1 where c1 = 1; + * query: select c2 from t1 where c1 = 1; + * post_tlist= {1}. + * + * AQUMV_FIXME_MVP: strict match with same resno? + * MVP0: expression replace + * mv: select c1, c2 from t1 where c1 = 50; + * select c1 from t1 where c1 = 50 and abs(t1.c2) = 51; + * rewrite: select c1 from mv where abs(mv.c2) = 51; + * + * MVP1: expression eliminate + * mv: select c1, abs(c2) as c2 from t1 where c1 = 50; + * select c1 from t1 where c1 = 50 and abs(c2) = 51; + * rewrite: select c1 from mv where c2 = 51; + * + * mv_final_tlist is the final targetList of mvQuery. + * + */ +static bool +aqumv_process_targetlist(aqumv_equivalent_transformation_context *context, List *query_tlist, List **mv_final_tlist) +{ + *mv_final_tlist = (List *)aqumv_adjust_sub_matched_expr_mutator((Node *)(copyObject(query_tlist)), context); + if (context->has_unmatched) + pfree(*mv_final_tlist); + + return !context->has_unmatched; +} diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index bfbdf677f31..5690a3b2e54 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -86,17 +86,6 @@ #include "storage/lmgr.h" #include "utils/guc.h" -#include "access/genam.h" -#include "access/htup_details.h" -#include "access/table.h" -#include "catalog/catalog.h" -#include "catalog/pg_class_d.h" -#include "catalog/pg_rewrite.h" -#include "parser/parsetree.h" -#include "tcop/tcopprot.h" -#include "utils/builtins.h" -#include "utils/rel.h" - #ifdef USE_ORCA extern void InitGPOPT(); #endif @@ -172,7 +161,7 @@ typedef struct /* Local functions */ static Node *preprocess_expression(PlannerInfo *root, Node *expr, int kind); -static void preprocess_qual_conditions(PlannerInfo *root, Node *jtnode); +void preprocess_qual_conditions(PlannerInfo *root, Node *jtnode); static void grouping_planner(PlannerInfo *root, double tuple_fraction); static grouping_sets_data *preprocess_grouping_sets(PlannerInfo *root); static List *remap_to_groupclause_idx(List *groupClause, List *gsets, @@ -305,29 +294,6 @@ static split_rollup_data *make_new_rollups_for_hash_grouping_set(PlannerInfo *ro grouping_sets_data *gd); -typedef struct -{ - int varno; -} aqumv_adjust_varno_context; - -static bool aqumv_process_from_quals(Node *query_quals, Node *mv_quals, List** post_quals); -static void aqumv_adjust_varno(Query *parse, int delta); -static Node *aqumv_adjust_varno_mutator(Node *node, aqumv_adjust_varno_context *context); - -typedef struct -{ - List *mv_pure_vars; /* List of pure Vars expression. */ - List *mv_pure_vars_index; /* Index list of pure Vars. */ - List *mv_nonpure_vars_index; /* Index list of nonpure Vars. */ - List *mv_query_tlist; /* mv query target list. */ - List *mv_tlist; /* mv relation's target list. */ - bool has_unmatched; /* True if we fail to rewrite an expression. */ -} aqumv_equivalent_transformation_context; - -static bool aqumv_process_targetlist(aqumv_equivalent_transformation_context *context, List *query_tlist, List **mv_final_tlist); -static void aqumv_process_nonpure_vars_expr(aqumv_equivalent_transformation_context* context); -static Node *aqumv_adjust_sub_matched_expr_mutator(Node *node, aqumv_equivalent_transformation_context *context); - /***************************************************************************** * * Query optimizer entry point @@ -733,7 +699,7 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions, Assert(glob->resultRelations == NIL); /* AQUMV_FIXME_MVP: We may rewrite the parse tree. */ - AssertImply(!answer_query_using_materialized_views, parse == root->parse); + AssertImply(!enable_answer_query_using_materialized_views, parse == root->parse); #if 0 Assert(parse == root->parse); #endif @@ -1532,7 +1498,7 @@ preprocess_expression(PlannerInfo *root, Node *expr, int kind) * Recursively scan the query's jointree and do subquery_planner's * preprocessing work on each qual condition found therein. */ -static void +void preprocess_qual_conditions(PlannerInfo *root, Node *jtnode) { if (jtnode == NULL) @@ -1910,382 +1876,9 @@ grouping_planner(PlannerInfo *root, double tuple_fraction) /* * Answer Query Using Materialized Views(AQUMV). */ - RelOptInfo *mv_final_rel = NULL; - bool can_not_use_mv = (parse->commandType != CMD_SELECT) || - (parse->rowMarks != NIL) || - parse->hasAggs || - parse->hasWindowFuncs || - parse->hasDistinctOn || - parse->hasModifyingCTE || - parse->sortClause || - (parse->parentStmtType == PARENTSTMTTYPE_REFRESH_MATVIEW) || - (parse->parentStmtType == PARENTSTMTTYPE_CTAS) || - parse->hasSubLinks; - if (Gp_role == GP_ROLE_DISPATCH && - answer_query_using_materialized_views && - !can_not_use_mv && - (list_length(parse->jointree->fromlist) == 1)) - { - Node *jtnode = (Node *) linitial(parse->jointree->fromlist); - - if (IsA(jtnode, RangeTblRef)) - { - int varno = ((RangeTblRef *) jtnode)->rtindex; - RangeTblEntry *rte = root->simple_rte_array[varno]; - - Assert(rte != NULL); - - /* - * AQUMV_FIXME_MVP: - * Single relation, excluding catalog. - */ - if (rte->rtekind == RTE_RELATION && - !IsSystemClassByRelid(rte->relid) && - !has_superclass(rte->relid) && - !has_subclass(rte->relid)) - { - - Relation ruleDesc; - Relation matviewRel; - SysScanDesc rcscan; - HeapTuple tup; - RewriteRule *rule; - Form_pg_rewrite rewrite_tup; - List *actions; - Query *mvQuery; - Query *mvRelQueryTree; /* Query tree of select from mv itself. */ - Node *mvjtnode; - bool need_close = false; - - ruleDesc = table_open(RewriteRelationId, AccessShareLock); - - rcscan = systable_beginscan(ruleDesc, InvalidOid, false, - NULL, 0, NULL); - - while (HeapTupleIsValid(tup = systable_getnext(rcscan))) - { - CHECK_FOR_INTERRUPTS(); - if (need_close) - table_close(matviewRel, AccessShareLock); - - rewrite_tup = (Form_pg_rewrite) GETSTRUCT(tup); - - matviewRel = table_open(rewrite_tup->ev_class, AccessShareLock); - need_close = true; - - /* - * AQUMV - * Currently the data of IVM is always up-to-date if there were. - * Take care of this when IVM defered-fefresh is supported. - */ - if (!(RelationIsIVM(matviewRel) && RelationIsPopulated(matviewRel))) - continue; - - if (matviewRel->rd_rel->relhasrules == false || - matviewRel->rd_rules->numLocks != 1) - continue; - - rule = matviewRel->rd_rules->rules[0]; - - /* Filter a SELECT action, and not instead. */ - if ((rule->event != CMD_SELECT) || !(rule->isInstead)) - continue; - - actions = rule->actions; - if (list_length(actions) != 1) - continue; - - /* - * AQUMV - * We will do some Equivalet Transformation on the mvQuery which - * represents the mv's corresponding query. - * - * AQUMV_FIXME_MVP: mvQuery is a simple query too, like the parse query. - * mvQuery->sortClause is ok here, though we can't use the Order by - * clause of mvQuery, and we have disabled the parse->sortClause. - * The reason is: the Order by clause of materialized view's query is - * typically pointless. We can't rely on the order even we wrote the - * ordered data into mv, ex: some other access methods except heap. - * The Seqscan on a heap-storaged mv seems ordered, but it's a free lunch. - * A Parallel Seqscan breaks that hypothesis. - */ - mvQuery = copyObject(linitial_node(Query, actions)); - Assert(IsA(mvQuery, Query)); - if(mvQuery->hasAggs || - mvQuery->hasWindowFuncs || - mvQuery->hasDistinctOn || - mvQuery->hasModifyingCTE || - mvQuery->hasSubLinks) - continue; - - if (list_length(mvQuery->jointree->fromlist) != 1) - continue; - - mvjtnode = (Node *) linitial(mvQuery->jointree->fromlist); - if (!IsA(mvjtnode, RangeTblRef)) - continue; - - /* - * AQUMV - * Require that the relation of mvQuery is a simple query too. - * We haven't do sth like: pull up sublinks or subqueries yet. - */ - int varno = ((RangeTblRef *) mvjtnode)->rtindex; - RangeTblEntry *mvrte = rt_fetch(varno, mvQuery->rtable); - Assert(mvrte != NULL); - - if (mvrte->rtekind != RTE_RELATION) - continue; - - /* - * AQUMV_FIXME_MVP - * Must be same relation, recursiviely embeded mv is not supported now. - */ - if (mvrte->relid != rte->relid) - continue; - - /* - * AQUMV_FIXME_MVP - * mv's query tree itself is needed to do the final replacement - * when we found corresponding column expression from mvQuery's - * TargetList by Query's. - * - * A plain SELECT on all columns seems the easiest way, though - * some columns may not be needed. - * And we get a mvRelQueryTree represents SELECT * FROM mv. - */ - char *mvname = quote_qualified_identifier(get_namespace_name(RelationGetNamespace(matviewRel)), - RelationGetRelationName(matviewRel)); - StringInfoData query_mv; - initStringInfo(&query_mv); - appendStringInfo(&query_mv, "SELECT * FROM %s", mvname); - List *raw_parsetree_list = pg_parse_query(query_mv.data); - - /* - * AQUMV_FIXME_MVP - * We should drop the mv if it has rules. - * Because mv's rules shouldn't apply to origin query. - */ - if (list_length(raw_parsetree_list) != 1) - continue; - - ParseState *mv_pstate = make_parsestate(NULL); - mv_pstate->p_sourcetext = query_mv.data; - mvRelQueryTree = transformTopLevelStmt(mv_pstate, linitial(raw_parsetree_list)); - free_parsestate(mv_pstate); - /* AQUMV_FIXME_MVP: free mvRelQueryTree? */ - - PlannerInfo *subroot; - subroot = (PlannerInfo *) palloc(sizeof(PlannerInfo)); - memcpy(subroot, root, sizeof(PlannerInfo)); - subroot->parent_root = root; - /* - * AQUMV_FIXME_MVP: - * TODO: keep ECs and adjust varno? - */ - subroot->eq_classes = NIL; - /* Reset subplan-related stuff */ - subroot->plan_params = NIL; - subroot->outer_params = NULL; - subroot->init_plans = NIL; - subroot->agginfos = NIL; - subroot->aggtransinfos = NIL; - subroot->parse = mvQuery; - - /* - * AQUMV - * We have to rewrite now before we do the real Equivalent - * Transformation 'rewrite'. - * Because actions sotored in rule is not a normal query tree, - * it can't be used directly, ex: new/old realtions used to - * refresh mv. - * Earse unused relatoins, keep the right one. - */ - foreach(lc, mvQuery->rtable) - { - RangeTblEntry* rtetmp = lfirst(lc); - if ((rtetmp->relkind == RELKIND_MATVIEW) && - (rtetmp->alias != NULL) && - (strcmp(rtetmp->alias->aliasname, "new") == 0 || - strcmp(rtetmp->alias->aliasname,"old") == 0)) - { - foreach_delete_current(mvQuery->rtable, lc); - } - } - - /* - * Now we have the right relation, adjust - * varnos in its query tree. - * AQUMV_FIXME_MVP: Only one single relation - * is supported now, we could assign varno - * to 1 opportunistically. - */ - aqumv_adjust_varno(mvQuery, 1); - - /* - * AQUMV_FIXME_MVP - * Are stable functions OK? - * A STABLE function cannot modify the database and is guaranteed to - * return the same results given the same arguments for all rows - * within a single statement. - * But AQUMV rewrites the query to a new SQL actually, though the same - * results is guaranteed. - * Its's unclear whether STABLE is OK, let's be conservative for now. - */ - if(contain_mutable_functions((Node *)mvQuery)) - continue; - - /* - * AQUMV - * Since tlist and quals rewrite are both based on mv query's tlist, - * put all stuff into a context. - */ - aqumv_equivalent_transformation_context *context = palloc0(sizeof(aqumv_equivalent_transformation_context)); - List *mv_pure_vars = NIL; /* TargetEntry[Var] in mv query. */ - List *mv_pure_vars_index = NIL; /* Index of TargetEntry[Var] in mv query. */ - List *mv_nonpure_vars_index = NIL; /* Index of nonpure[Var] expression in mv query. */ - ListCell *lc; - Expr *expr; - - /* - * AQUMV - * Process mv_query's tlist to pure-Var and no pure-Var expressions. - * See details in README.cbdb.aqumv - */ - int i = 0; - foreach(lc, mvQuery->targetList) - { - i++; - TargetEntry* tle = lfirst_node(TargetEntry, lc); - expr = tle->expr; - if(IsA(expr, Var)) - { - mv_pure_vars = lappend(mv_pure_vars, expr); - mv_pure_vars_index = lappend_int(mv_pure_vars_index, i); - } - else - mv_nonpure_vars_index = lappend_int(mv_nonpure_vars_index, i); - } - - context->mv_pure_vars = mv_pure_vars; - context->mv_pure_vars_index = mv_pure_vars_index; - context->mv_nonpure_vars_index = mv_nonpure_vars_index; - context->mv_tlist = mvRelQueryTree->targetList; - context->mv_query_tlist = mvQuery->targetList; - context->has_unmatched = false; - - /* Sort nonpure vars expression, prepare for Greedy Algorithm. */ - aqumv_process_nonpure_vars_expr(context); - - List *mv_final_tlist = NIL; /* Final target list we want to rewrite to. */ - - /* - * Process and rewrite target list, return false if failed. - */ - if(!aqumv_process_targetlist(context, parse->targetList, &mv_final_tlist)) - continue; - - /* - * AQUMV - * Process all quals to conjunctive normal form. - * - * We assume that the selection predicates of view and query expressions - * have been converted into conjunctive normal form(CNF) before we process - * them. - * AQUMV_MVP: no having quals now. - */ - preprocess_qual_conditions(subroot, (Node *) mvQuery->jointree); - - List *post_quals = NIL; - /* - * Process quals, return false if failed. - * Else, post_quals are filled if there were. - * Like process target list, post_quals is used later to see if we could - * rewrite and apply it to mv relation. - */ - if(!aqumv_process_from_quals(parse->jointree->quals, mvQuery->jointree->quals, &post_quals)) - continue; - - /* Rewrite post_quals, return false if failed. */ - post_quals = (List *)aqumv_adjust_sub_matched_expr_mutator((Node *)post_quals, context); - if (context->has_unmatched) - continue; - - /* - * Here! We succeed to rewrite a new SQL. - * Begin to replace all guts. - */ - mvQuery->targetList = mv_final_tlist; - - /* - * AQUMV - * NB: Update processed_tlist again in case that tlist has been changed. - */ - preprocess_targetlist(subroot); - - /* - * AQUMV - * NB: Correct the final_locus as we select from another realtion now. - */ - PathTarget *newtarget = make_pathtarget_from_tlist(subroot->processed_tlist); - subroot->final_locus = cdbllize_get_final_locus(subroot, newtarget); - - /* Rewrite with mv's query tree*/ - mvrte->relkind = RELKIND_MATVIEW; - mvrte->relid = matviewRel->rd_rel->oid; - /* - * AQUMV_FIXME_MVP - * Not sure where it's true from actions even it's not inherit tables. - */ - mvrte->inh = false; - mvQuery->rtable = list_make1(mvrte); /* rewrite to SELECT FROM mv itself. */ - mvQuery->jointree->quals = (Node *)post_quals; /* Could be NULL, but doesn'y matter for now. */ - - /* - * AQUMV - * Build a plan of new SQL. - * AQUMV is cost-based, let planner decide which is better. - * AQUMV_FIXME_MVP: - * no qp_callback function now. - * replcace one-by-one? - */ - mv_final_rel = query_planner(subroot, NULL, NULL); - - /* AQUMV_FIXME_MVP - * We don't use STD_FUZZ_FACTOR for cost comparisons like compare_path_costs_fuzzily here. - * The STD_FUZZ_FACTOR is used to reduce paths of a rel, and keep the significantly ones. - * But in AQUMV, we always have only one best path of rel at the last to compare. - * TODO: limit clause and startup_cost. - */ - if (mv_final_rel->cheapest_total_path->total_cost < current_rel->cheapest_total_path->total_cost) - { - root->parse = mvQuery; - root->processed_tlist = subroot->processed_tlist; - /* - * AQUMV_FIXME_MVP - * Use new query's ecs. - * Equivalence Class is not supported now, we may lost some ECs if the mv_query has - * equal quals or implicit ones. - * But keeping them also introduces more complex as we should process them like target list. - * Another flaw: the generated Filter expressions by keeping them are pointless as all - * rows of mv have matched the filter expressions. - * See more in README.cbdb.aqumv - */ - root->eq_classes = subroot->eq_classes; - /* Replace relation, don't close the right one. */ - current_rel = mv_final_rel; - table_close(matviewRel, AccessShareLock); - need_close = false; - } - } - if (need_close) - table_close(matviewRel, AccessShareLock); - systable_endscan(rcscan); - table_close(ruleDesc, AccessShareLock); - } - } - } + enable_answer_query_using_materialized_views) + current_rel = answer_query_using_materialized_views(root, current_rel); /* * Convert the query's result tlist into PathTarget format. @@ -9370,331 +8963,3 @@ make_new_rollups_for_hash_grouping_set(PlannerInfo *root, return srd; } - - -/* - * Process varno after we eliminate mv's actions("old" and "new" relation) - * Correct rindex and all varnos with a delta. - * - * MV's actions query tree: - * [rtable] - * RangeTblEntry [rtekind=RTE_RELATION] - * [alias] Alias [aliasname="old"] - * RangeTblEntry [rtekind=RTE_RELATION] - * [alias] Alias [aliasname="new"] - * RangeTblEntry [rtekind=RTE_RELATION] - * [jointree] - * FromExpr [] - * [fromlist] - * RangeTblRef [rtindex=3] - * [targetList] - * TargetEntry [resno=1 resname="c1"] - * Var [varno=3 varattno=1] - * TargetEntry [resno=2 resname="c2"] - * Var [varno=3 varattno=2] - *------------------------------------------------------------------------------------------ - * MV's query tree after rewrite: - * [rtable] - * RangeTblEntry [rtekind=RTE_RELATION] - * [jointree] - * FromExpr [] - * [fromlist] - * RangeTblRef [rtindex=3] - * [targetList] - * TargetEntry [resno=1 resname="c1"] - * Var [varno=3 varattno=1] - * TargetEntry [resno=2 resname="c2"] - * Var [varno=3 varattno=2] - *------------------------------------------------------------------------------------------ - * MV's query tree after varno adjust: - * [rtable] - * RangeTblEntry [rtekind=RTE_RELATION] - * [jointree] - * FromExpr [] - * [fromlist] - * RangeTblRef [rtindex=1] - * [targetList] - * TargetEntry [resno=1 resname="c1"] - * Var [varno=1 varattno=1] - * TargetEntry [resno=2 resname="c2"] - * Var [varno=1 varattno=2] - * - */ -static void -aqumv_adjust_varno(Query* parse, int varno) -{ - aqumv_adjust_varno_context context; - context.varno = varno; - parse = query_tree_mutator(parse, aqumv_adjust_varno_mutator, &context, QTW_DONT_COPY_QUERY); -} - -/* - * Only for plain select * from mv; - * All TargetEntrys are pure Var. - * var_index start from 1 - */ -static inline Var * -copyVarFromTatgetList(List* tlist, int var_index) -{ - TargetEntry * tle = (TargetEntry *) list_nth(tlist, var_index - 1); - Assert(IsA(tle->expr,Var)); - Var *var = copyObject((Var *)tle->expr); - return var; -} - -/* - * Adjust varno and rindex with delta. - */ -static Node *aqumv_adjust_varno_mutator(Node *node, aqumv_adjust_varno_context *context) -{ - if (node == NULL) - return NULL; - if (IsA(node, Var)) - ((Var *)node)->varno = context->varno; - else if (IsA(node, RangeTblRef)) - /* AQUMV_FIXME_MVP: currently we have only one relation */ - ((RangeTblRef*) node)->rtindex = context->varno; - return expression_tree_mutator(node, aqumv_adjust_varno_mutator, context); -} - -typedef struct -{ - int complexity; -} node_complexity_context; - -typedef struct -{ - int tlist_index; /* Index of tlist, begin from 1 */ - int count; /* Count of subnodes in this expression */ -} expr_to_sort; - -/* - * Compute a node complexity recursively. - * Complexity of a node is the total times we enter walker function after all - * subnodes are walked recursively. - * It's used to sort the expression in mv's tlist. - */ -static bool -compute_node_complexity_walker(Node *node, node_complexity_context *context) -{ - if (node == NULL) - return false; - context->complexity++; - return expression_tree_walker(node, compute_node_complexity_walker, (void *) context); -} - -static int -nonpure_vars_expr_compare(const ListCell *a, const ListCell *b) -{ - expr_to_sort *ets1 = (expr_to_sort *) lfirst(a); - expr_to_sort *ets2 = (expr_to_sort *) lfirst(b); - return (ets1->count < ets2->count) ? 1 : (ets1->count == ets2->count) ? 0 : -1; -} - -/* - * In-place update order of mv_nonpure_vars_index List - */ -static void -aqumv_process_nonpure_vars_expr(aqumv_equivalent_transformation_context* context) -{ - ListCell* lc; - List *expr_to_sort_list = NIL; - foreach(lc, context->mv_nonpure_vars_index) - { - int index = lfirst_int(lc); - Node *expr = lfirst(list_nth_cell(context->mv_query_tlist, index -1)); - node_complexity_context *subnode_context = palloc0(sizeof(node_complexity_context)); - (void) compute_node_complexity_walker(expr, subnode_context); - expr_to_sort *ets = palloc0(sizeof(expr_to_sort)); - ets->tlist_index = index; - ets->count = subnode_context->complexity; - expr_to_sort_list = lappend(expr_to_sort_list, ets); - } - - /* Sort the expr list */ - list_sort(expr_to_sort_list, nonpure_vars_expr_compare); - /* Reorder mv_nonpure_vars_index */ - context->mv_nonpure_vars_index = NIL; - foreach(lc, expr_to_sort_list) - { - expr_to_sort *ets = (expr_to_sort *) lfirst(lc); - context->mv_nonpure_vars_index = lappend_int(context->mv_nonpure_vars_index, ets->tlist_index); - } - return; -} - - -/* - * Process query and materialized views' quals. - * Return true if all mv_quals are in query_quals, - * else return false. - * - * If return true, put quals in query_quals but not in mv_quals - * into post_quals. - * - * Ex: create materialized view mv0 as select * from t1 where c1 = 1; - * query: select * from t1 where c1 = 1 and c2 = 2; - * post_quals = {c2 = 2}. - * - * AQUMV_FIXME_MVP: only support one relation now, so we don't need to - * compare varno(both are 1 after aqumv_adjust_varno), - * mv's query tree has been processed into one relation too. - * - * Will return false if varattno in mv->query has different order with query's. - * Ex: create materialized view mv0 as select c2, c1 from t1 where c1 = 1; - * query: select c1, c2 from t1 where c1 = 1 and c2 = 2; - * - * The returned post_quals may be or may not be used later, it's up to mv's targetList. - * - */ -static bool -aqumv_process_from_quals(Node *query_quals, Node *mv_quals, List **post_quals) -{ - List *qlist = NIL; - List *mlist = NIL; - - if (query_quals == NULL) - return mv_quals == NULL; - - if(!IsA(query_quals, List)) - qlist = list_make1(query_quals); - else - qlist = (List *)query_quals; - - if (mv_quals == NULL) - { - *post_quals = qlist; - return true; - } - - if(!IsA(mv_quals, List)) - mlist = list_make1(mv_quals); - else - mlist = (List *)mv_quals; - - if (list_difference(mlist, qlist) != NIL) - return false; - *post_quals = list_difference(qlist, mlist); - return true; -} - -/* - * Adjust query expr's Vars - * Replace Vars with corresponding attribute in mv relation. - * Return a new expr after rewrite. - */ -static Node *aqumv_adjust_sub_matched_expr_mutator(Node *node, aqumv_equivalent_transformation_context *context) -{ - if (!node || context->has_unmatched) - return node; - - bool is_targetEntry = IsA(node, TargetEntry); - Expr *node_expr = is_targetEntry ? ((TargetEntry *)node)->expr : (Expr *)node; - - /* Don't select Const results form mv, bypass it to upper when projection. */ - if (IsA(node_expr, Const)) - return is_targetEntry ? node : (Node *)node_expr; - - ListCell *lc = NULL; - foreach(lc, context->mv_nonpure_vars_index) - { - int index = lfirst_int(lc); - TargetEntry *tle = list_nth_node(TargetEntry, context->mv_query_tlist, index - 1); - if(equal(node_expr, tle->expr)) - { - Var *newVar = copyVarFromTatgetList(context->mv_tlist, index); - newVar->location = -2; /* hack here, use -2 to indicate already rewrited by mv rel Vars. */ - if (is_targetEntry) - { - TargetEntry *qtle = (TargetEntry *) node; - /* - * AQUMV_FIXME_MVP: - * resorigtbl, resorigcol, resjunck in mv_query is also rejunck in mv table itself ? - */ - TargetEntry *mvtle = makeTargetEntry((Expr *)newVar, qtle->resno, qtle->resname, qtle->resjunk); - return (Node *) mvtle; - } - else - return (Node *) newVar; - } - } - - /* - * We didn't find matched nonpure-Var expr. - * And if expr doesn't have Vars, return it to upper. - */ - List *expr_vars = pull_var_clause((Node *)node_expr, - PVC_RECURSE_AGGREGATES | - PVC_RECURSE_WINDOWFUNCS | - PVC_INCLUDE_PLACEHOLDERS); - - if (expr_vars == NIL) - return (Node *)node_expr; - list_free(expr_vars); - - /* Try match with mv_pure_vars_index, but do not disturb already rewrited exprs(Var->location = -2) */ - if (IsA(node_expr, Var)) - { - Var *var = (Var *)node_expr; - if (var->location == -2) - return node; - lc = NULL; - int i = 0; - foreach(lc, context->mv_pure_vars) - { - Var *pure_var = lfirst_node(Var,lc); - if (equal(node_expr, pure_var)) - { - int j = list_nth_int(context->mv_pure_vars_index, i); - Var *newvar = copyVarFromTatgetList(context->mv_tlist, j); - if (is_targetEntry) - { - ((TargetEntry *)node)->expr = (Expr *)newvar; - return node; - } - else - return (Node *)newvar; - } - i++; - } - context->has_unmatched = true; - } - - return expression_tree_mutator(node, aqumv_adjust_sub_matched_expr_mutator, context); -} - -/* - * Process query and materialized views' target list. - * Return true if all query_tlist are in mv_tlist. - * else return false. - * - * If return true, put tlist in mv_quals but not in query_tlist - * into post_tlist. - * - * Ex: create materialized view mv0 as select c1, c2 from t1 where c1 = 1; - * query: select c2 from t1 where c1 = 1; - * post_tlist= {1}. - * - * AQUMV_FIXME_MVP: strict match with same resno? - * MVP0: expression replace - * mv: select c1, c2 from t1 where c1 = 50; - * select c1 from t1 where c1 = 50 and abs(t1.c2) = 51; - * rewrite: select c1 from mv where abs(mv.c2) = 51; - * - * MVP1: expression eliminate - * mv: select c1, abs(c2) as c2 from t1 where c1 = 50; - * select c1 from t1 where c1 = 50 and abs(c2) = 51; - * rewrite: select c1 from mv where c2 = 51; - * - * mv_final_tlist is the final targetList of mvQuery. - * - */ -static bool -aqumv_process_targetlist(aqumv_equivalent_transformation_context *context, List *query_tlist, List **mv_final_tlist) -{ - *mv_final_tlist = (List *)aqumv_adjust_sub_matched_expr_mutator((Node *)(copyObject(query_tlist)), context); - if (context->has_unmatched) - pfree(*mv_final_tlist); - - return !context->has_unmatched; -} diff --git a/src/backend/utils/misc/guc_gp.c b/src/backend/utils/misc/guc_gp.c index 3ccd6ede60b..786d52185b0 100644 --- a/src/backend/utils/misc/guc_gp.c +++ b/src/backend/utils/misc/guc_gp.c @@ -430,7 +430,7 @@ bool gp_enable_global_deadlock_detector = false; bool gp_enable_predicate_pushdown; int gp_predicate_pushdown_sample_rows; -bool answer_query_using_materialized_views = false; +bool enable_answer_query_using_materialized_views = false; static const struct config_enum_entry gp_log_format_options[] = { {"text", 0}, @@ -2985,12 +2985,12 @@ struct config_bool ConfigureNamesBool_gp[] = NULL, NULL, NULL }, { - {"answer_query_using_materialized_views", PGC_USERSET, QUERY_TUNING_METHOD, + {"enable_answer_query_using_materialized_views", PGC_USERSET, QUERY_TUNING_METHOD, gettext_noop("allow to answer query results using materialized views."), NULL, GUC_EXPLAIN }, - &answer_query_using_materialized_views, + &enable_answer_query_using_materialized_views, false, NULL, NULL, NULL }, diff --git a/src/include/optimizer/planner.h b/src/include/optimizer/planner.h index 8f00a6015f0..3d58720dfbe 100644 --- a/src/include/optimizer/planner.h +++ b/src/include/optimizer/planner.h @@ -62,4 +62,8 @@ extern Expr *preprocess_phv_expression(PlannerInfo *root, Expr *expr); extern bool optimizer_init; +extern RelOptInfo* answer_query_using_materialized_views(PlannerInfo *root, RelOptInfo *current_rel); + +extern void preprocess_qual_conditions(PlannerInfo *root, Node *jtnode); + #endif /* PLANNER_H */ diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h index ee60d339ff8..a24949b1b6f 100644 --- a/src/include/utils/guc.h +++ b/src/include/utils/guc.h @@ -300,7 +300,7 @@ extern bool gp_appendonly_compaction; extern bool enable_parallel; extern int gp_appendonly_insert_files; extern int gp_appendonly_insert_files_tuples_range; -extern bool answer_query_using_materialized_views; +extern bool enable_answer_query_using_materialized_views; /* * gp_enable_multiphase_limit is not cost based. * When set to false, the planner will not use multi-phase limit. diff --git a/src/include/utils/unsync_guc_name.h b/src/include/utils/unsync_guc_name.h index 9d1741139a8..04256739496 100644 --- a/src/include/utils/unsync_guc_name.h +++ b/src/include/utils/unsync_guc_name.h @@ -10,7 +10,7 @@ */ "allow_segment_DML", "allow_system_table_mods", - "answer_query_using_materialized_views", + "enable_answer_query_using_materialized_views", "application_name", "archive_cleanup_command", "archive_command", diff --git a/src/test/regress/expected/aqumv.out b/src/test/regress/expected/aqumv.out index fdfe8286d56..b0d0c56e917 100644 --- a/src/test/regress/expected/aqumv.out +++ b/src/test/regress/expected/aqumv.out @@ -6,11 +6,11 @@ create table aqumv_t1(c1 int, c2 int, c3 int) distributed by (c1); insert into aqumv_t1 select i, i+1, i+2 from generate_series(1, 1000) i; insert into aqumv_t1 select * from aqumv_t1; analyze aqumv_t1; -set answer_query_using_materialized_views = on; +set enable_answer_query_using_materialized_views = on; -- drop views if there is no data populated begin; create incremental materialized view aqumv_mvt1_need_refresh as select * from aqumv_t1 where c1 = 2 with no data; -set local answer_query_using_materialized_views = on; +set local enable_answer_query_using_materialized_views = on; explain(verbose, costs off) select * from aqumv_t1 where c1 = 2; QUERY PLAN --------------------------------------------------------------------------- @@ -19,7 +19,7 @@ explain(verbose, costs off) select * from aqumv_t1 where c1 = 2; -> Seq Scan on public.aqumv_t1 Output: c1, c2, c3 Filter: (aqumv_t1.c1 = 2) - Settings: answer_query_using_materialized_views = 'on', optimizer = 'off' + Settings: enable_answer_query_using_materialized_views = 'on', optimizer = 'off' Optimizer: Postgres query optimizer (7 rows) @@ -32,7 +32,7 @@ explain(verbose, costs off) select * from aqumv_t1 where c1 = 2; Output: c1, c2, c3 -> Seq Scan on public.aqumv_mvt1_need_refresh Output: c1, c2, c3 - Settings: answer_query_using_materialized_views = 'on', optimizer = 'off' + Settings: enable_answer_query_using_materialized_views = 'on', optimizer = 'off' Optimizer: Postgres query optimizer (6 rows) @@ -40,7 +40,7 @@ abort; begin; create incremental materialized view aqumv_mvt1_0 as select * from aqumv_t1 where c1 = 2; analyze aqumv_mvt1_0; -set local answer_query_using_materialized_views = off; +set local enable_answer_query_using_materialized_views = off; select * from aqumv_t1 where c1 = 2; c1 | c2 | c3 ----+----+---- @@ -69,7 +69,7 @@ select c3, c2 from aqumv_t1 where c1 = 2; 4 | 3 (2 rows) -set local answer_query_using_materialized_views = on; +set local enable_answer_query_using_materialized_views = on; select * from aqumv_t1 where c1 = 2; c1 | c2 | c3 ----+----+---- @@ -106,7 +106,7 @@ explain(verbose, costs off) select * from aqumv_t1 where c1 = 2; Output: c1, c2, c3 -> Seq Scan on public.aqumv_mvt1_0 Output: c1, c2, c3 - Settings: answer_query_using_materialized_views = 'on', optimizer = 'off' + Settings: enable_answer_query_using_materialized_views = 'on', optimizer = 'off' Optimizer: Postgres query optimizer (6 rows) @@ -117,7 +117,7 @@ explain(verbose, costs off) select c1, c2, c3 from aqumv_t1 where c1 = 2; Output: c1, c2, c3 -> Seq Scan on public.aqumv_mvt1_0 Output: c1, c2, c3 - Settings: answer_query_using_materialized_views = 'on', optimizer = 'off' + Settings: enable_answer_query_using_materialized_views = 'on', optimizer = 'off' Optimizer: Postgres query optimizer (6 rows) @@ -129,7 +129,7 @@ explain(verbose, costs off) select c2 from aqumv_t1 where c1 = 2; Output: c2 -> Seq Scan on public.aqumv_mvt1_0 Output: c2 - Settings: answer_query_using_materialized_views = 'on', optimizer = 'off' + Settings: enable_answer_query_using_materialized_views = 'on', optimizer = 'off' Optimizer: Postgres query optimizer (6 rows) @@ -141,7 +141,7 @@ explain(verbose, costs off) select c3, c2 from aqumv_t1 where c1 = 2; Output: c3, c2 -> Seq Scan on public.aqumv_mvt1_0 Output: c3, c2 - Settings: answer_query_using_materialized_views = 'on', optimizer = 'off' + Settings: enable_answer_query_using_materialized_views = 'on', optimizer = 'off' Optimizer: Postgres query optimizer (6 rows) @@ -149,7 +149,7 @@ abort; begin; create incremental materialized view aqumv_mvt1_1 as select c2 as mc2, c3 as mc3, c1 as mc1, c2 as mc2_1 from aqumv_t1 where c1 = 3; analyze aqumv_mvt1_1; -set local answer_query_using_materialized_views = off; +set local enable_answer_query_using_materialized_views = off; select c1 as col1, c2 as col2 from aqumv_t1 where c1 = 3; col1 | col2 ------+------ @@ -164,7 +164,7 @@ select c1, c1 from aqumv_t1 where c1 = 3; 3 | 3 (2 rows) -set local answer_query_using_materialized_views = on; +set local enable_answer_query_using_materialized_views = on; select c1 as col1, c2 as col2 from aqumv_t1 where c1 = 3; col1 | col2 ------+------ @@ -187,7 +187,7 @@ explain(verbose, costs off) select c1 as col1, c2 as col2 from aqumv_t1 where c1 Output: mc1, mc2 -> Seq Scan on public.aqumv_mvt1_1 Output: mc1, mc2 - Settings: answer_query_using_materialized_views = 'on', optimizer = 'off' + Settings: enable_answer_query_using_materialized_views = 'on', optimizer = 'off' Optimizer: Postgres query optimizer (6 rows) @@ -199,7 +199,7 @@ explain(verbose, costs off) select c1, c1 from aqumv_t1 where c1 = 3; Output: mc1, mc1 -> Seq Scan on public.aqumv_mvt1_1 Output: mc1, mc1 - Settings: answer_query_using_materialized_views = 'on', optimizer = 'off' + Settings: enable_answer_query_using_materialized_views = 'on', optimizer = 'off' Optimizer: Postgres query optimizer (6 rows) @@ -209,7 +209,7 @@ create incremental materialized view aqumv_mvt1_nonvar_expr as select c2, 1 as mc_const_1, sqrt(100) as mc_sqrt_100 from aqumv_t1 where c1 = 4; analyze aqumv_mvt1_nonvar_expr; -set local answer_query_using_materialized_views = off; +set local enable_answer_query_using_materialized_views = off; select c2, 200 from aqumv_t1 where c1 = 4; c2 | ?column? ----+---------- @@ -224,7 +224,7 @@ select c2, 1, sqrt(100) from aqumv_t1 where c1 = 4; 5 | 1 | 10 (2 rows) -set local answer_query_using_materialized_views = on; +set local enable_answer_query_using_materialized_views = on; select c2, 200 from aqumv_t1 where c1 = 4; c2 | ?column? ----+---------- @@ -247,7 +247,7 @@ explain(verbose, costs off) select c2, 200 from aqumv_t1 where c1 = 4; Output: c2, 200 -> Seq Scan on public.aqumv_mvt1_nonvar_expr Output: c2, 200 - Settings: answer_query_using_materialized_views = 'on', optimizer = 'off' + Settings: enable_answer_query_using_materialized_views = 'on', optimizer = 'off' Optimizer: Postgres query optimizer (6 rows) @@ -258,7 +258,7 @@ explain(verbose, costs off) select c2, 1, sqrt(100) from aqumv_t1 where c1 = 4; Output: c2, 1, '10'::double precision -> Seq Scan on public.aqumv_mvt1_nonvar_expr Output: c2, 1, '10'::double precision - Settings: answer_query_using_materialized_views = 'on', optimizer = 'off' + Settings: enable_answer_query_using_materialized_views = 'on', optimizer = 'off' Optimizer: Postgres query optimizer (6 rows) @@ -273,7 +273,7 @@ create incremental materialized view aqumv_mvt1_func_has_var as select c2, aqumv_func(c1, c3) as mc_func_res from aqumv_t1 where c1 = 5; analyze aqumv_mvt1_func_has_var; -set local answer_query_using_materialized_views = off; +set local enable_answer_query_using_materialized_views = off; select c2, aqumv_func(c1, c3) from aqumv_t1 where c1 = 5; c2 | aqumv_func ----+------------ @@ -281,7 +281,7 @@ select c2, aqumv_func(c1, c3) from aqumv_t1 where c1 = 5; 6 | 12 (2 rows) -set local answer_query_using_materialized_views = on; +set local enable_answer_query_using_materialized_views = on; select c2, aqumv_func(c1, c3) from aqumv_t1 where c1 = 5; c2 | aqumv_func ----+------------ @@ -297,7 +297,7 @@ explain(verbose, costs off) select c2, aqumv_func(c1, c3), aqumv_func(c1, c3) fr Output: c2, mc_func_res, mc_func_res -> Seq Scan on public.aqumv_mvt1_func_has_var Output: c2, mc_func_res, mc_func_res - Settings: answer_query_using_materialized_views = 'on', optimizer = 'off' + Settings: enable_answer_query_using_materialized_views = 'on', optimizer = 'off' Optimizer: Postgres query optimizer (6 rows) @@ -305,7 +305,7 @@ abort; begin; create incremental materialized view aqumv_mvt1_2 as select c2 as mc2, c1 as mc1 from aqumv_t1 where c1 > 1 and c1 < 5; analyze aqumv_mvt1_2; -set local answer_query_using_materialized_views = on; +set local enable_answer_query_using_materialized_views = on; -- shoud be unable to use mv, projection doesn't exit in mv's tlist explain(verbose, costs off) select c3 from aqumv_t1 where c1 < 5 and c1 > 1; QUERY PLAN @@ -315,7 +315,7 @@ explain(verbose, costs off) select c3 from aqumv_t1 where c1 < 5 and c1 > 1; -> Seq Scan on public.aqumv_t1 Output: c3 Filter: ((aqumv_t1.c1 < 5) AND (aqumv_t1.c1 > 1)) - Settings: answer_query_using_materialized_views = 'on', optimizer = 'off' + Settings: enable_answer_query_using_materialized_views = 'on', optimizer = 'off' Optimizer: Postgres query optimizer (7 rows) @@ -327,12 +327,12 @@ explain(verbose, costs off) select c1, c2 from aqumv_t1 where c1 < 5 and c1 > 1; Output: mc1, mc2 -> Seq Scan on public.aqumv_mvt1_2 Output: mc1, mc2 - Settings: answer_query_using_materialized_views = 'on', optimizer = 'off' + Settings: enable_answer_query_using_materialized_views = 'on', optimizer = 'off' Optimizer: Postgres query optimizer (6 rows) -- post quals added to mv. -set local answer_query_using_materialized_views = off; +set local enable_answer_query_using_materialized_views = off; select c1, c2 from aqumv_t1 where c1 < 5 and c1 > 1 and c2 = 4; c1 | c2 ----+---- @@ -340,7 +340,7 @@ select c1, c2 from aqumv_t1 where c1 < 5 and c1 > 1 and c2 = 4; 3 | 4 (2 rows) -set local answer_query_using_materialized_views = on; +set local enable_answer_query_using_materialized_views = on; select c1, c2 from aqumv_t1 where c1 < 5 and c1 > 1 and c2 = 4; c1 | c2 ----+---- @@ -356,7 +356,7 @@ explain(verbose, costs off) select c1, c2 from aqumv_t1 where c1 < 5 and c1 > 1 -> Seq Scan on public.aqumv_mvt1_2 Output: mc1, mc2 Filter: (aqumv_mvt1_2.mc2 = 4) - Settings: answer_query_using_materialized_views = 'on', optimizer = 'off' + Settings: enable_answer_query_using_materialized_views = 'on', optimizer = 'off' Optimizer: Postgres query optimizer (7 rows) @@ -369,7 +369,7 @@ explain(verbose, costs off) select * from aqumv_t1 where c1 < 5 and c1 > 1 and c -> Seq Scan on public.aqumv_t1 Output: c1, c2, c3 Filter: ((aqumv_t1.c1 < 5) AND (aqumv_t1.c1 > 1) AND (aqumv_t1.c3 > 1)) - Settings: answer_query_using_materialized_views = 'on', optimizer = 'off' + Settings: enable_answer_query_using_materialized_views = 'on', optimizer = 'off' Optimizer: Postgres query optimizer (7 rows) @@ -377,7 +377,7 @@ abort; begin; create incremental materialized view aqumv_mvt1_3 as select c2 as mc2, c1 as mc1, c3+1 as mc3 from aqumv_t1 where c1 > 5 and c1 < 10; analyze aqumv_mvt1_3; -set local answer_query_using_materialized_views = on; +set local enable_answer_query_using_materialized_views = on; -- should be unable to use mv, column c3 doesn't exist in mv's tlist. explain(verbose, costs off) select * from aqumv_t1 where c1 > 5 and c1 < 10; QUERY PLAN @@ -387,7 +387,7 @@ explain(verbose, costs off) select * from aqumv_t1 where c1 > 5 and c1 < 10; -> Seq Scan on public.aqumv_t1 Output: c1, c2, c3 Filter: ((aqumv_t1.c1 > 5) AND (aqumv_t1.c1 < 10)) - Settings: answer_query_using_materialized_views = 'on', optimizer = 'off' + Settings: enable_answer_query_using_materialized_views = 'on', optimizer = 'off' Optimizer: Postgres query optimizer (7 rows) @@ -399,7 +399,7 @@ explain(verbose, costs off) select c1 as col1, c3+1 as col2 from aqumv_t1 where Output: mc1, mc3 -> Seq Scan on public.aqumv_mvt1_3 Output: mc1, mc3 - Settings: answer_query_using_materialized_views = 'on', optimizer = 'off' + Settings: enable_answer_query_using_materialized_views = 'on', optimizer = 'off' Optimizer: Postgres query optimizer (6 rows) @@ -411,7 +411,7 @@ explain(verbose, costs off) select c1+1 as col1, c2, c3+1 as col2 from aqumv_t1 Output: ((mc1 + 1)), mc2, mc3 -> Seq Scan on public.aqumv_mvt1_3 Output: (mc1 + 1), mc2, mc3 - Settings: answer_query_using_materialized_views = 'on', optimizer = 'off' + Settings: enable_answer_query_using_materialized_views = 'on', optimizer = 'off' Optimizer: Postgres query optimizer (6 rows) @@ -441,7 +441,7 @@ select c1+1 as col1, c2, c3+1 as col2 from aqumv_t1 where c1 > 5 and c1 < 10; 9 | 9 | 11 (8 rows) -set local answer_query_using_materialized_views = off; +set local enable_answer_query_using_materialized_views = off; select c1 as col1, c3+1 as col2 from aqumv_t1 where c1 > 5 and c1 < 10; col1 | col2 ------+------ @@ -474,7 +474,7 @@ create incremental materialized view aqumv_mvt1_4 as select c1 as mc1, c2 as mc2, abs(c2) as mc3, abs(abs(c2) - c1 - 1) as mc4 from aqumv_t1 where c1 > 10 and c1 < 15; analyze aqumv_mvt1_4; -set local answer_query_using_materialized_views = on; +set local enable_answer_query_using_materialized_views = on; -- complex exprs explain(verbose, costs off) select c1, sqrt(abs(abs(c2) - c1 - 1) + abs(c2)) + 1, abs(c2) + 1 from aqumv_t1 where c1 > 10 and c1 < 15; QUERY PLAN @@ -483,7 +483,7 @@ explain(verbose, costs off) select c1, sqrt(abs(abs(c2) - c1 - 1) + abs(c2)) + 1 Output: mc1, ((sqrt(((mc4 + mc3))::double precision) + '1'::double precision)), ((mc3 + 1)) -> Seq Scan on public.aqumv_mvt1_4 Output: mc1, (sqrt(((mc4 + mc3))::double precision) + '1'::double precision), (mc3 + 1) - Settings: answer_query_using_materialized_views = 'on', optimizer = 'off' + Settings: enable_answer_query_using_materialized_views = 'on', optimizer = 'off' Optimizer: Postgres query optimizer (6 rows) @@ -500,7 +500,7 @@ select c1, sqrt(abs(abs(c2) - c1 - 1) + abs(c2)) + 1, abs(c2) + 1 from aqumv_t1 14 | 4.872983346207417 | 16 (8 rows) -set local answer_query_using_materialized_views = off; +set local enable_answer_query_using_materialized_views = off; select c1, sqrt(abs(abs(c2) - c1 - 1) + abs(c2)) + 1, abs(c2) + 1 from aqumv_t1 where c1 > 10 and c1 < 15; c1 | ?column? | ?column? ----+-------------------+---------- @@ -521,7 +521,7 @@ create incremental materialized view aqumv_mvt1_post_quals as select c1 as mc1, c2 as mc2, abs(c2) as mc3, abs(abs(c2) - c1 - 1) as mc4 from aqumv_t1 where c1 > 20 and c1 < 30; analyze aqumv_mvt1_post_quals; -set local answer_query_using_materialized_views = on; +set local enable_answer_query_using_materialized_views = on; explain(verbose, costs off) select c1, sqrt(abs(abs(c2) - c1 - 1) + abs(c2)) + 1, abs(c2) + 1 from aqumv_t1 where c1 > 20 and c1 < 30 and sqrt(abs(c2) + 1) > 1; @@ -532,7 +532,7 @@ explain(verbose, costs off) -> Seq Scan on public.aqumv_mvt1_post_quals Output: mc1, (sqrt(((mc4 + mc3))::double precision) + '1'::double precision), (mc3 + 1) Filter: (sqrt(((aqumv_mvt1_post_quals.mc3 + 1))::double precision) > '1'::double precision) - Settings: answer_query_using_materialized_views = 'on', optimizer = 'off' + Settings: enable_answer_query_using_materialized_views = 'on', optimizer = 'off' Optimizer: Postgres query optimizer (7 rows) @@ -546,7 +546,7 @@ explain(verbose, costs off) -> Seq Scan on public.aqumv_mvt1_post_quals Output: mc1, (sqrt(((mc4 + mc3))::double precision) + '1'::double precision), (mc3 + 1) Filter: (sqrt(((aqumv_mvt1_post_quals.mc3 + 1))::double precision) > ((abs(aqumv_mvt1_post_quals.mc1) + 2))::double precision) - Settings: answer_query_using_materialized_views = 'on', optimizer = 'off' + Settings: enable_answer_query_using_materialized_views = 'on', optimizer = 'off' Optimizer: Postgres query optimizer (7 rows) @@ -560,7 +560,7 @@ select c1, sqrt(abs(abs(c2) - c1 - 1) + abs(c2)) + 1, abs(c2) + 1 -> Seq Scan on public.aqumv_mvt1_post_quals Output: mc1, (sqrt(((mc4 + mc3))::double precision) + '1'::double precision), (mc3 + 1) Filter: (sqrt(((aqumv_mvt1_post_quals.mc4 + 10))::double precision) > '2'::double precision) - Settings: answer_query_using_materialized_views = 'on', optimizer = 'off' + Settings: enable_answer_query_using_materialized_views = 'on', optimizer = 'off' Optimizer: Postgres query optimizer (7 rows) @@ -618,7 +618,7 @@ select c1, sqrt(abs(abs(c2) - c1 - 1) + abs(c2)) + 1, abs(c2) + 1 28 | 6.385164807134504 | 30 (18 rows) -set local answer_query_using_materialized_views = off; +set local enable_answer_query_using_materialized_views = off; select c1, sqrt(abs(abs(c2) - c1 - 1) + abs(c2)) + 1, abs(c2) + 1 from aqumv_t1 where c1 > 20 and c1 < 30 and sqrt(abs(c2) + 1) > 1; c1 | ?column? | ?column? @@ -676,7 +676,7 @@ select c1, sqrt(abs(abs(c2) - c1 - 1) + abs(c2)) + 1, abs(c2) + 1 abort; -- choose the best one if there are multiple chooses based on cost. begin; -set local answer_query_using_materialized_views = on; +set local enable_answer_query_using_materialized_views = on; create incremental materialized view aqumv_mvt1_candidate_0 as select c1 as mc1, c2 as mc2, abs(c2) as mc3 from aqumv_t1 where c1 > 30; @@ -691,7 +691,7 @@ select sqrt(abs(abs(c2) - c1 - 1) + abs(c2)) from aqumv_t1 where c1 > 30 and c1 -> Seq Scan on public.aqumv_mvt1_candidate_0 Output: sqrt(((abs(((mc3 - mc1) - 1)) + mc3))::double precision) Filter: ((aqumv_mvt1_candidate_0.mc1 < 40) AND (sqrt((aqumv_mvt1_candidate_0.mc3)::double precision) > '5.8'::double precision)) - Settings: answer_query_using_materialized_views = 'on', optimizer = 'off' + Settings: enable_answer_query_using_materialized_views = 'on', optimizer = 'off' Optimizer: Postgres query optimizer (7 rows) @@ -709,7 +709,7 @@ select sqrt(abs(abs(c2) - c1 - 1) + abs(c2)) from aqumv_t1 where c1 > 30 and c1 -> Seq Scan on public.aqumv_mvt1_candidate_1 Output: sqrt(((mc4 + mc3))::double precision) Filter: (sqrt((aqumv_mvt1_candidate_1.mc3)::double precision) > '5.8'::double precision) - Settings: answer_query_using_materialized_views = 'on', optimizer = 'off' + Settings: enable_answer_query_using_materialized_views = 'on', optimizer = 'off' Optimizer: Postgres query optimizer (7 rows) @@ -727,11 +727,11 @@ select sqrt(abs(abs(c2) - c1 - 1) + abs(c2)) from aqumv_t1 where c1 > 30 and c1 -> Seq Scan on public.aqumv_mvt1_candidate_1 Output: sqrt(((mc4 + mc3))::double precision) Filter: (sqrt((aqumv_mvt1_candidate_1.mc3)::double precision) > '5.8'::double precision) - Settings: answer_query_using_materialized_views = 'on', optimizer = 'off' + Settings: enable_answer_query_using_materialized_views = 'on', optimizer = 'off' Optimizer: Postgres query optimizer (7 rows) abort; reset optimizer; -reset answer_query_using_materialized_views; +reset enable_answer_query_using_materialized_views; drop table aqumv_t1 cascade; diff --git a/src/test/regress/expected/rangefuncs_cdb.out b/src/test/regress/expected/rangefuncs_cdb.out index 31956c8d8ca..8ea61363c1c 100644 --- a/src/test/regress/expected/rangefuncs_cdb.out +++ b/src/test/regress/expected/rangefuncs_cdb.out @@ -2,7 +2,7 @@ -- Will run in parallel mode with enable_parallel=on and non-parallel mode. -- Filter this gucs to pass regression. -- -SELECT name, setting FROM pg_settings WHERE name LIKE 'enable%' and name != 'enable_parallel'; +SELECT name, setting FROM pg_settings WHERE name LIKE 'enable%' and name not in ('enable_parallel', 'enable_answer_query_using_materialized_views'); name | setting --------------------------------+--------- enable_async_append | on diff --git a/src/test/regress/expected/sysviews.out b/src/test/regress/expected/sysviews.out index e1766efe7df..cd5998a128a 100644 --- a/src/test/regress/expected/sysviews.out +++ b/src/test/regress/expected/sysviews.out @@ -100,7 +100,7 @@ select count(*) = 0 as ok from pg_stat_wal_receiver; -- This is to record the prevailing planner enable_foo settings during -- a regression test run. -- GP parallel tests will run another with enable_parallel=on, filter this to pass regression. -select name, setting from pg_settings where name like 'enable%' and name != 'enable_parallel'; +select name, setting from pg_settings where name like 'enable%' and name not in ('enable_parallel', 'enable_answer_query_using_materialized_views'); name | setting --------------------------------+--------- enable_async_append | on diff --git a/src/test/regress/sql/aqumv.sql b/src/test/regress/sql/aqumv.sql index 962fbaf5aec..2da54655430 100644 --- a/src/test/regress/sql/aqumv.sql +++ b/src/test/regress/sql/aqumv.sql @@ -6,12 +6,12 @@ create table aqumv_t1(c1 int, c2 int, c3 int) distributed by (c1); insert into aqumv_t1 select i, i+1, i+2 from generate_series(1, 1000) i; insert into aqumv_t1 select * from aqumv_t1; analyze aqumv_t1; -set answer_query_using_materialized_views = on; +set enable_answer_query_using_materialized_views = on; -- drop views if there is no data populated begin; create incremental materialized view aqumv_mvt1_need_refresh as select * from aqumv_t1 where c1 = 2 with no data; -set local answer_query_using_materialized_views = on; +set local enable_answer_query_using_materialized_views = on; explain(verbose, costs off) select * from aqumv_t1 where c1 = 2; refresh materialized view aqumv_mvt1_need_refresh; analyze aqumv_mvt1_need_refresh; @@ -22,13 +22,13 @@ begin; create incremental materialized view aqumv_mvt1_0 as select * from aqumv_t1 where c1 = 2; analyze aqumv_mvt1_0; -set local answer_query_using_materialized_views = off; +set local enable_answer_query_using_materialized_views = off; select * from aqumv_t1 where c1 = 2; select c1, c2, c3 from aqumv_t1 where c1 = 2; select c2 from aqumv_t1 where c1 = 2; select c3, c2 from aqumv_t1 where c1 = 2; -set local answer_query_using_materialized_views = on; +set local enable_answer_query_using_materialized_views = on; select * from aqumv_t1 where c1 = 2; select c1, c2, c3 from aqumv_t1 where c1 = 2; select c2 from aqumv_t1 where c1 = 2; @@ -45,11 +45,11 @@ abort; begin; create incremental materialized view aqumv_mvt1_1 as select c2 as mc2, c3 as mc3, c1 as mc1, c2 as mc2_1 from aqumv_t1 where c1 = 3; analyze aqumv_mvt1_1; -set local answer_query_using_materialized_views = off; +set local enable_answer_query_using_materialized_views = off; select c1 as col1, c2 as col2 from aqumv_t1 where c1 = 3; select c1, c1 from aqumv_t1 where c1 = 3; -set local answer_query_using_materialized_views = on; +set local enable_answer_query_using_materialized_views = on; select c1 as col1, c2 as col2 from aqumv_t1 where c1 = 3; select c1, c1 from aqumv_t1 where c1 = 3; @@ -64,11 +64,11 @@ create incremental materialized view aqumv_mvt1_nonvar_expr as select c2, 1 as mc_const_1, sqrt(100) as mc_sqrt_100 from aqumv_t1 where c1 = 4; analyze aqumv_mvt1_nonvar_expr; -set local answer_query_using_materialized_views = off; +set local enable_answer_query_using_materialized_views = off; select c2, 200 from aqumv_t1 where c1 = 4; select c2, 1, sqrt(100) from aqumv_t1 where c1 = 4; -set local answer_query_using_materialized_views = on; +set local enable_answer_query_using_materialized_views = on; select c2, 200 from aqumv_t1 where c1 = 4; select c2, 1, sqrt(100) from aqumv_t1 where c1 = 4; @@ -88,10 +88,10 @@ create incremental materialized view aqumv_mvt1_func_has_var as from aqumv_t1 where c1 = 5; analyze aqumv_mvt1_func_has_var; -set local answer_query_using_materialized_views = off; +set local enable_answer_query_using_materialized_views = off; select c2, aqumv_func(c1, c3) from aqumv_t1 where c1 = 5; -set local answer_query_using_materialized_views = on; +set local enable_answer_query_using_materialized_views = on; select c2, aqumv_func(c1, c3) from aqumv_t1 where c1 = 5; -- Functions has Vars are replaced. @@ -101,16 +101,16 @@ abort; begin; create incremental materialized view aqumv_mvt1_2 as select c2 as mc2, c1 as mc1 from aqumv_t1 where c1 > 1 and c1 < 5; analyze aqumv_mvt1_2; -set local answer_query_using_materialized_views = on; +set local enable_answer_query_using_materialized_views = on; -- shoud be unable to use mv, projection doesn't exit in mv's tlist explain(verbose, costs off) select c3 from aqumv_t1 where c1 < 5 and c1 > 1; -- no post quals. explain(verbose, costs off) select c1, c2 from aqumv_t1 where c1 < 5 and c1 > 1; -- post quals added to mv. -set local answer_query_using_materialized_views = off; +set local enable_answer_query_using_materialized_views = off; select c1, c2 from aqumv_t1 where c1 < 5 and c1 > 1 and c2 = 4; -set local answer_query_using_materialized_views = on; +set local enable_answer_query_using_materialized_views = on; select c1, c2 from aqumv_t1 where c1 < 5 and c1 > 1 and c2 = 4; explain(verbose, costs off) select c1, c2 from aqumv_t1 where c1 < 5 and c1 > 1 and c2 = 4; @@ -122,7 +122,7 @@ begin; create incremental materialized view aqumv_mvt1_3 as select c2 as mc2, c1 as mc1, c3+1 as mc3 from aqumv_t1 where c1 > 5 and c1 < 10; analyze aqumv_mvt1_3; -set local answer_query_using_materialized_views = on; +set local enable_answer_query_using_materialized_views = on; -- should be unable to use mv, column c3 doesn't exist in mv's tlist. explain(verbose, costs off) select * from aqumv_t1 where c1 > 5 and c1 < 10; -- expr c3+1 is in mv's tlist @@ -132,7 +132,7 @@ explain(verbose, costs off) select c1+1 as col1, c2, c3+1 as col2 from aqumv_t1 select c1 as col1, c3+1 as col2 from aqumv_t1 where c1 > 5 and c1 < 10; select c1+1 as col1, c2, c3+1 as col2 from aqumv_t1 where c1 > 5 and c1 < 10; -set local answer_query_using_materialized_views = off; +set local enable_answer_query_using_materialized_views = off; select c1 as col1, c3+1 as col2 from aqumv_t1 where c1 > 5 and c1 < 10; select c1+1 as col1, c2, c3+1 as col2 from aqumv_t1 where c1 > 5 and c1 < 10; abort; @@ -144,12 +144,12 @@ create incremental materialized view aqumv_mvt1_4 as analyze aqumv_mvt1_4; -set local answer_query_using_materialized_views = on; +set local enable_answer_query_using_materialized_views = on; -- complex exprs explain(verbose, costs off) select c1, sqrt(abs(abs(c2) - c1 - 1) + abs(c2)) + 1, abs(c2) + 1 from aqumv_t1 where c1 > 10 and c1 < 15; select c1, sqrt(abs(abs(c2) - c1 - 1) + abs(c2)) + 1, abs(c2) + 1 from aqumv_t1 where c1 > 10 and c1 < 15; -set local answer_query_using_materialized_views = off; +set local enable_answer_query_using_materialized_views = off; select c1, sqrt(abs(abs(c2) - c1 - 1) + abs(c2)) + 1, abs(c2) + 1 from aqumv_t1 where c1 > 10 and c1 < 15; abort; @@ -161,7 +161,7 @@ create incremental materialized view aqumv_mvt1_post_quals as analyze aqumv_mvt1_post_quals; -set local answer_query_using_materialized_views = on; +set local enable_answer_query_using_materialized_views = on; explain(verbose, costs off) select c1, sqrt(abs(abs(c2) - c1 - 1) + abs(c2)) + 1, abs(c2) + 1 @@ -182,7 +182,7 @@ select c1, sqrt(abs(abs(c2) - c1 - 1) + abs(c2)) + 1, abs(c2) + 1 select c1, sqrt(abs(abs(c2) - c1 - 1) + abs(c2)) + 1, abs(c2) + 1 from aqumv_t1 where c1 > 20 and c1 < 30 and sqrt(abs(abs(c2) - c1 - 1) + 10) > 2; -set local answer_query_using_materialized_views = off; +set local enable_answer_query_using_materialized_views = off; select c1, sqrt(abs(abs(c2) - c1 - 1) + abs(c2)) + 1, abs(c2) + 1 from aqumv_t1 where c1 > 20 and c1 < 30 and sqrt(abs(c2) + 1) > 1; @@ -195,7 +195,7 @@ abort; -- choose the best one if there are multiple chooses based on cost. begin; -set local answer_query_using_materialized_views = on; +set local enable_answer_query_using_materialized_views = on; create incremental materialized view aqumv_mvt1_candidate_0 as select c1 as mc1, c2 as mc2, abs(c2) as mc3 @@ -223,5 +223,5 @@ select sqrt(abs(abs(c2) - c1 - 1) + abs(c2)) from aqumv_t1 where c1 > 30 and c1 abort; reset optimizer; -reset answer_query_using_materialized_views; +reset enable_answer_query_using_materialized_views; drop table aqumv_t1 cascade; diff --git a/src/test/regress/sql/rangefuncs_cdb.sql b/src/test/regress/sql/rangefuncs_cdb.sql index 0b3dc239adb..d34502b9bcf 100644 --- a/src/test/regress/sql/rangefuncs_cdb.sql +++ b/src/test/regress/sql/rangefuncs_cdb.sql @@ -2,7 +2,7 @@ -- Will run in parallel mode with enable_parallel=on and non-parallel mode. -- Filter this gucs to pass regression. -- -SELECT name, setting FROM pg_settings WHERE name LIKE 'enable%' and name != 'enable_parallel'; +SELECT name, setting FROM pg_settings WHERE name LIKE 'enable%' and name not in ('enable_parallel', 'enable_answer_query_using_materialized_views'); -- start_ignore create schema rangefuncs_cdb; set search_path to rangefuncs_cdb, public; diff --git a/src/test/regress/sql/sysviews.sql b/src/test/regress/sql/sysviews.sql index 3891a767df0..54ef918640f 100644 --- a/src/test/regress/sql/sysviews.sql +++ b/src/test/regress/sql/sysviews.sql @@ -49,7 +49,7 @@ select count(*) = 0 as ok from pg_stat_wal_receiver; -- This is to record the prevailing planner enable_foo settings during -- a regression test run. -- GP parallel tests will run another with enable_parallel=on, filter this to pass regression. -select name, setting from pg_settings where name like 'enable%' and name != 'enable_parallel'; +select name, setting from pg_settings where name like 'enable%' and name not in ('enable_parallel', 'enable_answer_query_using_materialized_views'); -- Test that the pg_timezone_names and pg_timezone_abbrevs views are -- more-or-less working. We can't test their contents in any great detail diff --git a/src/test/singlenode_regress/expected/sysviews.out b/src/test/singlenode_regress/expected/sysviews.out index e981a4e8e5f..bfea34f1f12 100644 --- a/src/test/singlenode_regress/expected/sysviews.out +++ b/src/test/singlenode_regress/expected/sysviews.out @@ -99,7 +99,7 @@ select count(*) = 0 as ok from pg_stat_wal_receiver; -- This is to record the prevailing planner enable_foo settings during -- a regression test run. -select name, setting from pg_settings where name like 'enable%' and name != 'enable_parallel'; +select name, setting from pg_settings where name like 'enable%' and name not in ('enable_parallel', 'enable_answer_query_using_materialized_views'); name | setting --------------------------------+--------- enable_async_append | on diff --git a/src/test/singlenode_regress/sql/sysviews.sql b/src/test/singlenode_regress/sql/sysviews.sql index c686d2620e7..f8867ffbe3b 100644 --- a/src/test/singlenode_regress/sql/sysviews.sql +++ b/src/test/singlenode_regress/sql/sysviews.sql @@ -48,7 +48,7 @@ select count(*) = 0 as ok from pg_stat_wal_receiver; -- This is to record the prevailing planner enable_foo settings during -- a regression test run. -select name, setting from pg_settings where name like 'enable%' and name != 'enable_parallel'; +select name, setting from pg_settings where name like 'enable%' and name not in ('enable_parallel', 'enable_answer_query_using_materialized_views'); -- Test that the pg_timezone_names and pg_timezone_abbrevs views are -- more-or-less working. We can't test their contents in any great detail