diesel/sqlite/connection/
attach.rs1use super::SqliteConnection;
2use crate::query_builder::{AstPass, QueryFragment, QueryId};
3use crate::query_dsl::RunQueryDslSupport;
4use crate::result::QueryResult;
5use crate::sqlite::Sqlite;
6
7impl SqliteConnection {
8 pub fn attach_database(&mut self, path: &str, schema_name: &str) -> QueryResult<()> {
39 use crate::query_dsl::RunQueryDsl;
40 AttachDatabase { path, schema_name }
41 .execute(self)
42 .map(|_| ())
43 }
44
45 pub fn detach_database(&mut self, schema_name: &str) -> QueryResult<()> {
51 use crate::query_dsl::RunQueryDsl;
52 DetachDatabase { schema_name }.execute(self).map(|_| ())
53 }
54}
55
56#[derive(const _: () =
{
use diesel;
#[allow(non_camel_case_types)]
impl<'a> diesel::query_builder::QueryId for AttachDatabase<'a> {
type QueryId = AttachDatabase<'static>;
const HAS_STATIC_QUERY_ID: bool = true;
const IS_WINDOW_FUNCTION: bool = false;
}
};QueryId)]
57struct AttachDatabase<'a> {
58 path: &'a str,
59 schema_name: &'a str,
60}
61
62impl QueryFragment<Sqlite> for AttachDatabase<'_> {
63 fn walk_ast<'b>(&'b self, mut out: AstPass<'_, 'b, Sqlite>) -> QueryResult<()> {
64 out.push_sql("ATTACH DATABASE ");
65 out.push_bind_param::<crate::sql_types::Text, _>(self.path)?;
66 out.push_sql(" AS ");
67 out.push_bind_param::<crate::sql_types::Text, _>(self.schema_name)?;
68 Ok(())
69 }
70}
71
72impl RunQueryDslSupport for AttachDatabase<'_> {}
73
74#[derive(const _: () =
{
use diesel;
#[allow(non_camel_case_types)]
impl<'a> diesel::query_builder::QueryId for DetachDatabase<'a> {
type QueryId = DetachDatabase<'static>;
const HAS_STATIC_QUERY_ID: bool = true;
const IS_WINDOW_FUNCTION: bool = false;
}
};QueryId)]
75struct DetachDatabase<'a> {
76 schema_name: &'a str,
77}
78
79impl QueryFragment<Sqlite> for DetachDatabase<'_> {
80 fn walk_ast<'b>(&'b self, mut out: AstPass<'_, 'b, Sqlite>) -> QueryResult<()> {
81 out.push_sql("DETACH DATABASE ");
82 out.push_bind_param::<crate::sql_types::Text, _>(self.schema_name)?;
83 Ok(())
84 }
85}
86
87impl RunQueryDslSupport for DetachDatabase<'_> {}
88
89#[cfg(test)]
90mod tests {
91 use super::*;
92 use crate::dsl::sql;
93 use crate::prelude::*;
94 use crate::sql_types::Integer;
95
96 fn connection() -> SqliteConnection {
97 SqliteConnection::establish(":memory:").unwrap()
98 }
99
100 #[cfg(not(any(all(target_family = "wasm", target_os = "unknown"), miri)))]
103 fn temp_db_path(name: &str) -> (tempfile::TempDir, std::path::PathBuf) {
104 let dir = tempfile::tempdir().unwrap();
105 let path = dir.path().join(name);
106 (dir, path)
107 }
108
109 #[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
112 table! {
113 attach_owners (id) {
114 id -> Integer,
115 name -> Text,
116 }
117 }
118 #[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
119 table! {
120 aux.attach_pets (id) {
121 id -> Integer,
122 owner_id -> Integer,
123 name -> Text,
124 }
125 }
126 #[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
127 allow_tables_to_appear_in_same_query!(attach_owners, attach_pets);
128 #[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
129 table! {
130 attach_marker (id) {
131 id -> Integer,
132 }
133 }
134 #[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
135 table! {
136 ro.readonly_marker (id) {
137 id -> Integer,
138 }
139 }
140
141 #[cfg(not(any(all(target_family = "wasm", target_os = "unknown"), miri)))]
143 #[diesel_test_helper::test]
144 fn attach_database_supports_cross_schema_join_then_detach() {
145 use crate::connection::SimpleConnection;
146 let conn = &mut connection();
147
148 conn.attach_database(":memory:", "aux").unwrap();
149
150 conn.batch_execute(
153 "CREATE TABLE attach_owners (id INTEGER PRIMARY KEY, name TEXT NOT NULL);
154 CREATE TABLE aux.attach_pets (id INTEGER PRIMARY KEY, owner_id INTEGER, name TEXT NOT NULL);",
155 )
156 .unwrap();
157
158 crate::insert_into(attach_owners::table)
159 .values(&[
160 (attach_owners::id.eq(1), attach_owners::name.eq("Sean")),
161 (attach_owners::id.eq(2), attach_owners::name.eq("Tess")),
162 ])
163 .execute(conn)
164 .unwrap();
165 crate::insert_into(attach_pets::table)
166 .values((
167 attach_pets::id.eq(1),
168 attach_pets::owner_id.eq(1),
169 attach_pets::name.eq("Ferris"),
170 ))
171 .execute(conn)
172 .unwrap();
173
174 let pet_owner = attach_owners::table
175 .inner_join(attach_pets::table.on(attach_pets::owner_id.eq(attach_owners::id)))
176 .filter(attach_pets::name.eq("Ferris"))
177 .select(attach_owners::name)
178 .get_result::<String>(conn)
179 .unwrap();
180 assert_eq!(pet_owner, "Sean");
181
182 conn.detach_database("aux").unwrap();
183
184 assert!(
186 attach_pets::table
187 .select(attach_pets::name)
188 .get_result::<String>(conn)
189 .is_err()
190 );
191 }
192
193 #[cfg(not(any(all(target_family = "wasm", target_os = "unknown"), miri)))]
195 #[diesel_test_helper::test]
196 fn attach_database_binds_path_verbatim_without_quoting() {
197 let (_dir, path) = temp_db_path("o'brien.db");
200
201 conn_attach_roundtrip(&path);
202
203 let mut direct = SqliteConnection::establish(path.to_str().unwrap()).unwrap();
206 let count = attach_marker::table
207 .count()
208 .get_result::<i64>(&mut direct)
209 .unwrap();
210 assert_eq!(count, 0);
211 }
212
213 #[cfg(not(any(all(target_family = "wasm", target_os = "unknown"), miri)))]
214 fn conn_attach_roundtrip(path: &std::path::Path) {
215 use crate::connection::SimpleConnection;
216 let conn = &mut connection();
217 conn.attach_database(path.to_str().unwrap(), "verbatim")
218 .unwrap();
219 conn.batch_execute("CREATE TABLE verbatim.attach_marker (id INTEGER PRIMARY KEY)")
220 .unwrap();
221 conn.detach_database("verbatim").unwrap();
222 }
223
224 #[cfg(not(any(all(target_family = "wasm", target_os = "unknown"), miri)))]
226 #[diesel_test_helper::test]
227 fn attach_database_interprets_file_uri_query_parameters() {
228 let (_dir, path) = temp_db_path("uri_seed.db");
230 {
231 let mut seed = SqliteConnection::establish(path.to_str().unwrap()).unwrap();
232 crate::sql_query("CREATE TABLE t (id INTEGER PRIMARY KEY)")
233 .execute(&mut seed)
234 .unwrap();
235 crate::sql_query("INSERT INTO t (id) VALUES (1)")
236 .execute(&mut seed)
237 .unwrap();
238 }
239
240 let uri = format!("file:{}?mode=ro", path.display());
244 let conn = &mut connection();
245 conn.attach_database(&uri, "ro_schema").unwrap();
246
247 let id: i64 = sql::<crate::sql_types::BigInt>("SELECT id FROM ro_schema.t")
249 .get_result(conn)
250 .unwrap();
251 assert_eq!(id, 1);
252
253 assert!(
255 crate::sql_query("INSERT INTO ro_schema.t (id) VALUES (2)")
256 .execute(conn)
257 .is_err()
258 );
259
260 conn.detach_database("ro_schema").unwrap();
261 }
262
263 #[diesel_test_helper::test]
264 #[cfg(not(miri))] fn attach_and_detach_surface_errors_without_panicking() {
266 let conn = &mut connection();
267
268 conn.attach_database(":memory:", "dup").unwrap();
270 assert!(conn.attach_database(":memory:", "dup").is_err());
271 conn.detach_database("dup").unwrap();
272
273 assert!(conn.detach_database("never_attached").is_err());
277 }
278
279 #[diesel_test_helper::test]
280 fn attach_database_binds_schema_name_verbatim_without_identifier_quoting() {
281 use crate::connection::SimpleConnection;
282 let conn = &mut connection();
283
284 let schema = "weird 'schema";
288 conn.attach_database(":memory:", schema).unwrap();
289
290 conn.batch_execute(
291 r#"CREATE TABLE "weird 'schema".t (id INTEGER PRIMARY KEY);
292 INSERT INTO "weird 'schema".t (id) VALUES (7);"#,
293 )
294 .unwrap();
295 let id = sql::<Integer>(r#"SELECT id FROM "weird 'schema".t"#)
296 .get_result::<i32>(conn)
297 .unwrap();
298 assert_eq!(id, 7);
299
300 conn.detach_database(schema).unwrap();
301 }
302
303 #[cfg(not(any(all(target_family = "wasm", target_os = "unknown"), miri)))]
305 #[diesel_test_helper::test]
306 fn attach_database_honors_create_and_write_hardening_knobs() {
307 use crate::connection::SimpleConnection;
308 let conn = &mut connection();
309
310 if conn.set_attach_create_enabled(false).is_err() {
312 return;
313 }
314
315 let (_dir_missing, missing) = temp_db_path("nocreate.db");
317 assert!(
318 conn.attach_database(missing.to_str().unwrap(), "missing")
319 .is_err()
320 );
321 assert!(!missing.exists());
322
323 conn.set_attach_write_enabled(false).unwrap();
325 let (_dir_existing, existing) = temp_db_path("readonly.db");
326 {
327 let mut seed = SqliteConnection::establish(existing.to_str().unwrap()).unwrap();
328 seed.batch_execute("CREATE TABLE readonly_marker (id INTEGER)")
329 .unwrap();
330 }
331 conn.attach_database(existing.to_str().unwrap(), "ro")
332 .unwrap();
333 assert!(
334 crate::insert_into(readonly_marker::table)
335 .values(readonly_marker::id.eq(1))
336 .execute(conn)
337 .is_err()
338 );
339 conn.detach_database("ro").unwrap();
340 }
341}