Skip to main content

reinhardt_query/query/database/
detach_database.rs

1//! DETACH DATABASE statement builder
2//!
3//! This module provides the `DetachDatabaseStatement` type for building SQL DETACH DATABASE queries.
4//! DETACH DATABASE is a SQLite-specific feature for disconnecting previously attached database files.
5
6use crate::types::{DynIden, IntoIden};
7
8use crate::query::traits::{QueryBuilderTrait, QueryStatementBuilder, QueryStatementWriter};
9
10/// DETACH DATABASE statement builder (SQLite-specific)
11///
12/// This struct provides a fluent API for constructing DETACH DATABASE queries.
13/// DETACH DATABASE allows detaching previously attached database files from the current connection.
14///
15/// # Examples
16///
17/// ```rust
18/// use reinhardt_query::prelude::*;
19///
20/// // DETACH DATABASE auxiliary
21/// let query = Query::detach_database()
22///     .name("auxiliary");
23/// ```
24#[derive(Debug, Clone)]
25pub struct DetachDatabaseStatement {
26	pub(crate) database_name: Option<DynIden>,
27}
28
29impl DetachDatabaseStatement {
30	/// Create a new DETACH DATABASE statement
31	///
32	/// # Examples
33	///
34	/// ```rust
35	/// use reinhardt_query::prelude::*;
36	///
37	/// let query = Query::detach_database();
38	/// ```
39	pub fn new() -> Self {
40		Self {
41			database_name: None,
42		}
43	}
44
45	/// Take the ownership of data in the current [`DetachDatabaseStatement`]
46	pub fn take(&mut self) -> Self {
47		Self {
48			database_name: self.database_name.take(),
49		}
50	}
51
52	/// Set the name of the database to detach
53	///
54	/// # Examples
55	///
56	/// ```rust
57	/// use reinhardt_query::prelude::*;
58	///
59	/// let query = Query::detach_database()
60	///     .name("auxiliary");
61	/// ```
62	pub fn name<N>(&mut self, name: N) -> &mut Self
63	where
64		N: IntoIden,
65	{
66		self.database_name = Some(name.into_iden());
67		self
68	}
69}
70
71impl Default for DetachDatabaseStatement {
72	fn default() -> Self {
73		Self::new()
74	}
75}
76
77impl QueryStatementBuilder for DetachDatabaseStatement {
78	fn build_any(&self, query_builder: &dyn QueryBuilderTrait) -> (String, crate::value::Values) {
79		use std::any::Any;
80		if (query_builder as &dyn Any)
81			.downcast_ref::<crate::backend::PostgresQueryBuilder>()
82			.is_some()
83		{
84			panic!("DETACH DATABASE is SQLite-specific and not supported in PostgreSQL");
85		}
86		if (query_builder as &dyn Any)
87			.downcast_ref::<crate::backend::MySqlQueryBuilder>()
88			.is_some()
89		{
90			panic!("DETACH DATABASE is SQLite-specific and not supported in MySQL");
91		}
92		if let Some(sqlite_builder) =
93			(query_builder as &dyn Any).downcast_ref::<crate::backend::SqliteQueryBuilder>()
94		{
95			use crate::backend::QueryBuilder as _;
96			let db_name = self
97				.database_name
98				.as_ref()
99				.expect("DETACH DATABASE requires a database name");
100			// Reuse escape_identifier for proper identifier escaping
101			let escaped_db_name = sqlite_builder.escape_identifier(&db_name.to_string());
102			let sql = format!("DETACH DATABASE {}", escaped_db_name);
103			return (sql, crate::value::Values::new());
104		}
105		if (query_builder as &dyn Any)
106			.downcast_ref::<crate::backend::CockroachDBQueryBuilder>()
107			.is_some()
108		{
109			panic!("DETACH DATABASE is SQLite-specific and not supported in CockroachDB");
110		}
111		panic!("Unsupported query builder type");
112	}
113}
114
115impl QueryStatementWriter for DetachDatabaseStatement {}
116
117#[cfg(test)]
118mod tests {
119	use super::*;
120	use rstest::*;
121
122	#[rstest]
123	fn test_detach_database_new() {
124		let stmt = DetachDatabaseStatement::new();
125		assert!(stmt.database_name.is_none());
126	}
127
128	#[rstest]
129	fn test_detach_database_with_name() {
130		let mut stmt = DetachDatabaseStatement::new();
131		stmt.name("auxiliary");
132		assert_eq!(
133			stmt.database_name.as_ref().unwrap().to_string(),
134			"auxiliary"
135		);
136	}
137
138	#[rstest]
139	fn test_detach_database_take() {
140		let mut stmt = DetachDatabaseStatement::new();
141		stmt.name("auxiliary");
142		let taken = stmt.take();
143		assert!(stmt.database_name.is_none());
144		assert_eq!(
145			taken.database_name.as_ref().unwrap().to_string(),
146			"auxiliary"
147		);
148	}
149
150	#[rstest]
151	fn test_detach_database_default() {
152		let stmt = DetachDatabaseStatement::default();
153		assert!(stmt.database_name.is_none());
154	}
155
156	#[rstest]
157	fn test_detach_database_fluent_api() {
158		let mut stmt = DetachDatabaseStatement::new();
159		let result = stmt.name("test_db");
160		// Verify fluent API returns mutable reference
161		assert_eq!(
162			result.database_name.as_ref().unwrap().to_string(),
163			"test_db"
164		);
165	}
166
167	#[rstest]
168	fn test_detach_database_build_sql() {
169		// Arrange
170		let mut stmt = DetachDatabaseStatement::new();
171		stmt.name("auxiliary");
172
173		// Act
174		let (sql, values) = stmt.build_any(&crate::backend::SqliteQueryBuilder);
175
176		// Assert
177		assert_eq!(sql, r#"DETACH DATABASE "auxiliary""#);
178		assert!(values.0.is_empty());
179	}
180
181	#[rstest]
182	fn test_detach_database_db_name_with_double_quotes() {
183		// Arrange
184		let mut stmt = DetachDatabaseStatement::new();
185		stmt.name(r#"my"db"#);
186
187		// Act
188		let (sql, _) = stmt.build_any(&crate::backend::SqliteQueryBuilder);
189
190		// Assert
191		assert_eq!(sql, r#"DETACH DATABASE "my""db""#);
192	}
193
194	#[rstest]
195	fn test_detach_database_db_name_with_special_chars() {
196		// Arrange
197		let mut stmt = DetachDatabaseStatement::new();
198		stmt.name(r#"test"schema"name"#);
199
200		// Act
201		let (sql, _) = stmt.build_any(&crate::backend::SqliteQueryBuilder);
202
203		// Assert
204		assert_eq!(sql, r#"DETACH DATABASE "test""schema""name""#);
205	}
206}