Skip to main content

reinhardt_query/query/materialized_view/
create_materialized_view.rs

1//! CREATE MATERIALIZED VIEW statement builder
2//!
3//! This module provides the `CreateMaterializedViewStatement` type for building
4//! SQL CREATE MATERIALIZED VIEW queries.
5//!
6//! Note: Materialized views are PostgreSQL and CockroachDB specific features.
7
8use crate::{
9	backend::QueryBuilder,
10	query::SelectStatement,
11	types::{IntoIden, MaterializedViewDef},
12};
13
14use crate::query::traits::{QueryBuilderTrait, QueryStatementBuilder, QueryStatementWriter};
15
16/// CREATE MATERIALIZED VIEW statement builder
17///
18/// This struct provides a fluent API for constructing CREATE MATERIALIZED VIEW queries.
19///
20/// # Examples
21///
22/// ```rust,ignore
23/// use reinhardt_query::prelude::*;
24///
25/// let select = Query::select()
26///     .column(Expr::col("id"))
27///     .column(Expr::col("name"))
28///     .from("users")
29///     .and_where(Expr::col("active").eq(true));
30///
31/// let query = Query::create_materialized_view()
32///     .name("active_users_mv")
33///     .as_select(select)
34///     .with_data(true);
35/// ```
36#[derive(Debug, Clone)]
37pub struct CreateMaterializedViewStatement {
38	pub(crate) def: MaterializedViewDef,
39	pub(crate) select: Option<SelectStatement>,
40}
41
42impl CreateMaterializedViewStatement {
43	/// Create a new CREATE MATERIALIZED VIEW statement
44	pub fn new() -> Self {
45		Self {
46			def: MaterializedViewDef::new(""),
47			select: None,
48		}
49	}
50
51	/// Take the ownership of data in the current statement
52	pub fn take(&mut self) -> Self {
53		Self {
54			def: self.def.clone(),
55			select: self.select.take(),
56		}
57	}
58
59	/// Set the materialized view name
60	///
61	/// # Examples
62	///
63	/// ```rust,ignore
64	/// use reinhardt_query::prelude::*;
65	///
66	/// let query = Query::create_materialized_view()
67	///     .name("active_users_mv");
68	/// ```
69	pub fn name<N>(&mut self, name: N) -> &mut Self
70	where
71		N: IntoIden,
72	{
73		self.def.name = name.into_iden();
74		self
75	}
76
77	/// Set the SELECT statement for the materialized view
78	///
79	/// # Examples
80	///
81	/// ```rust,ignore
82	/// use reinhardt_query::prelude::*;
83	///
84	/// let select = Query::select()
85	///     .column(Expr::col("id"))
86	///     .from("users");
87	///
88	/// let query = Query::create_materialized_view()
89	///     .name("users_mv")
90	///     .as_select(select);
91	/// ```
92	pub fn as_select(&mut self, select: SelectStatement) -> &mut Self {
93		self.select = Some(select);
94		self
95	}
96
97	/// Add IF NOT EXISTS clause
98	///
99	/// # Examples
100	///
101	/// ```rust,ignore
102	/// use reinhardt_query::prelude::*;
103	///
104	/// let query = Query::create_materialized_view()
105	///     .name("users_mv")
106	///     .if_not_exists();
107	/// ```
108	pub fn if_not_exists(&mut self) -> &mut Self {
109		self.def.if_not_exists = true;
110		self
111	}
112
113	/// Set column names for the materialized view
114	///
115	/// # Examples
116	///
117	/// ```rust,ignore
118	/// use reinhardt_query::prelude::*;
119	///
120	/// let query = Query::create_materialized_view()
121	///     .name("users_mv")
122	///     .columns(["user_id", "user_name"]);
123	/// ```
124	pub fn columns<I, C>(&mut self, cols: I) -> &mut Self
125	where
126		I: IntoIterator<Item = C>,
127		C: IntoIden,
128	{
129		for col in cols {
130			self.def.columns.push(col.into_iden());
131		}
132		self
133	}
134
135	/// Set TABLESPACE for the materialized view
136	///
137	/// # Examples
138	///
139	/// ```rust,ignore
140	/// use reinhardt_query::prelude::*;
141	///
142	/// let query = Query::create_materialized_view()
143	///     .name("users_mv")
144	///     .tablespace("pg_default");
145	/// ```
146	pub fn tablespace<T>(&mut self, tablespace: T) -> &mut Self
147	where
148		T: IntoIden,
149	{
150		self.def.tablespace = Some(tablespace.into_iden());
151		self
152	}
153
154	/// Set WITH DATA or WITH NO DATA clause
155	///
156	/// # Examples
157	///
158	/// ```rust,ignore
159	/// use reinhardt_query::prelude::*;
160	///
161	/// // WITH DATA (populate immediately)
162	/// let query = Query::create_materialized_view()
163	///     .name("users_mv")
164	///     .with_data(true);
165	///
166	/// // WITH NO DATA (don't populate)
167	/// let query = Query::create_materialized_view()
168	///     .name("users_mv")
169	///     .with_data(false);
170	/// ```
171	pub fn with_data(&mut self, with_data: bool) -> &mut Self {
172		self.def.with_data = Some(with_data);
173		self
174	}
175}
176
177impl Default for CreateMaterializedViewStatement {
178	fn default() -> Self {
179		Self::new()
180	}
181}
182
183impl QueryStatementBuilder for CreateMaterializedViewStatement {
184	fn build_any(&self, query_builder: &dyn QueryBuilderTrait) -> (String, crate::value::Values) {
185		// Downcast to concrete QueryBuilder type
186		use std::any::Any;
187		if let Some(builder) =
188			(query_builder as &dyn Any).downcast_ref::<crate::backend::PostgresQueryBuilder>()
189		{
190			return builder.build_create_materialized_view(self);
191		}
192		if let Some(builder) =
193			(query_builder as &dyn Any).downcast_ref::<crate::backend::CockroachDBQueryBuilder>()
194		{
195			return builder.build_create_materialized_view(self);
196		}
197		if let Some(_builder) =
198			(query_builder as &dyn Any).downcast_ref::<crate::backend::MySqlQueryBuilder>()
199		{
200			panic!(
201				"MySQL does not support materialized views. Use regular tables with triggers or scheduled queries instead."
202			);
203		}
204		if let Some(_builder) =
205			(query_builder as &dyn Any).downcast_ref::<crate::backend::SqliteQueryBuilder>()
206		{
207			panic!(
208				"SQLite does not support materialized views. Use regular tables with triggers or application-level caching instead."
209			);
210		}
211		panic!("Unsupported query builder type");
212	}
213}
214
215impl QueryStatementWriter for CreateMaterializedViewStatement {}
216
217#[cfg(test)]
218mod tests {
219	use super::*;
220	use rstest::*;
221
222	#[rstest]
223	fn test_create_materialized_view_basic() {
224		let mut stmt = CreateMaterializedViewStatement::new();
225		stmt.name("my_mv");
226		assert_eq!(stmt.def.name.to_string(), "my_mv");
227		assert!(!stmt.def.if_not_exists);
228		assert!(stmt.select.is_none());
229	}
230
231	#[rstest]
232	fn test_create_materialized_view_if_not_exists() {
233		let mut stmt = CreateMaterializedViewStatement::new();
234		stmt.name("my_mv").if_not_exists();
235		assert_eq!(stmt.def.name.to_string(), "my_mv");
236		assert!(stmt.def.if_not_exists);
237	}
238
239	#[rstest]
240	fn test_create_materialized_view_with_data() {
241		let mut stmt = CreateMaterializedViewStatement::new();
242		stmt.name("my_mv").with_data(true);
243		assert_eq!(stmt.def.with_data, Some(true));
244	}
245
246	#[rstest]
247	fn test_create_materialized_view_with_no_data() {
248		let mut stmt = CreateMaterializedViewStatement::new();
249		stmt.name("my_mv").with_data(false);
250		assert_eq!(stmt.def.with_data, Some(false));
251	}
252
253	#[rstest]
254	fn test_create_materialized_view_columns() {
255		let mut stmt = CreateMaterializedViewStatement::new();
256		stmt.name("my_mv").columns(["id", "name", "email"]);
257		assert_eq!(stmt.def.columns.len(), 3);
258		assert_eq!(stmt.def.columns[0].to_string(), "id");
259		assert_eq!(stmt.def.columns[1].to_string(), "name");
260		assert_eq!(stmt.def.columns[2].to_string(), "email");
261	}
262
263	#[rstest]
264	fn test_create_materialized_view_tablespace() {
265		let mut stmt = CreateMaterializedViewStatement::new();
266		stmt.name("my_mv").tablespace("pg_default");
267		assert_eq!(
268			stmt.def.tablespace.as_ref().unwrap().to_string(),
269			"pg_default"
270		);
271	}
272
273	#[rstest]
274	fn test_create_materialized_view_as_select() {
275		let mut stmt = CreateMaterializedViewStatement::new();
276		let select = SelectStatement::new();
277		stmt.name("my_mv").as_select(select);
278		assert!(stmt.select.is_some());
279	}
280
281	#[rstest]
282	fn test_create_materialized_view_all_options() {
283		let mut stmt = CreateMaterializedViewStatement::new();
284		let select = SelectStatement::new();
285		stmt.name("my_mv")
286			.if_not_exists()
287			.columns(["id", "name"])
288			.tablespace("pg_default")
289			.with_data(true)
290			.as_select(select);
291
292		assert_eq!(stmt.def.name.to_string(), "my_mv");
293		assert!(stmt.def.if_not_exists);
294		assert_eq!(stmt.def.columns.len(), 2);
295		assert_eq!(
296			stmt.def.tablespace.as_ref().unwrap().to_string(),
297			"pg_default"
298		);
299		assert_eq!(stmt.def.with_data, Some(true));
300		assert!(stmt.select.is_some());
301	}
302
303	#[rstest]
304	fn test_create_materialized_view_default() {
305		let stmt = CreateMaterializedViewStatement::default();
306		assert_eq!(stmt.def.name.to_string(), "");
307		assert!(!stmt.def.if_not_exists);
308		assert!(stmt.select.is_none());
309	}
310
311	#[rstest]
312	fn test_create_materialized_view_take() {
313		let mut stmt = CreateMaterializedViewStatement::new();
314		stmt.name("my_mv").if_not_exists();
315		let taken = stmt.take();
316		assert_eq!(taken.def.name.to_string(), "my_mv");
317		assert!(taken.def.if_not_exists);
318	}
319
320	#[rstest]
321	fn test_create_materialized_view_chaining() {
322		let mut stmt = CreateMaterializedViewStatement::new();
323		stmt.name("my_mv")
324			.if_not_exists()
325			.columns(["id"])
326			.with_data(true);
327		assert_eq!(stmt.def.name.to_string(), "my_mv");
328		assert!(stmt.def.if_not_exists);
329		assert_eq!(stmt.def.columns.len(), 1);
330		assert_eq!(stmt.def.with_data, Some(true));
331	}
332
333	#[rstest]
334	fn test_create_materialized_view_multiple_columns() {
335		let mut stmt = CreateMaterializedViewStatement::new();
336		stmt.name("my_mv").columns(["id", "name"]);
337		stmt.columns(["email"]);
338		assert_eq!(stmt.def.columns.len(), 3);
339		assert_eq!(stmt.def.columns[0].to_string(), "id");
340		assert_eq!(stmt.def.columns[1].to_string(), "name");
341		assert_eq!(stmt.def.columns[2].to_string(), "email");
342	}
343}