reinhardt_query/query/database/
attach_database.rs1use crate::types::{DynIden, IntoIden};
7use crate::value::Value;
8
9use crate::query::traits::{QueryBuilderTrait, QueryStatementBuilder, QueryStatementWriter};
10
11#[derive(Debug, Clone)]
27pub struct AttachDatabaseStatement {
28 pub(crate) file_path: Option<String>,
29 pub(crate) database_name: Option<DynIden>,
30}
31
32impl AttachDatabaseStatement {
33 pub fn new() -> Self {
43 Self {
44 file_path: None,
45 database_name: None,
46 }
47 }
48
49 pub fn take(&mut self) -> Self {
51 Self {
52 file_path: self.file_path.take(),
53 database_name: self.database_name.take(),
54 }
55 }
56
57 pub fn file_path<S>(&mut self, path: S) -> &mut Self
68 where
69 S: Into<String>,
70 {
71 self.file_path = Some(path.into());
72 self
73 }
74
75 pub fn as_name<N>(&mut self, name: N) -> &mut Self
87 where
88 N: IntoIden,
89 {
90 self.database_name = Some(name.into_iden());
91 self
92 }
93}
94
95impl Default for AttachDatabaseStatement {
96 fn default() -> Self {
97 Self::new()
98 }
99}
100
101impl QueryStatementBuilder for AttachDatabaseStatement {
102 fn build_any(&self, query_builder: &dyn QueryBuilderTrait) -> (String, crate::value::Values) {
103 use std::any::Any;
104 if (query_builder as &dyn Any)
105 .downcast_ref::<crate::backend::PostgresQueryBuilder>()
106 .is_some()
107 {
108 panic!("ATTACH DATABASE is SQLite-specific and not supported in PostgreSQL");
109 }
110 if (query_builder as &dyn Any)
111 .downcast_ref::<crate::backend::MySqlQueryBuilder>()
112 .is_some()
113 {
114 panic!("ATTACH DATABASE is SQLite-specific and not supported in MySQL");
115 }
116 if let Some(sqlite_builder) =
117 (query_builder as &dyn Any).downcast_ref::<crate::backend::SqliteQueryBuilder>()
118 {
119 use crate::backend::QueryBuilder as _;
120 let file_path = self
121 .file_path
122 .as_deref()
123 .expect("ATTACH DATABASE requires a file path");
124 let db_name = self
125 .database_name
126 .as_ref()
127 .expect("ATTACH DATABASE requires a schema name (AS clause)");
128 let escaped_file_path =
130 Value::String(Some(Box::new(file_path.to_string()))).to_sql_literal();
131 let escaped_db_name = sqlite_builder.escape_identifier(&db_name.to_string());
133 let sql = format!(
134 "ATTACH DATABASE {} AS {}",
135 escaped_file_path, escaped_db_name,
136 );
137 return (sql, crate::value::Values::new());
138 }
139 if (query_builder as &dyn Any)
140 .downcast_ref::<crate::backend::CockroachDBQueryBuilder>()
141 .is_some()
142 {
143 panic!("ATTACH DATABASE is SQLite-specific and not supported in CockroachDB");
144 }
145 panic!("Unsupported query builder type");
146 }
147}
148
149impl QueryStatementWriter for AttachDatabaseStatement {}
150
151#[cfg(test)]
152mod tests {
153 use super::*;
154 use rstest::*;
155
156 #[rstest]
157 fn test_attach_database_new() {
158 let stmt = AttachDatabaseStatement::new();
159 assert!(stmt.file_path.is_none());
160 assert!(stmt.database_name.is_none());
161 }
162
163 #[rstest]
164 fn test_attach_database_with_file_path() {
165 let mut stmt = AttachDatabaseStatement::new();
166 stmt.file_path("path/to/db.sqlite");
167 assert_eq!(stmt.file_path.as_ref().unwrap(), "path/to/db.sqlite");
168 }
169
170 #[rstest]
171 fn test_attach_database_with_as_name() {
172 let mut stmt = AttachDatabaseStatement::new();
173 stmt.as_name("auxiliary");
174 assert_eq!(
175 stmt.database_name.as_ref().unwrap().to_string(),
176 "auxiliary"
177 );
178 }
179
180 #[rstest]
181 fn test_attach_database_full() {
182 let mut stmt = AttachDatabaseStatement::new();
183 stmt.file_path("path/to/db.sqlite").as_name("auxiliary");
184 assert_eq!(stmt.file_path.as_ref().unwrap(), "path/to/db.sqlite");
185 assert_eq!(
186 stmt.database_name.as_ref().unwrap().to_string(),
187 "auxiliary"
188 );
189 }
190
191 #[rstest]
192 fn test_attach_database_take() {
193 let mut stmt = AttachDatabaseStatement::new();
194 stmt.file_path("path/to/db.sqlite").as_name("auxiliary");
195 let taken = stmt.take();
196 assert!(stmt.file_path.is_none());
197 assert!(stmt.database_name.is_none());
198 assert_eq!(taken.file_path.as_ref().unwrap(), "path/to/db.sqlite");
199 assert_eq!(
200 taken.database_name.as_ref().unwrap().to_string(),
201 "auxiliary"
202 );
203 }
204
205 #[rstest]
206 fn test_attach_database_default() {
207 let stmt = AttachDatabaseStatement::default();
208 assert!(stmt.file_path.is_none());
209 assert!(stmt.database_name.is_none());
210 }
211
212 #[rstest]
213 fn test_attach_database_build_sql() {
214 let mut stmt = AttachDatabaseStatement::new();
216 stmt.file_path("path/to/db.sqlite").as_name("auxiliary");
217
218 let (sql, values) = stmt.build_any(&crate::backend::SqliteQueryBuilder);
220
221 assert_eq!(sql, r#"ATTACH DATABASE 'path/to/db.sqlite' AS "auxiliary""#);
223 assert!(values.0.is_empty());
224 }
225
226 #[rstest]
227 fn test_attach_database_file_path_with_single_quotes() {
228 let mut stmt = AttachDatabaseStatement::new();
230 stmt.file_path("/path/to/file's.db").as_name("auxiliary");
231
232 let (sql, _) = stmt.build_any(&crate::backend::SqliteQueryBuilder);
234
235 assert_eq!(
237 sql,
238 r#"ATTACH DATABASE '/path/to/file''s.db' AS "auxiliary""#
239 );
240 }
241
242 #[rstest]
243 fn test_attach_database_db_name_with_double_quotes() {
244 let mut stmt = AttachDatabaseStatement::new();
246 stmt.file_path("path/to/db.sqlite").as_name(r#"my"db"#);
247
248 let (sql, _) = stmt.build_any(&crate::backend::SqliteQueryBuilder);
250
251 assert_eq!(sql, r#"ATTACH DATABASE 'path/to/db.sqlite' AS "my""db""#);
253 }
254
255 #[rstest]
256 fn test_attach_database_both_special_chars() {
257 let mut stmt = AttachDatabaseStatement::new();
259 stmt.file_path("/tmp/user's data/test.db")
260 .as_name(r#"special"name"#);
261
262 let (sql, _) = stmt.build_any(&crate::backend::SqliteQueryBuilder);
264
265 assert_eq!(
267 sql,
268 r#"ATTACH DATABASE '/tmp/user''s data/test.db' AS "special""name""#
269 );
270 }
271}