iceberg_rust/materialized_view/mod.rs
1//! Materialized view functionality for Apache Iceberg
2//!
3//! This module provides the implementation of materialized views in Apache Iceberg.
4//! Materialized views maintain a physical copy of query results that can be refreshed
5//! to stay in sync with source tables.
6//!
7//! Key components:
8//! - [`MaterializedView`]: The main type representing a materialized view
9//! - [`Transaction`]: Provides ACID guarantees for view operations
10//! - [`StorageTable`]: Manages the physical storage of materialized data
11//!
12//! # Example
13//! ```no_run
14//! # use iceberg_rust::materialized_view::MaterializedView;
15//! # async fn example() {
16//! let mut view = MaterializedView::builder()
17//! .name("event_counts")
18//! .build()
19//! .await
20//! .unwrap();
21//!
22//! // Perform operations in a transaction
23//! view.new_transaction(None)
24//! .full_refresh(vec![], refresh_state)
25//! .unwrap()
26//! .commit()
27//! .await
28//! .unwrap();
29//! # }
30//! ```
31
32use std::sync::Arc;
33
34use iceberg_rust_spec::spec::{
35 materialized_view_metadata::MaterializedViewMetadata, schema::Schema,
36};
37
38use crate::{
39 catalog::{
40 create::CreateMaterializedViewBuilder, identifier::Identifier, tabular::Tabular, Catalog,
41 },
42 error::Error,
43};
44
45use self::{storage_table::StorageTable, transaction::Transaction as MaterializedViewTransaction};
46
47mod storage_table;
48pub mod transaction;
49
50/// Default postfix for the storage table identifier
51pub static STORAGE_TABLE_POSTFIX: &str = "__storage";
52/// Flag to mark a table as a storage table
53pub static STORAGE_TABLE_FLAG: &str = "materialize.storage_table";
54
55#[derive(Debug, Clone)]
56/// A materialized view in Apache Iceberg that maintains a physical copy of query results
57/// in a storage table. The view provides ACID guarantees and can be refreshed to
58/// stay in sync with changes in the source tables.
59///
60/// The materialized view consists of:
61/// * A view definition (SQL or other representation)
62/// * A storage table containing the materialized data
63/// * Metadata tracking the freshness state relative to source tables
64pub struct MaterializedView {
65 /// Type of the View, either filesystem or metastore.
66 identifier: Identifier,
67 /// Metadata for the iceberg view according to the iceberg view spec
68 metadata: MaterializedViewMetadata,
69 /// Catalog of the table
70 catalog: Arc<dyn Catalog>,
71}
72
73/// Storage table states
74#[derive(Debug)]
75pub enum SourceTableState {
76 /// Data in storage table is fresh
77 Fresh,
78 /// Data in storage table is outdated
79 Outdated(i64),
80 /// Data in storage table is invalid
81 Invalid,
82}
83
84/// Public interface of the table.
85impl MaterializedView {
86 /// Creates a new builder for configuring and creating a materialized view
87 ///
88 /// Returns a `CreateMaterializedViewBuilder` that provides a fluent interface for:
89 /// - Setting the view name and location
90 /// - Configuring view properties
91 /// - Defining the view schema and SQL representation
92 /// - Specifying the catalog and storage table settings
93 pub fn builder() -> CreateMaterializedViewBuilder {
94 CreateMaterializedViewBuilder::default()
95 }
96
97 /// Creates a new materialized view instance with the given identifier, catalog and metadata
98 ///
99 /// # Arguments
100 /// * `identifier` - The unique identifier for this view in the catalog
101 /// * `catalog` - The catalog that will store this view's metadata
102 /// * `metadata` - The view metadata containing schema, properties, etc.
103 ///
104 /// # Returns
105 /// * `Result<MaterializedView, Error>` - The created materialized view or an error
106 pub async fn new(
107 identifier: Identifier,
108 catalog: Arc<dyn Catalog>,
109 metadata: MaterializedViewMetadata,
110 ) -> Result<Self, Error> {
111 Ok(MaterializedView {
112 identifier,
113 metadata,
114 catalog,
115 })
116 }
117 /// Returns the unique identifier for this materialized view in the catalog
118 ///
119 /// The identifier contains the namespace and name that uniquely identify
120 /// this view within its catalog
121 pub fn identifier(&self) -> &Identifier {
122 &self.identifier
123 }
124 /// Returns a reference to the catalog that stores this materialized view
125 ///
126 /// The catalog manages the view's metadata and provides ACID guarantees
127 /// for operations on the view
128 pub fn catalog(&self) -> Arc<dyn Catalog> {
129 self.catalog.clone()
130 }
131 /// Returns the current schema for this materialized view
132 ///
133 /// # Arguments
134 /// * `branch` - Optional branch name to get the schema for. If None, returns the main branch schema
135 ///
136 /// # Returns
137 /// * `Result<&Schema, Error>` - The current schema or an error if it cannot be found
138 pub fn current_schema(&self, branch: Option<&str>) -> Result<&Schema, Error> {
139 self.metadata.current_schema(branch).map_err(Error::from)
140 }
141 /// Returns a reference to this materialized view's metadata
142 ///
143 /// The metadata contains the view's schema, properties, version history,
144 /// and other configuration details as defined by the Apache Iceberg spec
145 pub fn metadata(&self) -> &MaterializedViewMetadata {
146 &self.metadata
147 }
148 /// Creates a new transaction for performing atomic operations on this materialized view
149 ///
150 /// # Arguments
151 /// * `branch` - Optional branch name to perform the transaction on. If None, uses the main branch
152 ///
153 /// # Returns
154 /// A new transaction that can be used to perform multiple operations atomically
155 pub fn new_transaction(&mut self, branch: Option<&str>) -> MaterializedViewTransaction<'_> {
156 MaterializedViewTransaction::new(self, branch)
157 }
158 /// Returns the storage table that contains the materialized data for this view
159 ///
160 /// The storage table is a regular Iceberg table that stores the physical data
161 /// for this materialized view. It is managed internally by the view and should
162 /// not be modified directly.
163 ///
164 /// # Returns
165 /// * `Result<StorageTable, Error>` - The storage table or an error if it cannot be loaded
166 pub async fn storage_table(&self) -> Result<StorageTable, Error> {
167 let identifier = self.metadata().current_version(None)?.storage_table();
168 if let Tabular::Table(table) = self.catalog().load_tabular(&identifier.into()).await? {
169 Ok(StorageTable::new(table))
170 } else {
171 Err(Error::InvalidFormat("storage table".to_string()))
172 }
173 }
174}