Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
349 changes: 349 additions & 0 deletions src/backend/optimizer/plan/aqumv.c
Original file line number Diff line number Diff line change
Expand Up @@ -996,3 +996,352 @@ groupby_query_rewrite(PlannerInfo *subroot,
subroot->append_rel_list = NIL;
return true;
}

/*
* aqumv_query_is_exact_match
*
* Compare two Query trees for semantic identity. Both should be at the
* same preprocessing stage (raw parser output). Returns true only if
* they are structurally identical in all query-semantics fields.
*/
static bool
aqumv_query_is_exact_match(Query *raw_parse, Query *viewQuery)
{
/* Both must be CMD_SELECT */
if (raw_parse->commandType != CMD_SELECT ||
viewQuery->commandType != CMD_SELECT)
return false;

/* Same number of range table entries */
if (list_length(raw_parse->rtable) != list_length(viewQuery->rtable))
return false;

/* Compare range tables (table OIDs, join types, aliases structure) */
if (!equal(raw_parse->rtable, viewQuery->rtable))
return false;

/* Compare join tree (FROM clause + WHERE quals) */
if (!equal(raw_parse->jointree, viewQuery->jointree))
return false;

/* Compare target list entries: expressions and sort/group refs */
if (list_length(raw_parse->targetList) != list_length(viewQuery->targetList))
return false;
{
ListCell *lc1, *lc2;
forboth(lc1, raw_parse->targetList, lc2, viewQuery->targetList)
{
TargetEntry *tle1 = lfirst_node(TargetEntry, lc1);
TargetEntry *tle2 = lfirst_node(TargetEntry, lc2);
if (!equal(tle1->expr, tle2->expr))
return false;
if (tle1->resjunk != tle2->resjunk)
return false;
if (tle1->ressortgroupref != tle2->ressortgroupref)
return false;
}
}

/* Compare GROUP BY, HAVING, ORDER BY, DISTINCT, LIMIT */
if (!equal(raw_parse->groupClause, viewQuery->groupClause))
return false;
if (raw_parse->groupDistinct != viewQuery->groupDistinct)
return false;
if (!equal(raw_parse->havingQual, viewQuery->havingQual))
return false;
if (!equal(raw_parse->sortClause, viewQuery->sortClause))
return false;
if (!equal(raw_parse->distinctClause, viewQuery->distinctClause))
return false;
if (!equal(raw_parse->limitCount, viewQuery->limitCount))
return false;
if (!equal(raw_parse->limitOffset, viewQuery->limitOffset))
return false;
if (raw_parse->limitOption != viewQuery->limitOption)
return false;

/* Compare boolean flags */
if (raw_parse->hasAggs != viewQuery->hasAggs)
return false;
if (raw_parse->hasWindowFuncs != viewQuery->hasWindowFuncs)
return false;
if (raw_parse->hasDistinctOn != viewQuery->hasDistinctOn)
return false;

return true;
}

/*
* answer_query_using_materialized_views_for_join
*
* Handle multi-table JOIN queries via exact-match comparison.
* This is completely independent from the single-table AQUMV code path.
*
* We compare the saved raw parse tree (before any planner preprocessing)
* against the stored viewQuery from gp_matview_aux. On exact match,
* rewrite the query to a simple SELECT FROM mv.
*/
RelOptInfo *
answer_query_using_materialized_views_for_join(PlannerInfo *root, AqumvContext aqumv_context)
{
RelOptInfo *current_rel = aqumv_context->current_rel;
query_pathkeys_callback qp_callback = aqumv_context->qp_callback;
Query *parse = root->parse;
Query *raw_parse = root->aqumv_raw_parse;
RelOptInfo *mv_final_rel = current_rel;
Relation matviewRel;
Relation mvauxDesc;
TupleDesc mvaux_tupdesc;
SysScanDesc mvscan;
HeapTuple tup;
Form_gp_matview_aux mvaux_tup;
bool need_close = false;

/* Must have the saved raw parse tree. */
if (raw_parse == NULL)
return mv_final_rel;

/* Must be a join query (more than one table in FROM). */
if (list_length(raw_parse->rtable) <= 1)
return mv_final_rel;

/* Basic eligibility checks (same as single-table AQUMV). */
if (parse->commandType != CMD_SELECT ||
parse->rowMarks != NIL ||
parse->scatterClause != NIL ||
parse->cteList != NIL ||
parse->setOperations != NULL ||
parse->hasModifyingCTE ||
parse->parentStmtType == PARENTSTMTTYPE_REFRESH_MATVIEW ||
parse->parentStmtType == PARENTSTMTTYPE_CTAS ||
contain_mutable_functions((Node *) raw_parse) ||
parse->hasSubLinks)
return mv_final_rel;

mvauxDesc = table_open(GpMatviewAuxId, AccessShareLock);
mvaux_tupdesc = RelationGetDescr(mvauxDesc);

mvscan = systable_beginscan(mvauxDesc, InvalidOid, false,
NULL, 0, NULL);

while (HeapTupleIsValid(tup = systable_getnext(mvscan)))
{
Datum view_query_datum;
char *view_query_str;
bool is_null;
Query *viewQuery;
RangeTblEntry *mvrte;
PlannerInfo *subroot;
TupleDesc mv_tupdesc;

CHECK_FOR_INTERRUPTS();
if (need_close)
table_close(matviewRel, AccessShareLock);

mvaux_tup = (Form_gp_matview_aux) GETSTRUCT(tup);
matviewRel = table_open(mvaux_tup->mvoid, AccessShareLock);
need_close = true;

if (!RelationIsPopulated(matviewRel))
continue;

/* MV must be up-to-date (IVM is always current). */
if (!RelationIsIVM(matviewRel) &&
!MatviewIsGeneralyUpToDate(RelationGetRelid(matviewRel)))
continue;

/* Get a copy of view query. */
view_query_datum = heap_getattr(tup,
Anum_gp_matview_aux_view_query,
mvaux_tupdesc,
&is_null);

view_query_str = TextDatumGetCString(view_query_datum);
viewQuery = copyObject(stringToNode(view_query_str));
pfree(view_query_str);
Assert(IsA(viewQuery, Query));

/* Skip single-table viewQueries (handled by existing AQUMV). */
if (list_length(viewQuery->rtable) <= 1)
continue;

/* Exact match comparison between raw parse and view query. */
if (!aqumv_query_is_exact_match(raw_parse, viewQuery))
continue;

/*
* We have an exact match. Rewrite viewQuery to:
* SELECT mv.col1, mv.col2, ... FROM mv
*/
mv_tupdesc = RelationGetDescr(matviewRel);

/* Build new target list referencing MV columns. */
{
List *new_tlist = NIL;
ListCell *lc;
int attnum = 0;

foreach(lc, viewQuery->targetList)
{
TargetEntry *old_tle = lfirst_node(TargetEntry, lc);
TargetEntry *new_tle;
Var *newVar;
Form_pg_attribute attr;

if (old_tle->resjunk)
continue;

attnum++;
attr = TupleDescAttr(mv_tupdesc, attnum - 1);

newVar = makeVar(1,
attr->attnum,
attr->atttypid,
attr->atttypmod,
attr->attcollation,
0);
newVar->location = -1;

new_tle = makeTargetEntry((Expr *) newVar,
(AttrNumber) attnum,
old_tle->resname,
false);
new_tle->ressortgroupref = old_tle->ressortgroupref;
new_tlist = lappend(new_tlist, new_tle);
}

viewQuery->targetList = new_tlist;
}

/* Create new RTE for the MV. */
mvrte = makeNode(RangeTblEntry);
mvrte->rtekind = RTE_RELATION;
mvrte->relid = RelationGetRelid(matviewRel);
mvrte->relkind = RELKIND_MATVIEW;
mvrte->rellockmode = AccessShareLock;
mvrte->inh = false;
mvrte->inFromCl = true;

/* Build eref with column names from the MV's TupleDesc. */
{
Alias *eref = makeAlias(RelationGetRelationName(matviewRel), NIL);
int i;
for (i = 0; i < mv_tupdesc->natts; i++)
{
Form_pg_attribute attr = TupleDescAttr(mv_tupdesc, i);
if (!attr->attisdropped)
eref->colnames = lappend(eref->colnames,
makeString(pstrdup(NameStr(attr->attname))));
else
eref->colnames = lappend(eref->colnames,
makeString(pstrdup("")));
}
mvrte->eref = eref;
mvrte->alias = makeAlias(RelationGetRelationName(matviewRel), NIL);
}

viewQuery->rtable = list_make1(mvrte);
viewQuery->jointree = makeFromExpr(list_make1(makeNode(RangeTblRef)), NULL);
((RangeTblRef *) linitial(viewQuery->jointree->fromlist))->rtindex = 1;

/* Clear aggregation/grouping state — already materialized in MV. */
viewQuery->hasAggs = false;
viewQuery->groupClause = NIL;
viewQuery->havingQual = NULL;
/* Keep sortClause: upper planner needs it to add Sort node. */
viewQuery->distinctClause = NIL;
viewQuery->hasDistinctOn = false;
viewQuery->hasWindowFuncs = false;
viewQuery->hasTargetSRFs = false;
viewQuery->limitCount = parse->limitCount;
viewQuery->limitOffset = parse->limitOffset;
viewQuery->limitOption = parse->limitOption;

/* Create subroot for planning the MV scan. */
subroot = (PlannerInfo *) palloc(sizeof(PlannerInfo));
memcpy(subroot, root, sizeof(PlannerInfo));
subroot->parent_root = root;
subroot->eq_classes = NIL;
subroot->plan_params = NIL;
subroot->outer_params = NULL;
subroot->init_plans = NIL;
subroot->agginfos = NIL;
subroot->aggtransinfos = NIL;
subroot->parse = viewQuery;
subroot->tuple_fraction = root->tuple_fraction;
subroot->limit_tuples = root->limit_tuples;
subroot->append_rel_list = NIL;
subroot->hasHavingQual = false;
subroot->hasNonPartialAggs = false;
subroot->hasNonSerialAggs = false;
subroot->numOrderedAggs = 0;
subroot->hasNonCombine = false;
subroot->numPureOrderedAggs = 0;

subroot->processed_tlist = NIL;
preprocess_targetlist(subroot);

/* Compute final locus for the MV scan. */
{
PathTarget *newtarget = make_pathtarget_from_tlist(subroot->processed_tlist);
subroot->final_locus = cdbllize_get_final_locus(subroot, newtarget);
}

/*
* Plan the MV scan.
*
* Clear qp_extra's groupClause and activeWindows because the
* rewritten viewQuery is a simple SELECT from the MV with no
* GROUP BY or windowing. standard_qp_callback would otherwise
* try to compute group_pathkeys from stale expressions.
*
* Safe: grouping_planner() no longer reads qp_extra after AQUMV.
*/
{
standard_qp_extra *qp = (standard_qp_extra *) aqumv_context->qp_extra;
qp->activeWindows = NIL;
qp->groupClause = NIL;
}
mv_final_rel = query_planner(subroot, qp_callback, aqumv_context->qp_extra);

/* Cost-based decision: use MV only if cheaper. */
if (mv_final_rel->cheapest_total_path->total_cost < current_rel->cheapest_total_path->total_cost)
{
root->parse = viewQuery;
root->processed_tlist = subroot->processed_tlist;
root->agginfos = subroot->agginfos;
root->aggtransinfos = subroot->aggtransinfos;
root->simple_rte_array = subroot->simple_rte_array;
root->simple_rel_array = subroot->simple_rel_array;
root->simple_rel_array_size = subroot->simple_rel_array_size;
root->hasNonPartialAggs = subroot->hasNonPartialAggs;
root->hasNonSerialAggs = subroot->hasNonSerialAggs;
root->numOrderedAggs = subroot->numOrderedAggs;
root->hasNonCombine = subroot->hasNonCombine;
root->numPureOrderedAggs = subroot->numPureOrderedAggs;
root->hasHavingQual = subroot->hasHavingQual;
root->group_pathkeys = subroot->group_pathkeys;
root->sort_pathkeys = subroot->sort_pathkeys;
root->query_pathkeys = subroot->query_pathkeys;
root->distinct_pathkeys = subroot->distinct_pathkeys;
root->eq_classes = subroot->eq_classes;
root->append_rel_list = subroot->append_rel_list;
current_rel = mv_final_rel;
table_close(matviewRel, NoLock);
need_close = false;
break;
}
else
{
/* MV is not cheaper, reset and try next. */
mv_final_rel = current_rel;
}
}

if (need_close)
table_close(matviewRel, AccessShareLock);
systable_endscan(mvscan);
table_close(mvauxDesc, AccessShareLock);

return current_rel;
}
25 changes: 19 additions & 6 deletions src/backend/optimizer/plan/planner.c
Original file line number Diff line number Diff line change
Expand Up @@ -120,12 +120,7 @@ create_upper_paths_hook_type create_upper_paths_hook = NULL;
#define EXPRKIND_TABLEFUNC_LATERAL 12
#define EXPRKIND_WINDOW_BOUND 13

/* Passthrough data for standard_qp_callback */
typedef struct
{
List *activeWindows; /* active windows, if any */
List *groupClause; /* overrides parse->groupClause */
} standard_qp_extra;
/* standard_qp_extra is defined in optimizer/planner.h */

/*
* Data specific to grouping sets
Expand Down Expand Up @@ -952,6 +947,17 @@ subquery_planner(PlannerGlobal *glob, Query *parse,
root->partColsUpdated = false;
root->is_correlated_subplan = false;

/*
* Save a copy of the raw parse tree for AQUMV join exact-match.
* This must be done before any preprocessing modifies the parse tree.
*/
if (Gp_role == GP_ROLE_DISPATCH &&
enable_answer_query_using_materialized_views &&
parent_root == NULL)
root->aqumv_raw_parse = copyObject(parse);
else
root->aqumv_raw_parse = NULL;

/*
* If there is a WITH list, process each WITH query and either convert it
* to RTE_SUBQUERY RTE(s) or build an initplan SubPlan structure for it.
Expand Down Expand Up @@ -1935,6 +1941,13 @@ grouping_planner(PlannerInfo *root, double tuple_fraction)

/* Do the real work. */
current_rel = answer_query_using_materialized_views(root, aqumv_context);

/* Try join AQUMV if single-table didn't rewrite. */
if (current_rel == aqumv_context->current_rel)
{
current_rel = answer_query_using_materialized_views_for_join(root, aqumv_context);
}

/* parse tree may be rewriten. */
parse = root->parse;
}
Expand Down
Loading
Loading