diff --git a/src/backend/cdb/dispatcher/cdbdisp_query.c b/src/backend/cdb/dispatcher/cdbdisp_query.c index 6eab2c0dc59..697efe518f0 100644 --- a/src/backend/cdb/dispatcher/cdbdisp_query.c +++ b/src/backend/cdb/dispatcher/cdbdisp_query.c @@ -49,12 +49,17 @@ #include "cdb/cdbdispatchresult.h" #include "cdb/cdbcopy.h" #include "executor/execUtils.h" +#include "cdb/cdbpq.h" #define QUERY_STRING_TRUNCATE_SIZE (1024) extern bool Test_print_direct_dispatch_info; extern bool gp_print_create_gang_time; + +ExtendProtocolDataStore epd_storage = {0}; +ExtendProtocolData epd = &epd_storage; + typedef struct ParamWalkerContext { plan_tree_base_prefix base; /* Required prefix for @@ -1678,3 +1683,104 @@ findParamType(List *params, int paramid) return InvalidOid; } + +/* + * process data in pointer cosume_p, ex: copy everything interested in. + * For EP_TAG_I, EP_TAG_U, EP_TAG_D, consume_p is a Bitmapset**, we just + * copy the content and process them later. + */ +void +ConsumeExtendProtocolData(ExtendProtocolSubTag subtag, void *consume_p) +{ + Assert(epd); + + if ((epd->consumed_bitmap & (1 << subtag)) == 0) + return; + + switch (subtag) + { + case EP_TAG_I: + case EP_TAG_U: + case EP_TAG_D: + Assert(consume_p != NULL); + *((Bitmapset **) consume_p) = bms_copy(list_nth(epd->subtagdata, subtag)); + bms_free(list_nth(epd->subtagdata, subtag)); /* clean up */ + break; + default: + Assert(false); + } + + /* Mark subtag consumed. */ + epd->consumed_bitmap &= ~(1 << subtag); +} + +/* + * Contents must be allocated in TopTransactionMemoryContext. + */ +void InitExtendProtocolData(void) +{ + MemoryContext oldctx = MemoryContextSwitchTo(TopTransactionContext); + + /* Make bitmapset allocated under the context we set. */ + Bitmapset *inserted = bms_make_singleton(0); + inserted = bms_del_member(inserted, 0); + Bitmapset *updated = bms_make_singleton(0); + updated = bms_del_member(updated, 0); + Bitmapset *deleted = bms_make_singleton(0); + deleted = bms_del_member(deleted, 0); + + epd->subtagdata = list_make1(inserted); + epd->subtagdata = lappend(epd->subtagdata, updated); + epd->subtagdata = lappend(epd->subtagdata, deleted); + + epd->consumed_bitmap = 0; + + MemoryContextSwitchTo(oldctx); +} + +/* + * Handle extend protocol aside from upstream. + * Store everything in TopTransactionMemoryContext. + * Do not error here, let libpq work. + * End of each process when we reached a EP_TAG_MAX, callers + * should follow this behaviour!. + */ +bool HandleExtendProtocol(PGconn *conn) +{ + int subtag; + int num; + if (Gp_role != GP_ROLE_DISPATCH) + return false; + + for (;;) + { + if (pqGetInt(&subtag, 4, conn)) + return false; + switch (subtag) + { + case EP_TAG_I: + case EP_TAG_U: + case EP_TAG_D: + if (pqGetInt(&num, 4, conn)) + return false; + for (int i = 0; i < num; i++) + { + Bitmapset *relids = (Bitmapset *) list_nth(epd->subtagdata, subtag); + int relid; + if (pqGetInt(&relid, 4, conn)) + return false; + relids = bms_add_member(relids, relid); + list_nth_replace(epd->subtagdata, subtag, relids); + /* Mark subtag to be consumed. */ + epd->consumed_bitmap |= 1 << subtag; + } + break; + case EP_TAG_MAX: + /* End of this run. */ + return true; + default: + Assert(false); + return false; + } + } +} diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c index 996838f8fd6..5bb1463e422 100644 --- a/src/backend/executor/execMain.c +++ b/src/backend/executor/execMain.c @@ -192,6 +192,9 @@ static void EvalPlanQualStart(EPQState *epqstate, Plan *planTree); static void AdjustReplicatedTableCounts(EState *estate); +static void +MaintainMaterializedViewStatus(QueryDesc *queryDesc, CmdType operation); + /* end of local decls */ @@ -248,6 +251,7 @@ standard_ExecutorStart(QueryDesc *queryDesc, int eflags) bool shouldDispatch; bool needDtx; List *volatile toplevelOidCache = NIL; + bool has_writable_operation = false; /* sanity checks: queryDesc must not be started already */ Assert(queryDesc != NULL); @@ -395,7 +399,10 @@ standard_ExecutorStart(QueryDesc *queryDesc, int eflags) */ if (queryDesc->plannedstmt->rowMarks != NIL || queryDesc->plannedstmt->hasModifyingCTE) + { estate->es_output_cid = GetCurrentCommandId(true); + has_writable_operation = true; + } /* * A SELECT without modifying CTEs can't possibly queue triggers, @@ -411,6 +418,7 @@ standard_ExecutorStart(QueryDesc *queryDesc, int eflags) case CMD_DELETE: case CMD_UPDATE: estate->es_output_cid = GetCurrentCommandId(true); + has_writable_operation = true; break; default: @@ -545,6 +553,11 @@ standard_ExecutorStart(QueryDesc *queryDesc, int eflags) else shouldDispatch = false; + if (IS_QD_OR_SINGLENODE() && has_writable_operation) + { + InitExtendProtocolData(); + } + /* * We don't eliminate aliens if we don't have an MPP plan * or we are executing on master. @@ -1037,7 +1050,8 @@ standard_ExecutorRun(QueryDesc *queryDesc, /* should never happen */ Assert(!"undefined parallel execution strategy"); } - if ((exec_identity == GP_IGNORE || exec_identity == GP_ROOT_SLICE) && operation != CMD_SELECT) + if ((exec_identity == GP_IGNORE || exec_identity == GP_ROOT_SLICE) && + (operation != CMD_SELECT || (queryDesc->plannedstmt->hasModifyingCTE))) es_processed = mppExecutorWait(queryDesc); /* @@ -1062,45 +1076,7 @@ standard_ExecutorRun(QueryDesc *queryDesc, queryDesc->plannedstmt->hasModifyingCTE) && ((es_processed > 0 || estate->es_processed > 0) || !queryDesc->plannedstmt->canSetTag)) { - List *rtable = queryDesc->plannedstmt->rtable; - int length = list_length(rtable); - ListCell *lc; - List *unique_result_relations = list_concat_unique_int(NIL, queryDesc->plannedstmt->resultRelations); - - foreach(lc, unique_result_relations) - { - - int varno = lfirst_int(lc); - RangeTblEntry *rte = rt_fetch(varno, rtable); - - /* Avoid crash in case we don't find a rte. */ - if (varno > length + 1) - { - ereport(WARNING, (errmsg("could not find rte of varno: %u ", varno))); - continue; - } - - switch (operation) - { - case CMD_INSERT: - SetRelativeMatviewAuxStatus(rte->relid, - MV_DATA_STATUS_EXPIRED_INSERT_ONLY, - MV_DATA_STATUS_TRANSFER_DIRECTION_ALL); - break; - case CMD_UPDATE: - case CMD_DELETE: - SetRelativeMatviewAuxStatus(rte->relid, - MV_DATA_STATUS_EXPIRED, - MV_DATA_STATUS_TRANSFER_DIRECTION_ALL); - break; - default: - /* If there were writable CTE, just mark it as expired. */ - if (queryDesc->plannedstmt->hasModifyingCTE) - SetRelativeMatviewAuxStatus(rte->relid, MV_DATA_STATUS_EXPIRED, - MV_DATA_STATUS_TRANSFER_DIRECTION_ALL); - break; - } - } + MaintainMaterializedViewStatus(queryDesc, operation); } } PG_CATCH(); @@ -4291,3 +4267,119 @@ already_under_executor_run(void) { return executor_run_nesting_level > 0; } + +/* + * Maintain the status of Materialized Views in response to write operations on the underlying relations. + * + * For partitioned tables, changes are tracked using the relations of their leaf partitions rather than + * the parent tables themselves. This minimizes the impact on Materialized Views that depend on the + * partition tree, ensuring only relevant partitions are affected. + * + * In the case of cross-partition updates, an UPDATE operation on a parent table is decomposed into + * an INSERT on one leaf partition and a DELETE on another. As a result, the status transition follows + * an UP direction for both INSERT and DELETE operations, rather than an UP_AND_DOWN direction on the + * parent table. This approach optimizes performance and reduces unnecessary status changes avoding + * invalidations of unrelated materialized views. + * + * For non-partitioned tables, the status transition is handled based on the semantic relations. + */ +static void +MaintainMaterializedViewStatus(QueryDesc *queryDesc, CmdType operation) +{ + Bitmapset *inserted = NULL; + Bitmapset *updated = NULL; + Bitmapset *deleted = NULL; + List *unique_result_relations = NIL; + List *rtable = queryDesc->plannedstmt->rtable; + int length = list_length(rtable); + ListCell *lc; + int relid = -1; + + /* + * Process epd first to get the addected relations.. + */ + ConsumeExtendProtocolData(EP_TAG_I, &inserted); + ConsumeExtendProtocolData(EP_TAG_U, &updated); + ConsumeExtendProtocolData(EP_TAG_D, &deleted); + + relid = -1; + while((relid = bms_next_member(inserted, relid)) >= 0) + { + /* Only need to transfer to UP direction. */ + SetRelativeMatviewAuxStatus(relid, MV_DATA_STATUS_EXPIRED_INSERT_ONLY, + MV_DATA_STATUS_TRANSFER_DIRECTION_UP); + + } + + relid = -1; + while((relid = bms_next_member(updated, relid)) >= 0) + { + SetRelativeMatviewAuxStatus(relid, MV_DATA_STATUS_EXPIRED, + MV_DATA_STATUS_TRANSFER_DIRECTION_UP); + + } + + relid = -1; + while((relid = bms_next_member(deleted, relid)) >= 0) + { + SetRelativeMatviewAuxStatus(relid, MV_DATA_STATUS_EXPIRED, + MV_DATA_STATUS_TRANSFER_DIRECTION_UP); + + } + + unique_result_relations = list_concat_unique_int(NIL, queryDesc->plannedstmt->resultRelations); + foreach(lc, unique_result_relations) + { + int varno = lfirst_int(lc); + RangeTblEntry *rte = rt_fetch(varno, rtable); + + /* Avoid crash in case we don't find a rte. */ + if (varno > length + 1) + { + ereport(WARNING, (errmsg("could not find rte of varno: %u ", varno))); + continue; + } + + if (RELKIND_PARTITIONED_TABLE == rte->relkind) + { + /* + * There should be leaf paritions if we modifed a partitioned table + * Do a second check and fall back to partitioned table + * in case that if we failed to find a one. + */ + if (bms_is_empty(inserted) && + bms_is_empty(updated) && + bms_is_empty(deleted)) + { + ereport(WARNING, + (errmsg("fail to find leafs of partitioned table: %u ", rte->relid))); + } + /* Should already be processed, just bypass. */ + continue; + } + else + { + /* Do a normal update. */ + switch (operation) + { + case CMD_INSERT: + SetRelativeMatviewAuxStatus(rte->relid, + MV_DATA_STATUS_EXPIRED_INSERT_ONLY, + MV_DATA_STATUS_TRANSFER_DIRECTION_ALL); + break; + case CMD_UPDATE: + case CMD_DELETE: + SetRelativeMatviewAuxStatus(rte->relid, + MV_DATA_STATUS_EXPIRED, + MV_DATA_STATUS_TRANSFER_DIRECTION_ALL); + break; + default: + /* If there were writable CTE, just mark it as expired. */ + if (queryDesc->plannedstmt->hasModifyingCTE) + SetRelativeMatviewAuxStatus(rte->relid, MV_DATA_STATUS_EXPIRED, + MV_DATA_STATUS_TRANSFER_DIRECTION_ALL); + break; + } + } + } +} diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index da4575acb72..0a35902481b 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -57,12 +57,17 @@ #include "access/transam.h" #include "cdb/cdbaocsam.h" #include "cdb/cdbappendonlyam.h" +#include "cdb/cdbdisp_query.h" #include "cdb/cdbhash.h" +#include "cdb/cdbpq.h" #include "cdb/cdbvars.h" #include "parser/parsetree.h" #include "utils/lsyscache.h" #include "utils/snapmgr.h" +#include "libpq/libpq.h" +#include "libpq/pqformat.h" + ext_dml_func_hook_type ext_dml_init_hook = NULL; ext_dml_func_hook_type ext_dml_finish_hook = NULL; @@ -94,6 +99,18 @@ static TupleTableSlot *ExecPrepareTupleRouting(ModifyTableState *mtstate, TupleTableSlot *slot, ResultRelInfo **partRelInfo); +static void +send_subtag(StringInfoData *buf, ExtendProtocolSubTag subtag, Bitmapset* relids); + +static void +notify_modified_relations_to_QD(ModifyTableState *node); + +static void +notify_modified_relations_local(ModifyTableState *node); + +static void +epd_add_subtag_data(ExtendProtocolSubTag subtag, Bitmapset *relids); + /* * Verify that the tuples to be produced by INSERT match the * target relation's rowtype @@ -1033,6 +1050,13 @@ ExecInsert(ModifyTableState *mtstate, if (canSetTag) (estate->es_processed)++; + if (resultRelationDesc->rd_rel->relispartition) + { + mtstate->mt_leaf_relids_inserted = + bms_add_member(mtstate->mt_leaf_relids_inserted, RelationGetRelid(resultRelationDesc)); + mtstate->has_leaf_changed = true; + } + /* * If this insert is the result of a partition key update that moved the * tuple to a new partition, put this row into the transition NEW TABLE, @@ -1494,6 +1518,14 @@ ldelete:; if (canSetTag) (estate->es_processed)++; + if (resultRelationDesc->rd_rel->relispartition) + { + + mtstate->mt_leaf_relids_deleted = + bms_add_member(mtstate->mt_leaf_relids_deleted, RelationGetRelid(resultRelationDesc)); + mtstate->has_leaf_changed = true; + } + /* Tell caller that the delete actually happened. */ if (tupleDeleted) *tupleDeleted = true; @@ -2136,6 +2168,13 @@ lreplace:; if (canSetTag) (estate->es_processed)++; + if (resultRelationDesc->rd_rel->relispartition) + { + mtstate->mt_leaf_relids_updated = + bms_add_member(mtstate->mt_leaf_relids_updated, RelationGetRelid(resultRelationDesc)); + mtstate->has_leaf_changed = true; + } + /* AFTER ROW UPDATE Triggers */ /* GPDB: AO and AOCO tables don't support triggers */ if (!RelationIsNonblockRelation(resultRelationDesc)) @@ -2964,6 +3003,17 @@ ExecModifyTable(PlanState *pstate) node->mt_done = true; + /* + * For SINGLENODE mode or we are entry db, we could not use extend + * libpq to send message because we actually already on kind of QD + * role. + * Process modified relations here instead of EndModifiyTable(). + * It's too late to do there because we update materialized views + * when executor end. + */ + if (IS_QD_OR_SINGLENODE()) + notify_modified_relations_local(node); + return NULL; } @@ -3050,6 +3100,11 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags) mtstate->ps.state = estate; mtstate->ps.ExecProcNode = ExecModifyTable; + mtstate->mt_leaf_relids_inserted = NULL; + mtstate->mt_leaf_relids_updated = NULL; + mtstate->mt_leaf_relids_deleted = NULL; + mtstate->has_leaf_changed = false; + mtstate->operation = operation; mtstate->canSetTag = node->canSetTag; mtstate->mt_done = false; @@ -3654,6 +3709,70 @@ ExecEndModifyTable(ModifyTableState *node) * shut down subplan */ ExecEndNode(outerPlanState(node)); + + /* Notify modified leaf relids to QD */ + if (GP_ROLE_EXECUTE == Gp_role && node->has_leaf_changed) + notify_modified_relations_to_QD(node); +} + +/* + * notify_modified_relations_local + * For SINGLENODE or we are entry db, update the modified relids on local. + * To keep consistent, we set the extend protocol data which will be processed + * uniformly later at the end of exetuor run. + */ +static void +notify_modified_relations_local(ModifyTableState *node) +{ + Assert(epd); + + if (!bms_is_empty(node->mt_leaf_relids_inserted)) + epd_add_subtag_data(EP_TAG_I, node->mt_leaf_relids_inserted); + + if (!bms_is_empty(node->mt_leaf_relids_updated)) + epd_add_subtag_data(EP_TAG_U, node->mt_leaf_relids_updated); + + if (!bms_is_empty(node->mt_leaf_relids_deleted)) + epd_add_subtag_data(EP_TAG_D, node->mt_leaf_relids_deleted); +} + +static void +epd_add_subtag_data(ExtendProtocolSubTag subtag, Bitmapset *relids) +{ + Bitmapset *data = (Bitmapset *) list_nth(epd->subtagdata, subtag); + + data = bms_union(data, relids); + list_nth_replace(epd->subtagdata, subtag, data); + /* Mark subtag to be consumed. */ + epd->consumed_bitmap |= 1 << subtag; +} + +static void +send_subtag(StringInfoData *buf, ExtendProtocolSubTag subtag, Bitmapset* relids) +{ + int relid = -1; + + /* No need to send. */ + if (bms_is_empty(relids)) + return; + + pq_sendint32(buf, subtag); /* subtag */ + pq_sendint32(buf, bms_num_members(relids)); /* number of relid */ + while ((relid = bms_next_member(relids, relid)) >= 0) + pq_sendint32(buf, relid); /* each relid */ +} + +static void +notify_modified_relations_to_QD(ModifyTableState *node) +{ + StringInfoData buf; + pq_beginmessage(&buf, PQExtendProtocol); + send_subtag(&buf, EP_TAG_I, node->mt_leaf_relids_inserted); + send_subtag(&buf, EP_TAG_U, node->mt_leaf_relids_updated); + send_subtag(&buf, EP_TAG_D, node->mt_leaf_relids_deleted); + pq_sendint32(&buf, EP_TAG_MAX); /* Finish this run. */ + pq_endmessage(&buf); + pq_flush(); /* Flush to notify QD in time. */ } void diff --git a/src/include/cdb/cdbdisp_query.h b/src/include/cdb/cdbdisp_query.h index 34c1deea4d8..2d8651cf762 100644 --- a/src/include/cdb/cdbdisp_query.h +++ b/src/include/cdb/cdbdisp_query.h @@ -114,4 +114,31 @@ extern void CdbDispatchCopyEnd(struct CdbCopy *cdbCopy); extern ParamListInfo deserializeExternParams(struct SerializedParams *sparams); +/* + * Extended protocol for libpq. + */ +#define PQExtendProtocol 'e' + +typedef enum +{ + EP_TAG_I, /* insert */ + EP_TAG_U, /* update */ + EP_TAG_D, /* delete */ + EP_TAG_MAX, /* the last, new added one should be put before this. */ +} ExtendProtocolSubTag; + +typedef struct ExtendProtocolDataStore +{ + List *subtagdata; /* capacity of EP_TAG_MAX */ + int consumed_bitmap; /* bitmap to indentify subtag consumed status */ +} ExtendProtocolDataStore; + +typedef ExtendProtocolDataStore* ExtendProtocolData; + +extern ExtendProtocolData epd; + +extern void InitExtendProtocolData(void); + +extern void ConsumeExtendProtocolData(ExtendProtocolSubTag subtag, void *consume_p); + #endif /* CDBDISP_QUERY_H */ diff --git a/src/include/cdb/cdbpq.h b/src/include/cdb/cdbpq.h index a995d0c2a05..8ef768ede4f 100644 --- a/src/include/cdb/cdbpq.h +++ b/src/include/cdb/cdbpq.h @@ -25,4 +25,9 @@ extern PGcmdQueueEntry *pqAllocCmdQueueEntry(PGconn *conn); extern void pqRecycleCmdQueueEntry(PGconn *conn, PGcmdQueueEntry *entry); extern void pqAppendCmdQueueEntry(PGconn *conn, PGcmdQueueEntry *entry); +/* + * Handle extend protocol aside from upstream. + */ +extern bool HandleExtendProtocol(PGconn *conn); + #endif diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h index 9f8a5997e74..422d5c34742 100644 --- a/src/include/nodes/execnodes.h +++ b/src/include/nodes/execnodes.h @@ -1349,6 +1349,12 @@ typedef struct ModifyTableState /* controls transition table population for INSERT...ON CONFLICT UPDATE */ struct TransitionCaptureState *mt_oc_transition_capture; + + /* Record modified leaf relation(s) */ + bool has_leaf_changed; + Bitmapset *mt_leaf_relids_inserted; + Bitmapset *mt_leaf_relids_updated; + Bitmapset *mt_leaf_relids_deleted; } ModifyTableState; /* ---------------- diff --git a/src/interfaces/libpq/fe-protocol3.c b/src/interfaces/libpq/fe-protocol3.c index e946eb3ebb2..9d74dd0e39d 100644 --- a/src/interfaces/libpq/fe-protocol3.c +++ b/src/interfaces/libpq/fe-protocol3.c @@ -39,6 +39,7 @@ #include "libpq-int.h" #include "mb/pg_wchar.h" #include "port/pg_bswap.h" +#include "cdb/cdbpq.h" /* * This macro lists the backend message types that could be "long" (more @@ -51,7 +52,7 @@ #define VALID_LONG_MESSAGE_TYPE(id) \ ((id) == 'T' || (id) == 'D' || (id) == 'd' || (id) == 'V' || \ (id) == 'E' || (id) == 'N' || (id) == 'A' || (id) == 'Y' || \ - (id) == 'y' || (id) == 'o') + (id) == 'y' || (id) == 'o' || (id) == 'e') static void handleSyncLoss(PGconn *conn, char id, int msgLength); @@ -475,6 +476,17 @@ pqParseInput3(PGconn *conn) */ break; #ifndef FRONTEND + case 'e': + if (conn->result == NULL) + { + conn->result = PQmakeEmptyPGresult(conn, PGRES_COMMAND_OK); + if (!conn->result) + return; + } + if (!HandleExtendProtocol(conn)) + return; + conn->inCursor = conn->inStart + 5 + msgLength; + break; case 'j': /* * QE COPY reports number of rejected rows to the QD COPY diff --git a/src/test/regress/expected/aqumv.out b/src/test/regress/expected/aqumv.out index a0977a65358..eabb56c0255 100644 --- a/src/test/regress/expected/aqumv.out +++ b/src/test/regress/expected/aqumv.out @@ -3181,7 +3181,7 @@ create materialized view mv_par1 as select count(*) from par_1_prt_1; create materialized view mv_par1_1 as select count(*) from par_1_prt_1_2_prt_1; create materialized view mv_par1_2 as select count(*) from par_1_prt_1_2_prt_2; create materialized view mv_par2 as select count(*) from par_1_prt_2; -create materialized view mv_par2_2 as select count(*) from par_1_prt_2_2_prt_1; +create materialized view mv_par2_1 as select count(*) from par_1_prt_2_2_prt_1; create materialized view mv_par_prune as select count(*) from par where b = 1; set enable_answer_query_using_materialized_views = on; explain(costs off, verbose) @@ -3239,6 +3239,73 @@ reset enable_partition_pruning; -- -- End of test partitioned tables -- +begin; +insert into par values(1, 1, 1), (1, 1, 2); +explain(costs off, verbose) +select count(*) from par_1_prt_2; + QUERY PLAN +---------------------------------------------------------------------------------- + Gather Motion 3:1 (slice1; segments: 3) + Output: count + -> Seq Scan on aqumv.mv_par2 + Output: count + Settings: enable_answer_query_using_materialized_views = 'on', optimizer = 'off' + Optimizer: Postgres query optimizer +(6 rows) + +insert into par_1_prt_1_2_prt_1 values (1, 1, 1); +explain(costs off, verbose) +select count(*) from par_1_prt_2; + QUERY PLAN +---------------------------------------------------------------------------------- + Gather Motion 3:1 (slice1; segments: 3) + Output: count + -> Seq Scan on aqumv.mv_par2 + Output: count + Settings: enable_answer_query_using_materialized_views = 'on', optimizer = 'off' + Optimizer: Postgres query optimizer +(6 rows) + +delete from par_1_prt_1_2_prt_1; +explain(costs off, verbose) +select count(*) from par_1_prt_2; + QUERY PLAN +---------------------------------------------------------------------------------- + Gather Motion 3:1 (slice1; segments: 3) + Output: count + -> Seq Scan on aqumv.mv_par2 + Output: count + Settings: enable_answer_query_using_materialized_views = 'on', optimizer = 'off' + Optimizer: Postgres query optimizer +(6 rows) + +update par set c = 2 where b = 1 and c = 1; +explain(costs off, verbose) +select count(*) from par_1_prt_2; + QUERY PLAN +---------------------------------------------------------------------------------- + Gather Motion 3:1 (slice1; segments: 3) + Output: count + -> Seq Scan on aqumv.mv_par2 + Output: count + Settings: enable_answer_query_using_materialized_views = 'on', optimizer = 'off' + Optimizer: Postgres query optimizer +(6 rows) + +update par set c = 2, a = 2 where b = 1 and c = 1; +explain(costs off, verbose) +select count(*) from par_1_prt_2; + QUERY PLAN +---------------------------------------------------------------------------------- + Gather Motion 3:1 (slice1; segments: 3) + Output: count + -> Seq Scan on aqumv.mv_par2 + Output: count + Settings: enable_answer_query_using_materialized_views = 'on', optimizer = 'off' + Optimizer: Postgres query optimizer +(6 rows) + +abort; reset optimizer; reset enable_answer_query_using_materialized_views; -- start_ignore diff --git a/src/test/regress/expected/matview_data.out b/src/test/regress/expected/matview_data.out index ce96e86562e..281f89f4c3f 100644 --- a/src/test/regress/expected/matview_data.out +++ b/src/test/regress/expected/matview_data.out @@ -454,7 +454,7 @@ HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sur create materialized view mv_par2 as select * from par_1_prt_2; NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column(s) named 'a' as the Apache Cloudberry data distribution key for this table. HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. -create materialized view mv_par2_2 as select * from par_1_prt_2_2_prt_1; +create materialized view mv_par2_1 as select * from par_1_prt_2_2_prt_1; NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column(s) named 'a' as the Apache Cloudberry data distribution key for this table. HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. select mvname, datastatus from gp_matview_aux where mvname like 'mv_par%'; @@ -465,7 +465,7 @@ select mvname, datastatus from gp_matview_aux where mvname like 'mv_par%'; mv_par1_1 | u mv_par1_2 | u mv_par2 | u - mv_par2_2 | u + mv_par2_1 | u (6 rows) insert into par_1_prt_1 values (1, 1, 1); @@ -473,11 +473,11 @@ insert into par_1_prt_1 values (1, 1, 1); select mvname, datastatus from gp_matview_aux where mvname like 'mv_par%'; mvname | datastatus -----------+------------ + mv_par1_2 | u mv_par2 | u - mv_par2_2 | u - mv_par1 | i + mv_par2_1 | u mv_par1_1 | i - mv_par1_2 | i + mv_par1 | i mv_par | i (6 rows) @@ -486,12 +486,12 @@ insert into par values (1, 2, 2); select mvname, datastatus from gp_matview_aux where mvname like 'mv_par%'; mvname | datastatus -----------+------------ - mv_par1 | i + mv_par1_2 | u + mv_par2_1 | u mv_par1_1 | i - mv_par1_2 | i + mv_par1 | i mv_par | i - mv_par2 | i - mv_par2_2 | i + mv_par2 | u (6 rows) refresh materialized view mv_par; @@ -499,7 +499,7 @@ refresh materialized view mv_par1; refresh materialized view mv_par1_1; refresh materialized view mv_par1_2; refresh materialized view mv_par2; -refresh materialized view mv_par2_2; +refresh materialized view mv_par2_1; begin; insert into par_1_prt_2_2_prt_1 values (1, 2, 1); -- mv_par1* should not be updated @@ -509,7 +509,7 @@ select mvname, datastatus from gp_matview_aux where mvname like 'mv_par%'; mv_par1 | u mv_par1_1 | u mv_par1_2 | u - mv_par2_2 | i + mv_par2_1 | i mv_par2 | i mv_par | i (6 rows) @@ -524,7 +524,7 @@ select mvname, datastatus from gp_matview_aux where mvname like 'mv_par%'; mv_par1 | u mv_par1_1 | u mv_par1_2 | u - mv_par2_2 | e + mv_par2_1 | e mv_par2 | e mv_par | e (6 rows) @@ -538,7 +538,7 @@ select mvname, datastatus from gp_matview_aux where mvname like 'mv_par%'; mv_par1 | u mv_par1_1 | u mv_par1_2 | u - mv_par2_2 | e + mv_par2_1 | e mv_par2 | e mv_par | e (6 rows) @@ -548,14 +548,14 @@ refresh materialized view mv_par1; refresh materialized view mv_par1_1; refresh materialized view mv_par1_2; refresh materialized view mv_par2; -refresh materialized view mv_par2_2; +refresh materialized view mv_par2_1; vacuum full par_1_prt_1_2_prt_1; select mvname, datastatus from gp_matview_aux where mvname like 'mv_par%'; mvname | datastatus -----------+------------ mv_par1_2 | u mv_par2 | u - mv_par2_2 | u + mv_par2_1 | u mv_par1_1 | r mv_par1 | r mv_par | r @@ -566,7 +566,7 @@ refresh materialized view mv_par1; refresh materialized view mv_par1_1; refresh materialized view mv_par1_2; refresh materialized view mv_par2; -refresh materialized view mv_par2_2; +refresh materialized view mv_par2_1; vacuum full par; -- all should be updated. select mvname, datastatus from gp_matview_aux where mvname like 'mv_par%'; @@ -574,7 +574,7 @@ select mvname, datastatus from gp_matview_aux where mvname like 'mv_par%'; -----------+------------ mv_par2 | r mv_par | r - mv_par2_2 | r + mv_par2_1 | r mv_par1_2 | r mv_par1 | r mv_par1_1 | r @@ -585,7 +585,7 @@ refresh materialized view mv_par1; refresh materialized view mv_par1_1; refresh materialized view mv_par1_2; refresh materialized view mv_par2; -refresh materialized view mv_par2_2; +refresh materialized view mv_par2_1; begin; create table par_1_prt_1_2_prt_3 partition of par_1_prt_1 for values from (3) to (4); NOTICE: table has parent, setting distribution columns to match parent table @@ -596,7 +596,7 @@ select mvname, datastatus from gp_matview_aux where mvname like 'mv_par%'; mv_par1_1 | u mv_par1_2 | u mv_par2 | u - mv_par2_2 | u + mv_par2_1 | u mv_par1 | e mv_par | e (6 rows) @@ -613,7 +613,7 @@ select mvname, datastatus from gp_matview_aux where mvname like 'mv_par%'; mvname | datastatus -----------+------------ mv_par2 | u - mv_par2_2 | u + mv_par2_1 | u mv_par | e (3 rows) @@ -627,7 +627,7 @@ select mvname, datastatus from gp_matview_aux where mvname like 'mv_par%'; mv_par1_1 | u mv_par1_2 | u mv_par2 | u - mv_par2_2 | u + mv_par2_1 | u mv_par1 | e mv_par | e (6 rows) @@ -645,12 +645,177 @@ select mvname, datastatus from gp_matview_aux where mvname like 'mv_par%'; mv_par1_1 | u mv_par1_2 | u mv_par2 | u - mv_par2_2 | u + mv_par2_1 | u mv_par1 | e mv_par | e (6 rows) abort; +-- +-- Maintain materialized views on partitioned tables from bottom to up. +-- +insert into par values(1, 1, 1), (1, 1, 2), (2, 2, 1), (2, 2, 2); +refresh materialized view mv_par; +refresh materialized view mv_par1; +refresh materialized view mv_par1_1; +refresh materialized view mv_par1_2; +refresh materialized view mv_par2; +refresh materialized view mv_par2_1; +begin; +select mvname, datastatus from gp_matview_aux where mvname like 'mv_par%'; + mvname | datastatus +-----------+------------ + mv_par | u + mv_par1 | u + mv_par1_1 | u + mv_par1_2 | u + mv_par2 | u + mv_par2_1 | u +(6 rows) + +insert into par values(1, 1, 1), (1, 1, 2); +select mvname, datastatus from gp_matview_aux where mvname like 'mv_par%'; + mvname | datastatus +-----------+------------ + mv_par2 | u + mv_par2_1 | u + mv_par1_1 | i + mv_par1 | i + mv_par | i + mv_par1_2 | i +(6 rows) + +abort; +begin; +select mvname, datastatus from gp_matview_aux where mvname like 'mv_par%'; + mvname | datastatus +-----------+------------ + mv_par | u + mv_par1 | u + mv_par1_1 | u + mv_par1_2 | u + mv_par2 | u + mv_par2_1 | u +(6 rows) + +insert into par_1_prt_2_2_prt_1 values(2, 2, 1); +select mvname, datastatus from gp_matview_aux where mvname like 'mv_par%'; + mvname | datastatus +-----------+------------ + mv_par1 | u + mv_par1_1 | u + mv_par1_2 | u + mv_par2_1 | i + mv_par2 | i + mv_par | i +(6 rows) + +abort; +begin; +select mvname, datastatus from gp_matview_aux where mvname like 'mv_par%'; + mvname | datastatus +-----------+------------ + mv_par | u + mv_par1 | u + mv_par1_1 | u + mv_par1_2 | u + mv_par2 | u + mv_par2_1 | u +(6 rows) + +delete from par where b = 2 and c = 1; +select mvname, datastatus from gp_matview_aux where mvname like 'mv_par%'; + mvname | datastatus +-----------+------------ + mv_par1 | u + mv_par1_1 | u + mv_par1_2 | u + mv_par2_1 | e + mv_par2 | e + mv_par | e +(6 rows) + +abort; +begin; +select mvname, datastatus from gp_matview_aux where mvname like 'mv_par%'; + mvname | datastatus +-----------+------------ + mv_par | u + mv_par1 | u + mv_par1_1 | u + mv_par1_2 | u + mv_par2 | u + mv_par2_1 | u +(6 rows) + +delete from par_1_prt_1_2_prt_2; +select mvname, datastatus from gp_matview_aux where mvname like 'mv_par%'; + mvname | datastatus +-----------+------------ + mv_par1_1 | u + mv_par2 | u + mv_par2_1 | u + mv_par1_2 | e + mv_par1 | e + mv_par | e +(6 rows) + +abort; +-- Across partition update. +begin; +select mvname, datastatus from gp_matview_aux where mvname like 'mv_par%'; + mvname | datastatus +-----------+------------ + mv_par | u + mv_par1 | u + mv_par1_1 | u + mv_par1_2 | u + mv_par2 | u + mv_par2_1 | u +(6 rows) + +update par set c = 2 where b = 1 and c = 1; +select mvname, datastatus from gp_matview_aux where mvname like 'mv_par%'; + mvname | datastatus +-----------+------------ + mv_par2 | u + mv_par2_1 | u + mv_par1_2 | i + mv_par1_1 | e + mv_par1 | e + mv_par | e +(6 rows) + +abort; +-- Split Update with acrosss partition update. +begin; +select mvname, datastatus from gp_matview_aux where mvname like 'mv_par%'; + mvname | datastatus +-----------+------------ + mv_par | u + mv_par1 | u + mv_par1_1 | u + mv_par1_2 | u + mv_par2 | u + mv_par2_1 | u +(6 rows) + +update par set c = 2, a = 2 where b = 1 and c = 1; +select mvname, datastatus from gp_matview_aux where mvname like 'mv_par%'; + mvname | datastatus +-----------+------------ + mv_par2 | u + mv_par2_1 | u + mv_par1_2 | i + mv_par1_1 | e + mv_par1 | e + mv_par | e +(6 rows) + +abort; +-- +-- End of Maintain materialized views on partitioned tables from bottom to up. +-- --start_ignore drop schema matview_data_schema cascade; NOTICE: drop cascades to 3 other objects diff --git a/src/test/regress/sql/aqumv.sql b/src/test/regress/sql/aqumv.sql index 244c2a27997..23580292885 100644 --- a/src/test/regress/sql/aqumv.sql +++ b/src/test/regress/sql/aqumv.sql @@ -809,7 +809,7 @@ create materialized view mv_par1 as select count(*) from par_1_prt_1; create materialized view mv_par1_1 as select count(*) from par_1_prt_1_2_prt_1; create materialized view mv_par1_2 as select count(*) from par_1_prt_1_2_prt_2; create materialized view mv_par2 as select count(*) from par_1_prt_2; -create materialized view mv_par2_2 as select count(*) from par_1_prt_2_2_prt_1; +create materialized view mv_par2_1 as select count(*) from par_1_prt_2_2_prt_1; create materialized view mv_par_prune as select count(*) from par where b = 1; set enable_answer_query_using_materialized_views = on; @@ -830,6 +830,28 @@ reset enable_partition_pruning; -- End of test partitioned tables -- +begin; +insert into par values(1, 1, 1), (1, 1, 2); +explain(costs off, verbose) +select count(*) from par_1_prt_2; + +insert into par_1_prt_1_2_prt_1 values (1, 1, 1); +explain(costs off, verbose) +select count(*) from par_1_prt_2; + +delete from par_1_prt_1_2_prt_1; +explain(costs off, verbose) +select count(*) from par_1_prt_2; + +update par set c = 2 where b = 1 and c = 1; +explain(costs off, verbose) +select count(*) from par_1_prt_2; + +update par set c = 2, a = 2 where b = 1 and c = 1; +explain(costs off, verbose) +select count(*) from par_1_prt_2; +abort; + reset optimizer; reset enable_answer_query_using_materialized_views; -- start_ignore diff --git a/src/test/regress/sql/matview_data.sql b/src/test/regress/sql/matview_data.sql index b2f6ce60603..2f829b54371 100644 --- a/src/test/regress/sql/matview_data.sql +++ b/src/test/regress/sql/matview_data.sql @@ -168,7 +168,7 @@ create materialized view mv_par1 as select * from par_1_prt_1; create materialized view mv_par1_1 as select * from par_1_prt_1_2_prt_1; create materialized view mv_par1_2 as select * from par_1_prt_1_2_prt_2; create materialized view mv_par2 as select * from par_1_prt_2; -create materialized view mv_par2_2 as select * from par_1_prt_2_2_prt_1; +create materialized view mv_par2_1 as select * from par_1_prt_2_2_prt_1; select mvname, datastatus from gp_matview_aux where mvname like 'mv_par%'; insert into par_1_prt_1 values (1, 1, 1); -- mv_par1* shoud be updated @@ -182,7 +182,7 @@ refresh materialized view mv_par1; refresh materialized view mv_par1_1; refresh materialized view mv_par1_2; refresh materialized view mv_par2; -refresh materialized view mv_par2_2; +refresh materialized view mv_par2_1; begin; insert into par_1_prt_2_2_prt_1 values (1, 2, 1); -- mv_par1* should not be updated @@ -203,7 +203,7 @@ refresh materialized view mv_par1; refresh materialized view mv_par1_1; refresh materialized view mv_par1_2; refresh materialized view mv_par2; -refresh materialized view mv_par2_2; +refresh materialized view mv_par2_1; vacuum full par_1_prt_1_2_prt_1; select mvname, datastatus from gp_matview_aux where mvname like 'mv_par%'; @@ -212,7 +212,7 @@ refresh materialized view mv_par1; refresh materialized view mv_par1_1; refresh materialized view mv_par1_2; refresh materialized view mv_par2; -refresh materialized view mv_par2_2; +refresh materialized view mv_par2_1; vacuum full par; -- all should be updated. select mvname, datastatus from gp_matview_aux where mvname like 'mv_par%'; @@ -222,7 +222,7 @@ refresh materialized view mv_par1; refresh materialized view mv_par1_1; refresh materialized view mv_par1_2; refresh materialized view mv_par2; -refresh materialized view mv_par2_2; +refresh materialized view mv_par2_1; begin; create table par_1_prt_1_2_prt_3 partition of par_1_prt_1 for values from (3) to (4); -- update status when partition of @@ -248,6 +248,58 @@ alter table par_1_prt_1 attach partition new_par for values from (4) to (5); select mvname, datastatus from gp_matview_aux where mvname like 'mv_par%'; abort; +-- +-- Maintain materialized views on partitioned tables from bottom to up. +-- +insert into par values(1, 1, 1), (1, 1, 2), (2, 2, 1), (2, 2, 2); +refresh materialized view mv_par; +refresh materialized view mv_par1; +refresh materialized view mv_par1_1; +refresh materialized view mv_par1_2; +refresh materialized view mv_par2; +refresh materialized view mv_par2_1; +begin; +select mvname, datastatus from gp_matview_aux where mvname like 'mv_par%'; +insert into par values(1, 1, 1), (1, 1, 2); +select mvname, datastatus from gp_matview_aux where mvname like 'mv_par%'; +abort; + +begin; +select mvname, datastatus from gp_matview_aux where mvname like 'mv_par%'; +insert into par_1_prt_2_2_prt_1 values(2, 2, 1); +select mvname, datastatus from gp_matview_aux where mvname like 'mv_par%'; +abort; + +begin; +select mvname, datastatus from gp_matview_aux where mvname like 'mv_par%'; +delete from par where b = 2 and c = 1; +select mvname, datastatus from gp_matview_aux where mvname like 'mv_par%'; +abort; + +begin; +select mvname, datastatus from gp_matview_aux where mvname like 'mv_par%'; +delete from par_1_prt_1_2_prt_2; +select mvname, datastatus from gp_matview_aux where mvname like 'mv_par%'; +abort; + +-- Across partition update. +begin; +select mvname, datastatus from gp_matview_aux where mvname like 'mv_par%'; +update par set c = 2 where b = 1 and c = 1; +select mvname, datastatus from gp_matview_aux where mvname like 'mv_par%'; +abort; + +-- Split Update with acrosss partition update. +begin; +select mvname, datastatus from gp_matview_aux where mvname like 'mv_par%'; +update par set c = 2, a = 2 where b = 1 and c = 1; +select mvname, datastatus from gp_matview_aux where mvname like 'mv_par%'; +abort; +-- +-- End of Maintain materialized views on partitioned tables from bottom to up. +-- + + --start_ignore drop schema matview_data_schema cascade; --end_ignore diff --git a/src/test/singlenode_regress/expected/matview_data.out b/src/test/singlenode_regress/expected/matview_data.out index 242bbc95e32..4139b96f5f3 100644 --- a/src/test/singlenode_regress/expected/matview_data.out +++ b/src/test/singlenode_regress/expected/matview_data.out @@ -403,6 +403,390 @@ select mvname, datastatus from gp_matview_aux where mvname in ('tri_mv1', 'tri_m drop trigger trigger_insert_tri_t2 on tri_t1; drop function trigger_insert_tri_t2; +-- test partitioned tables +create table par(a int, b int, c int) partition by range(b) + subpartition by range(c) subpartition template (start (1) end (3) every (1)) + (start(1) end(3) every(1)); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'a' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. +insert into par values(1, 1, 1), (1, 1, 2), (2, 2, 1), (2, 2, 2); +create materialized view mv_par as select * from par; +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column(s) named 'a' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. +create materialized view mv_par1 as select * from par_1_prt_1; +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column(s) named 'a' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. +create materialized view mv_par1_1 as select * from par_1_prt_1_2_prt_1; +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column(s) named 'a' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. +create materialized view mv_par1_2 as select * from par_1_prt_1_2_prt_2; +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column(s) named 'a' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. +create materialized view mv_par2 as select * from par_1_prt_2; +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column(s) named 'a' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. +create materialized view mv_par2_1 as select * from par_1_prt_2_2_prt_1; +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column(s) named 'a' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. +select mvname, datastatus from gp_matview_aux where mvname like 'mv_par%'; + mvname | datastatus +-----------+------------ + mv_par | u + mv_par1 | u + mv_par1_1 | u + mv_par1_2 | u + mv_par2 | u + mv_par2_1 | u +(6 rows) + +insert into par_1_prt_1 values (1, 1, 1); +-- mv_par1* shoud be updated +select mvname, datastatus from gp_matview_aux where mvname like 'mv_par%'; + mvname | datastatus +-----------+------------ + mv_par1_2 | u + mv_par2 | u + mv_par2_1 | u + mv_par1_1 | i + mv_par1 | i + mv_par | i +(6 rows) + +insert into par values (1, 2, 2); +-- mv_par* should be updated +select mvname, datastatus from gp_matview_aux where mvname like 'mv_par%'; + mvname | datastatus +-----------+------------ + mv_par1_2 | u + mv_par2_1 | u + mv_par1_1 | i + mv_par1 | i + mv_par | i + mv_par2 | u +(6 rows) + +refresh materialized view mv_par; +refresh materialized view mv_par1; +refresh materialized view mv_par1_1; +refresh materialized view mv_par1_2; +refresh materialized view mv_par2; +refresh materialized view mv_par2_1; +begin; +insert into par_1_prt_2_2_prt_1 values (1, 2, 1); +-- mv_par1* should not be updated +select mvname, datastatus from gp_matview_aux where mvname like 'mv_par%'; + mvname | datastatus +-----------+------------ + mv_par1 | u + mv_par1_1 | u + mv_par1_2 | u + mv_par2_1 | i + mv_par2 | i + mv_par | i +(6 rows) + +abort; +begin; +truncate par_1_prt_2; +-- mv_par1* should not be updated +select mvname, datastatus from gp_matview_aux where mvname like 'mv_par%'; + mvname | datastatus +-----------+------------ + mv_par1 | u + mv_par1_1 | u + mv_par1_2 | u + mv_par2_1 | e + mv_par2 | e + mv_par | e +(6 rows) + +abort; +truncate par_1_prt_2; +-- mv_par1* should not be updated +select mvname, datastatus from gp_matview_aux where mvname like 'mv_par%'; + mvname | datastatus +-----------+------------ + mv_par1 | u + mv_par1_1 | u + mv_par1_2 | u + mv_par2_1 | e + mv_par2 | e + mv_par | e +(6 rows) + +refresh materialized view mv_par; +refresh materialized view mv_par1; +refresh materialized view mv_par1_1; +refresh materialized view mv_par1_2; +refresh materialized view mv_par2; +refresh materialized view mv_par2_1; +vacuum full par_1_prt_1_2_prt_1; +select mvname, datastatus from gp_matview_aux where mvname like 'mv_par%'; + mvname | datastatus +-----------+------------ + mv_par1_2 | u + mv_par2 | u + mv_par2_1 | u + mv_par1_1 | r + mv_par1 | r + mv_par | r +(6 rows) + +refresh materialized view mv_par; +refresh materialized view mv_par1; +refresh materialized view mv_par1_1; +refresh materialized view mv_par1_2; +refresh materialized view mv_par2; +refresh materialized view mv_par2_1; +vacuum full par; +-- all should be updated. +select mvname, datastatus from gp_matview_aux where mvname like 'mv_par%'; + mvname | datastatus +-----------+------------ + mv_par2 | r + mv_par | r + mv_par2_1 | r + mv_par1_2 | r + mv_par1 | r + mv_par1_1 | r +(6 rows) + +refresh materialized view mv_par; +refresh materialized view mv_par1; +refresh materialized view mv_par1_1; +refresh materialized view mv_par1_2; +refresh materialized view mv_par2; +refresh materialized view mv_par2_1; +begin; +create table par_1_prt_1_2_prt_3 partition of par_1_prt_1 for values from (3) to (4); +NOTICE: table has parent, setting distribution columns to match parent table +-- update status when partition of +select mvname, datastatus from gp_matview_aux where mvname like 'mv_par%'; + mvname | datastatus +-----------+------------ + mv_par1_1 | u + mv_par1_2 | u + mv_par2 | u + mv_par2_1 | u + mv_par1 | e + mv_par | e +(6 rows) + +abort; +begin; +drop table par_1_prt_1 cascade; +NOTICE: drop cascades to 3 other objects +DETAIL: drop cascades to materialized view mv_par1_1 +drop cascades to materialized view mv_par1_2 +drop cascades to materialized view mv_par1 +-- update status when drop table +select mvname, datastatus from gp_matview_aux where mvname like 'mv_par%'; + mvname | datastatus +-----------+------------ + mv_par2 | u + mv_par2_1 | u + mv_par | e +(3 rows) + +abort; +begin; +alter table par_1_prt_1 detach partition par_1_prt_1_2_prt_1; +-- update status when detach +select mvname, datastatus from gp_matview_aux where mvname like 'mv_par%'; + mvname | datastatus +-----------+------------ + mv_par1_1 | u + mv_par1_2 | u + mv_par2 | u + mv_par2_1 | u + mv_par1 | e + mv_par | e +(6 rows) + +abort; +begin; +create table new_par(a int, b int, c int); +NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'a' as the Apache Cloudberry data distribution key for this table. +HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. +-- update status when attach +alter table par_1_prt_1 attach partition new_par for values from (4) to (5); +select mvname, datastatus from gp_matview_aux where mvname like 'mv_par%'; + mvname | datastatus +-----------+------------ + mv_par1_1 | u + mv_par1_2 | u + mv_par2 | u + mv_par2_1 | u + mv_par1 | e + mv_par | e +(6 rows) + +abort; +-- +-- Maintain materialized views on partitioned tables from bottom to up. +-- +insert into par values(1, 1, 1), (1, 1, 2), (2, 2, 1), (2, 2, 2); +refresh materialized view mv_par; +refresh materialized view mv_par1; +refresh materialized view mv_par1_1; +refresh materialized view mv_par1_2; +refresh materialized view mv_par2; +refresh materialized view mv_par2_1; +begin; +select mvname, datastatus from gp_matview_aux where mvname like 'mv_par%'; + mvname | datastatus +-----------+------------ + mv_par | u + mv_par1 | u + mv_par1_1 | u + mv_par1_2 | u + mv_par2 | u + mv_par2_1 | u +(6 rows) + +insert into par values(1, 1, 1), (1, 1, 2); +select mvname, datastatus from gp_matview_aux where mvname like 'mv_par%'; + mvname | datastatus +-----------+------------ + mv_par2 | u + mv_par2_1 | u + mv_par1_1 | i + mv_par1 | i + mv_par | i + mv_par1_2 | i +(6 rows) + +abort; +begin; +select mvname, datastatus from gp_matview_aux where mvname like 'mv_par%'; + mvname | datastatus +-----------+------------ + mv_par | u + mv_par1 | u + mv_par1_1 | u + mv_par1_2 | u + mv_par2 | u + mv_par2_1 | u +(6 rows) + +insert into par_1_prt_2_2_prt_1 values(2, 2, 1); +select mvname, datastatus from gp_matview_aux where mvname like 'mv_par%'; + mvname | datastatus +-----------+------------ + mv_par1 | u + mv_par1_1 | u + mv_par1_2 | u + mv_par2_1 | i + mv_par2 | i + mv_par | i +(6 rows) + +abort; +begin; +select mvname, datastatus from gp_matview_aux where mvname like 'mv_par%'; + mvname | datastatus +-----------+------------ + mv_par | u + mv_par1 | u + mv_par1_1 | u + mv_par1_2 | u + mv_par2 | u + mv_par2_1 | u +(6 rows) + +delete from par where b = 2 and c = 1; +select mvname, datastatus from gp_matview_aux where mvname like 'mv_par%'; + mvname | datastatus +-----------+------------ + mv_par1 | u + mv_par1_1 | u + mv_par1_2 | u + mv_par2_1 | e + mv_par2 | e + mv_par | e +(6 rows) + +abort; +begin; +select mvname, datastatus from gp_matview_aux where mvname like 'mv_par%'; + mvname | datastatus +-----------+------------ + mv_par | u + mv_par1 | u + mv_par1_1 | u + mv_par1_2 | u + mv_par2 | u + mv_par2_1 | u +(6 rows) + +delete from par_1_prt_1_2_prt_2; +select mvname, datastatus from gp_matview_aux where mvname like 'mv_par%'; + mvname | datastatus +-----------+------------ + mv_par1_1 | u + mv_par2 | u + mv_par2_1 | u + mv_par1_2 | e + mv_par1 | e + mv_par | e +(6 rows) + +abort; +-- Across partition update. +begin; +select mvname, datastatus from gp_matview_aux where mvname like 'mv_par%'; + mvname | datastatus +-----------+------------ + mv_par | u + mv_par1 | u + mv_par1_1 | u + mv_par1_2 | u + mv_par2 | u + mv_par2_1 | u +(6 rows) + +update par set c = 2 where b = 1 and c = 1; +select mvname, datastatus from gp_matview_aux where mvname like 'mv_par%'; + mvname | datastatus +-----------+------------ + mv_par2 | u + mv_par2_1 | u + mv_par1_2 | i + mv_par1_1 | e + mv_par1 | e + mv_par | e +(6 rows) + +abort; +-- Split Update with acrosss partition update. +begin; +select mvname, datastatus from gp_matview_aux where mvname like 'mv_par%'; + mvname | datastatus +-----------+------------ + mv_par | u + mv_par1 | u + mv_par1_1 | u + mv_par1_2 | u + mv_par2 | u + mv_par2_1 | u +(6 rows) + +update par set c = 2, a = 2 where b = 1 and c = 1; +select mvname, datastatus from gp_matview_aux where mvname like 'mv_par%'; + mvname | datastatus +-----------+------------ + mv_par2 | u + mv_par2_1 | u + mv_par1_2 | i + mv_par1_1 | e + mv_par1 | e + mv_par | e +(6 rows) + +abort; +-- +-- End of Maintain materialized views on partitioned tables from bottom to up. +-- -- start_ignore drop schema matview_data_schema cascade; NOTICE: drop cascades to 9 other objects diff --git a/src/test/singlenode_regress/sql/matview_data.sql b/src/test/singlenode_regress/sql/matview_data.sql index b9c14fab6ba..1fdb280a4db 100644 --- a/src/test/singlenode_regress/sql/matview_data.sql +++ b/src/test/singlenode_regress/sql/matview_data.sql @@ -156,6 +156,147 @@ select mvname, datastatus from gp_matview_aux where mvname in ('tri_mv1', 'tri_m drop trigger trigger_insert_tri_t2 on tri_t1; drop function trigger_insert_tri_t2; +-- test partitioned tables +create table par(a int, b int, c int) partition by range(b) + subpartition by range(c) subpartition template (start (1) end (3) every (1)) + (start(1) end(3) every(1)); +insert into par values(1, 1, 1), (1, 1, 2), (2, 2, 1), (2, 2, 2); +create materialized view mv_par as select * from par; +create materialized view mv_par1 as select * from par_1_prt_1; +create materialized view mv_par1_1 as select * from par_1_prt_1_2_prt_1; +create materialized view mv_par1_2 as select * from par_1_prt_1_2_prt_2; +create materialized view mv_par2 as select * from par_1_prt_2; +create materialized view mv_par2_1 as select * from par_1_prt_2_2_prt_1; +select mvname, datastatus from gp_matview_aux where mvname like 'mv_par%'; +insert into par_1_prt_1 values (1, 1, 1); +-- mv_par1* shoud be updated +select mvname, datastatus from gp_matview_aux where mvname like 'mv_par%'; +insert into par values (1, 2, 2); +-- mv_par* should be updated +select mvname, datastatus from gp_matview_aux where mvname like 'mv_par%'; + +refresh materialized view mv_par; +refresh materialized view mv_par1; +refresh materialized view mv_par1_1; +refresh materialized view mv_par1_2; +refresh materialized view mv_par2; +refresh materialized view mv_par2_1; +begin; +insert into par_1_prt_2_2_prt_1 values (1, 2, 1); +-- mv_par1* should not be updated +select mvname, datastatus from gp_matview_aux where mvname like 'mv_par%'; +abort; + +begin; +truncate par_1_prt_2; +-- mv_par1* should not be updated +select mvname, datastatus from gp_matview_aux where mvname like 'mv_par%'; +abort; +truncate par_1_prt_2; +-- mv_par1* should not be updated +select mvname, datastatus from gp_matview_aux where mvname like 'mv_par%'; + +refresh materialized view mv_par; +refresh materialized view mv_par1; +refresh materialized view mv_par1_1; +refresh materialized view mv_par1_2; +refresh materialized view mv_par2; +refresh materialized view mv_par2_1; +vacuum full par_1_prt_1_2_prt_1; +select mvname, datastatus from gp_matview_aux where mvname like 'mv_par%'; + +refresh materialized view mv_par; +refresh materialized view mv_par1; +refresh materialized view mv_par1_1; +refresh materialized view mv_par1_2; +refresh materialized view mv_par2; +refresh materialized view mv_par2_1; +vacuum full par; +-- all should be updated. +select mvname, datastatus from gp_matview_aux where mvname like 'mv_par%'; + +refresh materialized view mv_par; +refresh materialized view mv_par1; +refresh materialized view mv_par1_1; +refresh materialized view mv_par1_2; +refresh materialized view mv_par2; +refresh materialized view mv_par2_1; +begin; +create table par_1_prt_1_2_prt_3 partition of par_1_prt_1 for values from (3) to (4); +-- update status when partition of +select mvname, datastatus from gp_matview_aux where mvname like 'mv_par%'; +abort; + +begin; +drop table par_1_prt_1 cascade; +-- update status when drop table +select mvname, datastatus from gp_matview_aux where mvname like 'mv_par%'; +abort; + +begin; +alter table par_1_prt_1 detach partition par_1_prt_1_2_prt_1; +-- update status when detach +select mvname, datastatus from gp_matview_aux where mvname like 'mv_par%'; +abort; + +begin; +create table new_par(a int, b int, c int); +-- update status when attach +alter table par_1_prt_1 attach partition new_par for values from (4) to (5); +select mvname, datastatus from gp_matview_aux where mvname like 'mv_par%'; +abort; + +-- +-- Maintain materialized views on partitioned tables from bottom to up. +-- +insert into par values(1, 1, 1), (1, 1, 2), (2, 2, 1), (2, 2, 2); +refresh materialized view mv_par; +refresh materialized view mv_par1; +refresh materialized view mv_par1_1; +refresh materialized view mv_par1_2; +refresh materialized view mv_par2; +refresh materialized view mv_par2_1; +begin; +select mvname, datastatus from gp_matview_aux where mvname like 'mv_par%'; +insert into par values(1, 1, 1), (1, 1, 2); +select mvname, datastatus from gp_matview_aux where mvname like 'mv_par%'; +abort; + +begin; +select mvname, datastatus from gp_matview_aux where mvname like 'mv_par%'; +insert into par_1_prt_2_2_prt_1 values(2, 2, 1); +select mvname, datastatus from gp_matview_aux where mvname like 'mv_par%'; +abort; + +begin; +select mvname, datastatus from gp_matview_aux where mvname like 'mv_par%'; +delete from par where b = 2 and c = 1; +select mvname, datastatus from gp_matview_aux where mvname like 'mv_par%'; +abort; + +begin; +select mvname, datastatus from gp_matview_aux where mvname like 'mv_par%'; +delete from par_1_prt_1_2_prt_2; +select mvname, datastatus from gp_matview_aux where mvname like 'mv_par%'; +abort; + +-- Across partition update. +begin; +select mvname, datastatus from gp_matview_aux where mvname like 'mv_par%'; +update par set c = 2 where b = 1 and c = 1; +select mvname, datastatus from gp_matview_aux where mvname like 'mv_par%'; +abort; + +-- Split Update with acrosss partition update. +begin; +select mvname, datastatus from gp_matview_aux where mvname like 'mv_par%'; +update par set c = 2, a = 2 where b = 1 and c = 1; +select mvname, datastatus from gp_matview_aux where mvname like 'mv_par%'; +abort; +-- +-- End of Maintain materialized views on partitioned tables from bottom to up. +-- + -- start_ignore drop schema matview_data_schema cascade; -- end_ignore