Skip to main content

reinhardt_query/query/database/
attach_database.rs

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