Skip to content

Commit a5fe8de

Browse files
authored
feat(meta): add materialized view metadata support (#20184)
* feat(meta): add materialized view definition and source index support Persist materialized view definitions under MV table IDs. When commit_table_meta publishes a staged MV, read the source table ID from its TableMeta options and update the source-table-to-MV index in the same transaction. CREATE OR REPLACE replaces the previous MV index membership while retaining superseded definitions for GC. Explicit MV drop removes its definition and source-index membership. Table GC cleans up retained definitions and source-index keys. * refactor mget_mvs_by_source_table_id and remove get_mv_ids_by_source_table_id * refactor mget_mvs_by_source_table_id return, add source_index_seq * add source-table and mv consistency in src/meta/api/src/api_impl/materialized_view_api.rs; add MVDefinition::schema comment in materialized_view.proto * feat(meta): add sync creation mode to materialized view metadata - add immutable MVDefinition.sync_creation - rename schema to logical_schema to distinguish it from TableMeta.schema * when create mv engine table create_table(as_dropped=false) and commit_table_meta do not process mv * split vec * feat(meta): track materialized view source generations Replace the EmptyProto values of SourceTableMVIdent and MVSourceBindingVersionIdent with explicit generation records. SourceTableMVIdent must represent both a durable dependency and the source generation against which an MV was built. With EmptyProto, invalidation could only be represented by deleting the relationship key. MV-invalidating source DDL would therefore require O(N) deletes and would lose the dependency needed by SHOW, refresh, DROP, and GC. MVSourceBinding stores bound_source_generation instead. The relationship remains present after invalidating column DDL, and validity is derived by comparing the bound generation with the current source generation. Refresh can make one MV valid again by updating its relationship to the current generation. MVSourceBindingVersionIdent also cannot use EmptyProto with its KV sequence as the semantic generation. A missing key is observed as generation 0, but its first put receives a nonzero KV sequence. An edge atomically created with bound generation 0 would therefore be stale immediately. Initializing the key in a separate transaction would leave metadata behind when CREATE MV fails and complicate concurrent CREATE operations. MVSourceBindingVersion stores current_source_generation explicitly. This lets the first successful CREATE publish current generation 0 and an edge bound to generation 0 in one transaction. The KV sequence remains internal to the Meta API as a CAS token. Advance the source generation only for DDL that invalidates existing MV bindings: RENAME COLUMN, DROP COLUMN, and MODIFY COLUMN. ADD COLUMN and RENAME TABLE preserve MV binding metadata. Source DROP and UNDROP also preserve the relationship and version records for the same source table identity, while source GC removes them. Split dependency lookup into an unfiltered API for lifecycle operations and a generation-filtered API for query and maintenance paths. Validate the expected source generation during CREATE and document the CREATE, REFRESH, source DDL, DROP, UNDROP, and GC transaction contracts. Update the unreleased v181 protobuf records and compatibility tests.
1 parent 2501da7 commit a5fe8de

36 files changed

Lines changed: 1727 additions & 41 deletions

Cargo.lock

Lines changed: 2 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/common/exception/src/exception_code.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -525,7 +525,7 @@ build_exceptions! {
525525
IllegalUser(2218),
526526
}
527527

528-
// Database and Catalog Management Errors [2301-2317, 2321-2327]
528+
// Database and Catalog Management Errors [2301-2317, 2321-2329]
529529
build_exceptions! {
530530
/// Database already exists
531531
DatabaseAlreadyExists(2301),
@@ -569,6 +569,10 @@ build_exceptions! {
569569
GeneralDbGcFailure(2325),
570570
/// Table snapshot expired
571571
TableSnapshotExpired(2327),
572+
/// Invalid materialized view metadata or state
573+
InvalidMaterializedView(2328),
574+
/// Materialized view already exists
575+
MaterializedViewAlreadyExists(2329),
572576
}
573577

574578
// Stage and Connection Errors [2501-2505, 2510-2512]

src/meta/api/src/api_impl/auto_increment_api_test_suite.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,7 @@ impl AutoIncrementApiTestSuite {
129129
},
130130
table_meta: drop_table_meta(created_on),
131131
as_dropped: true,
132+
materialized_view: None,
132133
table_properties: None,
133134
table_partition: None,
134135
};

src/meta/api/src/api_impl/garbage_collection_api.rs

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,12 @@ use databend_common_meta_app::schema::DroppedId;
3838
use databend_common_meta_app::schema::GcDroppedTableReq;
3939
use databend_common_meta_app::schema::IndexNameIdent;
4040
use databend_common_meta_app::schema::ListIndexesReq;
41+
use databend_common_meta_app::schema::MVDefinitionIdent;
42+
use databend_common_meta_app::schema::MVSourceBindingVersionIdent;
4143
use databend_common_meta_app::schema::ObjectTagIdRef;
4244
use databend_common_meta_app::schema::ObjectTagIdRefIdent;
45+
use databend_common_meta_app::schema::SourceTableMV;
46+
use databend_common_meta_app::schema::SourceTableMVIdent;
4347
use databend_common_meta_app::schema::TableCopiedFileNameIdent;
4448
use databend_common_meta_app::schema::TableId;
4549
use databend_common_meta_app::schema::TableIdHistoryIdent;
@@ -51,6 +55,7 @@ use databend_common_meta_app::schema::TaggableObject;
5155
use databend_common_meta_app::schema::VacuumWatermark;
5256
use databend_common_meta_app::schema::index_id_ident::IndexIdIdent;
5357
use databend_common_meta_app::schema::index_id_to_name_ident::IndexIdToNameIdent;
58+
use databend_common_meta_app::schema::is_materialized_view_engine;
5459
use databend_common_meta_app::schema::table_niv::TableNIV;
5560
use databend_common_meta_app::schema::vacuum_watermark_ident::VacuumWatermarkIdent;
5661
use databend_common_meta_app::tenant::Tenant;
@@ -772,6 +777,45 @@ async fn remove_data_for_dropped_table(
772777
// warn!("{}", err);
773778
// return Ok(Err(err));
774779
// }
780+
// DROP TABLE normally deletes the MV definition. Database GC may collect
781+
// tables without first running the per-table DROP path, so keep this
782+
// deletion idempotent here as well.
783+
if is_materialized_view_engine(&seq_meta.data.engine) {
784+
txn.if_then
785+
.push(txn_del(&MVDefinitionIdent::new(tenant, table_id.table_id)));
786+
match seq_meta.data.materialized_view_source_table_id() {
787+
Ok(source_table_id) => {
788+
txn.if_then.push(txn_del(&SourceTableMVIdent::new_generic(
789+
tenant,
790+
SourceTableMV::new(source_table_id, table_id.table_id),
791+
)));
792+
}
793+
Err(err) => {
794+
warn!(
795+
table_id = table_id.table_id,
796+
error :% = err;
797+
"GC materialized view can not remove its source relationship"
798+
);
799+
}
800+
}
801+
}
802+
803+
let source_mv_prefix = DirName::new(SourceTableMVIdent::new_generic(
804+
tenant,
805+
SourceTableMV::new(table_id.table_id, 0),
806+
));
807+
let source_mvs = kv_api
808+
.list_pb_vec(ListOptions::unlimited(&source_mv_prefix))
809+
.await?;
810+
// The source table or its database is already dropped, so no new MV can
811+
// reference it. Concurrent relationship deletions are idempotent.
812+
txn.if_then
813+
.extend(source_mvs.into_iter().map(|(ident, _)| txn_del(&ident)));
814+
txn.if_then.push(txn_del(&MVSourceBindingVersionIdent::new(
815+
tenant,
816+
table_id.table_id,
817+
)));
818+
775819
txn_delete_exact(txn, table_id, seq_meta.seq);
776820

777821
// Get id -> name mapping

0 commit comments

Comments
 (0)