@@ -996,3 +996,348 @@ groupby_query_rewrite(PlannerInfo *subroot,
996996 subroot -> append_rel_list = NIL ;
997997 return true;
998998}
999+
1000+ /*
1001+ * aqumv_query_is_exact_match
1002+ *
1003+ * Compare two Query trees for semantic identity. Both should be at the
1004+ * same preprocessing stage (raw parser output). Returns true only if
1005+ * they are structurally identical in all query-semantics fields.
1006+ */
1007+ static bool
1008+ aqumv_query_is_exact_match (Query * raw_parse , Query * viewQuery )
1009+ {
1010+ /* Both must be CMD_SELECT */
1011+ if (raw_parse -> commandType != CMD_SELECT ||
1012+ viewQuery -> commandType != CMD_SELECT )
1013+ return false;
1014+
1015+ /* Same number of range table entries */
1016+ if (list_length (raw_parse -> rtable ) != list_length (viewQuery -> rtable ))
1017+ return false;
1018+
1019+ /* Compare range tables (table OIDs, join types, aliases structure) */
1020+ if (!equal (raw_parse -> rtable , viewQuery -> rtable ))
1021+ return false;
1022+
1023+ /* Compare join tree (FROM clause + WHERE quals) */
1024+ if (!equal (raw_parse -> jointree , viewQuery -> jointree ))
1025+ return false;
1026+
1027+ /* Compare target list entries: expressions and sort/group refs */
1028+ if (list_length (raw_parse -> targetList ) != list_length (viewQuery -> targetList ))
1029+ return false;
1030+ {
1031+ ListCell * lc1 , * lc2 ;
1032+ forboth (lc1 , raw_parse -> targetList , lc2 , viewQuery -> targetList )
1033+ {
1034+ TargetEntry * tle1 = lfirst_node (TargetEntry , lc1 );
1035+ TargetEntry * tle2 = lfirst_node (TargetEntry , lc2 );
1036+ if (!equal (tle1 -> expr , tle2 -> expr ))
1037+ return false;
1038+ if (tle1 -> resjunk != tle2 -> resjunk )
1039+ return false;
1040+ if (tle1 -> ressortgroupref != tle2 -> ressortgroupref )
1041+ return false;
1042+ }
1043+ }
1044+
1045+ /* Compare GROUP BY, HAVING, ORDER BY, DISTINCT, LIMIT */
1046+ if (!equal (raw_parse -> groupClause , viewQuery -> groupClause ))
1047+ return false;
1048+ if (!equal (raw_parse -> havingQual , viewQuery -> havingQual ))
1049+ return false;
1050+ if (!equal (raw_parse -> sortClause , viewQuery -> sortClause ))
1051+ return false;
1052+ if (!equal (raw_parse -> distinctClause , viewQuery -> distinctClause ))
1053+ return false;
1054+ if (!equal (raw_parse -> limitCount , viewQuery -> limitCount ))
1055+ return false;
1056+ if (!equal (raw_parse -> limitOffset , viewQuery -> limitOffset ))
1057+ return false;
1058+
1059+ /* Compare boolean flags */
1060+ if (raw_parse -> hasAggs != viewQuery -> hasAggs )
1061+ return false;
1062+ if (raw_parse -> hasWindowFuncs != viewQuery -> hasWindowFuncs )
1063+ return false;
1064+ if (raw_parse -> hasDistinctOn != viewQuery -> hasDistinctOn )
1065+ return false;
1066+
1067+ return true;
1068+ }
1069+
1070+ /*
1071+ * answer_query_using_materialized_views_for_join
1072+ *
1073+ * Handle multi-table JOIN queries via exact-match comparison.
1074+ * This is completely independent from the single-table AQUMV code path.
1075+ *
1076+ * We compare the saved raw parse tree (before any planner preprocessing)
1077+ * against the stored viewQuery from gp_matview_aux. On exact match,
1078+ * rewrite the query to a simple SELECT FROM mv.
1079+ */
1080+ RelOptInfo *
1081+ answer_query_using_materialized_views_for_join (PlannerInfo * root , AqumvContext aqumv_context )
1082+ {
1083+ RelOptInfo * current_rel = aqumv_context -> current_rel ;
1084+ query_pathkeys_callback qp_callback = aqumv_context -> qp_callback ;
1085+ Query * parse = root -> parse ;
1086+ Query * raw_parse = root -> aqumv_raw_parse ;
1087+ RelOptInfo * mv_final_rel = current_rel ;
1088+ Relation matviewRel ;
1089+ Relation mvauxDesc ;
1090+ TupleDesc mvaux_tupdesc ;
1091+ SysScanDesc mvscan ;
1092+ HeapTuple tup ;
1093+ Form_gp_matview_aux mvaux_tup ;
1094+ bool need_close = false;
1095+
1096+ /* Must have the saved raw parse tree. */
1097+ if (raw_parse == NULL )
1098+ return mv_final_rel ;
1099+
1100+ /* Must be a join query (more than one table in FROM). */
1101+ if (list_length (raw_parse -> rtable ) <= 1 )
1102+ return mv_final_rel ;
1103+
1104+ /* Basic eligibility checks (same as single-table AQUMV). */
1105+ if (parse -> commandType != CMD_SELECT ||
1106+ parse -> rowMarks != NIL ||
1107+ parse -> scatterClause != NIL ||
1108+ parse -> cteList != NIL ||
1109+ parse -> setOperations != NULL ||
1110+ parse -> hasModifyingCTE ||
1111+ parse -> parentStmtType == PARENTSTMTTYPE_REFRESH_MATVIEW ||
1112+ parse -> parentStmtType == PARENTSTMTTYPE_CTAS ||
1113+ contain_mutable_functions ((Node * ) raw_parse ) ||
1114+ parse -> hasSubLinks )
1115+ return mv_final_rel ;
1116+
1117+ mvauxDesc = table_open (GpMatviewAuxId , AccessShareLock );
1118+ mvaux_tupdesc = RelationGetDescr (mvauxDesc );
1119+
1120+ mvscan = systable_beginscan (mvauxDesc , InvalidOid , false,
1121+ NULL , 0 , NULL );
1122+
1123+ while (HeapTupleIsValid (tup = systable_getnext (mvscan )))
1124+ {
1125+ Datum view_query_datum ;
1126+ char * view_query_str ;
1127+ bool is_null ;
1128+ Query * viewQuery ;
1129+ RangeTblEntry * mvrte ;
1130+ PlannerInfo * subroot ;
1131+ TupleDesc mv_tupdesc ;
1132+
1133+ CHECK_FOR_INTERRUPTS ();
1134+ if (need_close )
1135+ table_close (matviewRel , AccessShareLock );
1136+
1137+ mvaux_tup = (Form_gp_matview_aux ) GETSTRUCT (tup );
1138+ matviewRel = table_open (mvaux_tup -> mvoid , AccessShareLock );
1139+ need_close = true;
1140+
1141+ if (!RelationIsPopulated (matviewRel ))
1142+ continue ;
1143+
1144+ /* MV must be up-to-date (IVM is always current). */
1145+ if (!RelationIsIVM (matviewRel ) &&
1146+ !MatviewIsGeneralyUpToDate (RelationGetRelid (matviewRel )))
1147+ continue ;
1148+
1149+ /* Get a copy of view query. */
1150+ view_query_datum = heap_getattr (tup ,
1151+ Anum_gp_matview_aux_view_query ,
1152+ mvaux_tupdesc ,
1153+ & is_null );
1154+
1155+ view_query_str = TextDatumGetCString (view_query_datum );
1156+ viewQuery = copyObject (stringToNode (view_query_str ));
1157+ pfree (view_query_str );
1158+ Assert (IsA (viewQuery , Query ));
1159+
1160+ /* Skip single-table viewQueries (handled by existing AQUMV). */
1161+ if (list_length (viewQuery -> rtable ) <= 1 )
1162+ continue ;
1163+
1164+ /* Exact match comparison between raw parse and view query. */
1165+ if (!aqumv_query_is_exact_match (raw_parse , viewQuery ))
1166+ continue ;
1167+
1168+ /*
1169+ * We have an exact match. Rewrite viewQuery to:
1170+ * SELECT mv.col1, mv.col2, ... FROM mv
1171+ */
1172+ mv_tupdesc = RelationGetDescr (matviewRel );
1173+
1174+ /* Build new target list referencing MV columns. */
1175+ {
1176+ List * new_tlist = NIL ;
1177+ ListCell * lc ;
1178+ int attnum = 0 ;
1179+
1180+ foreach (lc , viewQuery -> targetList )
1181+ {
1182+ TargetEntry * old_tle = lfirst_node (TargetEntry , lc );
1183+ TargetEntry * new_tle ;
1184+ Var * newVar ;
1185+ Form_pg_attribute attr ;
1186+
1187+ if (old_tle -> resjunk )
1188+ continue ;
1189+
1190+ attnum ++ ;
1191+ attr = TupleDescAttr (mv_tupdesc , attnum - 1 );
1192+
1193+ newVar = makeVar (1 ,
1194+ attr -> attnum ,
1195+ attr -> atttypid ,
1196+ attr -> atttypmod ,
1197+ attr -> attcollation ,
1198+ 0 );
1199+ newVar -> location = -1 ;
1200+
1201+ new_tle = makeTargetEntry ((Expr * ) newVar ,
1202+ (AttrNumber ) attnum ,
1203+ old_tle -> resname ,
1204+ false);
1205+ new_tlist = lappend (new_tlist , new_tle );
1206+ }
1207+
1208+ viewQuery -> targetList = new_tlist ;
1209+ }
1210+
1211+ /* Create new RTE for the MV. */
1212+ mvrte = makeNode (RangeTblEntry );
1213+ mvrte -> rtekind = RTE_RELATION ;
1214+ mvrte -> relid = RelationGetRelid (matviewRel );
1215+ mvrte -> relkind = RELKIND_MATVIEW ;
1216+ mvrte -> rellockmode = AccessShareLock ;
1217+ mvrte -> inh = false;
1218+ mvrte -> inFromCl = true;
1219+
1220+ /* Build eref with column names from the MV's TupleDesc. */
1221+ {
1222+ Alias * eref = makeAlias (RelationGetRelationName (matviewRel ), NIL );
1223+ int i ;
1224+ for (i = 0 ; i < mv_tupdesc -> natts ; i ++ )
1225+ {
1226+ Form_pg_attribute attr = TupleDescAttr (mv_tupdesc , i );
1227+ if (!attr -> attisdropped )
1228+ eref -> colnames = lappend (eref -> colnames ,
1229+ makeString (pstrdup (NameStr (attr -> attname ))));
1230+ else
1231+ eref -> colnames = lappend (eref -> colnames ,
1232+ makeString (pstrdup ("" )));
1233+ }
1234+ mvrte -> eref = eref ;
1235+ mvrte -> alias = makeAlias (RelationGetRelationName (matviewRel ), NIL );
1236+ }
1237+
1238+ viewQuery -> rtable = list_make1 (mvrte );
1239+ viewQuery -> jointree = makeFromExpr (list_make1 (makeNode (RangeTblRef )), NULL );
1240+ ((RangeTblRef * ) linitial (viewQuery -> jointree -> fromlist ))-> rtindex = 1 ;
1241+
1242+ /* Clear aggregation/grouping/sorting state — all materialized. */
1243+ viewQuery -> hasAggs = false;
1244+ viewQuery -> groupClause = NIL ;
1245+ viewQuery -> havingQual = NULL ;
1246+ viewQuery -> sortClause = NIL ;
1247+ viewQuery -> distinctClause = NIL ;
1248+ viewQuery -> hasDistinctOn = false;
1249+ viewQuery -> hasWindowFuncs = false;
1250+ viewQuery -> hasTargetSRFs = false;
1251+ viewQuery -> limitCount = parse -> limitCount ;
1252+ viewQuery -> limitOffset = parse -> limitOffset ;
1253+ viewQuery -> limitOption = parse -> limitOption ;
1254+
1255+ /* Create subroot for planning the MV scan. */
1256+ subroot = (PlannerInfo * ) palloc (sizeof (PlannerInfo ));
1257+ memcpy (subroot , root , sizeof (PlannerInfo ));
1258+ subroot -> parent_root = root ;
1259+ subroot -> eq_classes = NIL ;
1260+ subroot -> plan_params = NIL ;
1261+ subroot -> outer_params = NULL ;
1262+ subroot -> init_plans = NIL ;
1263+ subroot -> agginfos = NIL ;
1264+ subroot -> aggtransinfos = NIL ;
1265+ subroot -> parse = viewQuery ;
1266+ subroot -> tuple_fraction = root -> tuple_fraction ;
1267+ subroot -> limit_tuples = root -> limit_tuples ;
1268+ subroot -> append_rel_list = NIL ;
1269+ subroot -> hasHavingQual = false;
1270+ subroot -> hasNonPartialAggs = false;
1271+ subroot -> hasNonSerialAggs = false;
1272+ subroot -> numOrderedAggs = 0 ;
1273+ subroot -> hasNonCombine = false;
1274+ subroot -> numPureOrderedAggs = 0 ;
1275+
1276+ subroot -> processed_tlist = NIL ;
1277+ preprocess_targetlist (subroot );
1278+
1279+ /* Compute final locus for the MV scan. */
1280+ {
1281+ PathTarget * newtarget = make_pathtarget_from_tlist (subroot -> processed_tlist );
1282+ subroot -> final_locus = cdbllize_get_final_locus (subroot , newtarget );
1283+ }
1284+
1285+ /*
1286+ * Plan the MV scan.
1287+ *
1288+ * We need a clean qp_extra with no groupClause or activeWindows,
1289+ * because the rewritten viewQuery is a simple SELECT from the MV
1290+ * with no GROUP BY, windowing, etc. The standard_qp_callback uses
1291+ * qp_extra->groupClause to compute group_pathkeys, which would fail
1292+ * if it still contained the original query's GROUP BY expressions.
1293+ *
1294+ * standard_qp_extra is { List *activeWindows; List *groupClause; },
1295+ * so a zeroed struct of that size works correctly (both fields NIL).
1296+ */
1297+ {
1298+ char clean_qp_extra [2 * sizeof (List * )];
1299+ memset (clean_qp_extra , 0 , sizeof (clean_qp_extra ));
1300+ mv_final_rel = query_planner (subroot , qp_callback , clean_qp_extra );
1301+ }
1302+
1303+ /* Cost-based decision: use MV only if cheaper. */
1304+ if (mv_final_rel -> cheapest_total_path -> total_cost < current_rel -> cheapest_total_path -> total_cost )
1305+ {
1306+ root -> parse = viewQuery ;
1307+ root -> processed_tlist = subroot -> processed_tlist ;
1308+ root -> agginfos = subroot -> agginfos ;
1309+ root -> aggtransinfos = subroot -> aggtransinfos ;
1310+ root -> simple_rte_array = subroot -> simple_rte_array ;
1311+ root -> simple_rel_array = subroot -> simple_rel_array ;
1312+ root -> simple_rel_array_size = subroot -> simple_rel_array_size ;
1313+ root -> hasNonPartialAggs = subroot -> hasNonPartialAggs ;
1314+ root -> hasNonSerialAggs = subroot -> hasNonSerialAggs ;
1315+ root -> numOrderedAggs = subroot -> numOrderedAggs ;
1316+ root -> hasNonCombine = subroot -> hasNonCombine ;
1317+ root -> numPureOrderedAggs = subroot -> numPureOrderedAggs ;
1318+ root -> hasHavingQual = subroot -> hasHavingQual ;
1319+ root -> group_pathkeys = subroot -> group_pathkeys ;
1320+ root -> sort_pathkeys = subroot -> sort_pathkeys ;
1321+ root -> query_pathkeys = subroot -> query_pathkeys ;
1322+ root -> distinct_pathkeys = subroot -> distinct_pathkeys ;
1323+ root -> eq_classes = subroot -> eq_classes ;
1324+ root -> append_rel_list = subroot -> append_rel_list ;
1325+ current_rel = mv_final_rel ;
1326+ table_close (matviewRel , NoLock );
1327+ need_close = false;
1328+ break ;
1329+ }
1330+ else
1331+ {
1332+ /* MV is not cheaper, reset and try next. */
1333+ mv_final_rel = current_rel ;
1334+ }
1335+ }
1336+
1337+ if (need_close )
1338+ table_close (matviewRel , AccessShareLock );
1339+ systable_endscan (mvscan );
1340+ table_close (mvauxDesc , AccessShareLock );
1341+
1342+ return current_rel ;
1343+ }
0 commit comments