diesel/sqlite/connection/
db_config.rs1#[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
2extern crate libsqlite3_sys as ffi;
3
4#[cfg(all(target_family = "wasm", target_os = "unknown"))]
5use sqlite_wasm_rs as ffi;
6
7use super::SqliteConnection;
8use super::raw;
9use crate::result::QueryResult;
10
11impl SqliteConnection {
12 pub fn set_defensive(&mut self, enabled: bool) -> QueryResult<()> {
39 self.raw_connection
40 .set_db_config_bool(ffi::SQLITE_DBCONFIG_DEFENSIVE, enabled)
41 }
42
43 pub fn is_defensive(&self) -> QueryResult<bool> {
47 self.raw_connection
48 .get_db_config_bool(ffi::SQLITE_DBCONFIG_DEFENSIVE)
49 }
50
51 pub fn set_trusted_schema(&mut self, trusted: bool) -> QueryResult<()> {
63 self.raw_connection
64 .set_db_config_bool(ffi::SQLITE_DBCONFIG_TRUSTED_SCHEMA, trusted)
65 }
66
67 pub fn is_trusted_schema(&self) -> QueryResult<bool> {
71 self.raw_connection
72 .get_db_config_bool(ffi::SQLITE_DBCONFIG_TRUSTED_SCHEMA)
73 }
74
75 pub fn with_load_extension_enabled<R, E>(
103 &mut self,
104 f: impl FnOnce(&mut Self) -> Result<R, E>,
105 ) -> Result<R, E>
106 where
107 E: From<crate::result::Error>,
108 {
109 self.set_load_extension_enabled(true)?;
110
111 #[cfg(feature = "std")]
115 {
116 match std::panic::catch_unwind(core::panic::AssertUnwindSafe(|| f(self))) {
117 Ok(r) => {
118 self.set_load_extension_enabled(false)?;
119 r
120 }
121 Err(panic) => {
122 let _ = self.set_load_extension_enabled(false);
123 std::panic::resume_unwind(panic);
124 }
125 }
126 }
127 #[cfg(not(feature = "std"))]
128 {
129 let r = f(self);
130 self.set_load_extension_enabled(false)?;
131 r
132 }
133 }
134
135 fn set_load_extension_enabled(&mut self, enabled: bool) -> QueryResult<()> {
136 self.raw_connection
137 .set_db_config_bool(ffi::SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION, enabled)
138 }
139
140 #[cfg(test)]
141 fn is_load_extension_enabled(&self) -> QueryResult<bool> {
142 self.raw_connection
143 .get_db_config_bool(ffi::SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION)
144 }
145
146 pub fn set_fts3_tokenizer_enabled(&mut self, enabled: bool) -> QueryResult<()> {
155 self.raw_connection
156 .set_db_config_bool(ffi::SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER, enabled)
157 }
158
159 pub fn is_fts3_tokenizer_enabled(&self) -> QueryResult<bool> {
163 self.raw_connection
164 .get_db_config_bool(ffi::SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER)
165 }
166
167 pub fn set_writable_schema(&mut self, enabled: bool) -> QueryResult<()> {
176 self.raw_connection
177 .set_db_config_bool(ffi::SQLITE_DBCONFIG_WRITABLE_SCHEMA, enabled)
178 }
179
180 pub fn is_writable_schema(&self) -> QueryResult<bool> {
184 self.raw_connection
185 .get_db_config_bool(ffi::SQLITE_DBCONFIG_WRITABLE_SCHEMA)
186 }
187
188 pub fn set_attach_create_enabled(&mut self, enabled: bool) -> QueryResult<()> {
196 self.raw_connection
197 .set_db_config_bool(raw::SQLITE_DBCONFIG_ENABLE_ATTACH_CREATE, enabled)
198 }
199
200 pub fn is_attach_create_enabled(&self) -> QueryResult<bool> {
204 self.raw_connection
205 .get_db_config_bool(raw::SQLITE_DBCONFIG_ENABLE_ATTACH_CREATE)
206 }
207
208 pub fn set_attach_write_enabled(&mut self, enabled: bool) -> QueryResult<()> {
215 self.raw_connection
216 .set_db_config_bool(raw::SQLITE_DBCONFIG_ENABLE_ATTACH_WRITE, enabled)
217 }
218
219 pub fn is_attach_write_enabled(&self) -> QueryResult<bool> {
223 self.raw_connection
224 .get_db_config_bool(raw::SQLITE_DBCONFIG_ENABLE_ATTACH_WRITE)
225 }
226
227 pub fn set_triggers_enabled(&mut self, enabled: bool) -> QueryResult<()> {
233 self.raw_connection
234 .set_db_config_bool(ffi::SQLITE_DBCONFIG_ENABLE_TRIGGER, enabled)
235 }
236
237 pub fn are_triggers_enabled(&self) -> QueryResult<bool> {
241 self.raw_connection
242 .get_db_config_bool(ffi::SQLITE_DBCONFIG_ENABLE_TRIGGER)
243 }
244
245 pub fn set_views_enabled(&mut self, enabled: bool) -> QueryResult<()> {
251 self.raw_connection
252 .set_db_config_bool(ffi::SQLITE_DBCONFIG_ENABLE_VIEW, enabled)
253 }
254
255 pub fn are_views_enabled(&self) -> QueryResult<bool> {
259 self.raw_connection
260 .get_db_config_bool(ffi::SQLITE_DBCONFIG_ENABLE_VIEW)
261 }
262
263 pub fn set_foreign_keys_enabled(&mut self, enabled: bool) -> QueryResult<()> {
269 self.raw_connection
270 .set_db_config_bool(ffi::SQLITE_DBCONFIG_ENABLE_FKEY, enabled)
271 }
272
273 pub fn are_foreign_keys_enabled(&self) -> QueryResult<bool> {
277 self.raw_connection
278 .get_db_config_bool(ffi::SQLITE_DBCONFIG_ENABLE_FKEY)
279 }
280
281 pub fn set_double_quoted_strings_dml(&mut self, enabled: bool) -> QueryResult<()> {
289 self.raw_connection
290 .set_db_config_bool(ffi::SQLITE_DBCONFIG_DQS_DML, enabled)
291 }
292
293 pub fn are_double_quoted_strings_dml_enabled(&self) -> QueryResult<bool> {
297 self.raw_connection
298 .get_db_config_bool(ffi::SQLITE_DBCONFIG_DQS_DML)
299 }
300
301 pub fn set_double_quoted_strings_ddl(&mut self, enabled: bool) -> QueryResult<()> {
309 self.raw_connection
310 .set_db_config_bool(ffi::SQLITE_DBCONFIG_DQS_DDL, enabled)
311 }
312
313 pub fn are_double_quoted_strings_ddl_enabled(&self) -> QueryResult<bool> {
317 self.raw_connection
318 .get_db_config_bool(ffi::SQLITE_DBCONFIG_DQS_DDL)
319 }
320}
321
322#[cfg(test)]
323mod tests {
324 use super::*;
325 use crate::dsl::sql;
326 use crate::prelude::*;
327 #[cfg(not(miri))]
328 use crate::sql_types::Text;
329
330 fn connection() -> SqliteConnection {
331 SqliteConnection::establish(":memory:").unwrap()
332 }
333
334 #[diesel_test_helper::test]
337 fn db_config_defensive_roundtrip() {
338 let conn = &mut connection();
339 conn.set_defensive(true).unwrap();
340 assert!(conn.is_defensive().unwrap());
341 conn.set_defensive(false).unwrap();
342 assert!(!conn.is_defensive().unwrap());
343 }
344
345 #[diesel_test_helper::test]
346 fn db_config_trusted_schema_roundtrip() {
347 let conn = &mut connection();
348 conn.set_trusted_schema(false).unwrap();
349 assert!(!conn.is_trusted_schema().unwrap());
350 conn.set_trusted_schema(true).unwrap();
351 assert!(conn.is_trusted_schema().unwrap());
352 }
353
354 #[diesel_test_helper::test]
355 fn db_config_with_load_extension_enabled_scopes_the_flag() {
356 let conn = &mut connection();
357 conn.with_load_extension_enabled(|conn| {
358 assert!(conn.is_load_extension_enabled().unwrap());
360 QueryResult::Ok(())
361 })
362 .unwrap();
363 assert!(!conn.is_load_extension_enabled().unwrap());
365 }
366
367 #[cfg(all(
368 feature = "std",
369 not(all(target_family = "wasm", target_os = "unknown"))
370 ))]
371 #[diesel_test_helper::test]
372 fn with_load_extension_enabled_disables_after_panic() {
373 let conn = &mut connection();
374 let outcome = std::panic::catch_unwind(core::panic::AssertUnwindSafe(|| {
375 conn.with_load_extension_enabled(|_conn| -> QueryResult<()> {
376 panic!("boom inside closure");
377 })
378 }));
379 assert!(outcome.is_err(), "panic should propagate");
380 assert!(
381 !conn.is_load_extension_enabled().unwrap(),
382 "extension loading must be disabled again after a panic"
383 );
384 }
385
386 #[diesel_test_helper::test]
387 fn db_config_triggers_roundtrip() {
388 let conn = &mut connection();
389 conn.set_triggers_enabled(false).unwrap();
390 assert!(!conn.are_triggers_enabled().unwrap());
391 conn.set_triggers_enabled(true).unwrap();
392 assert!(conn.are_triggers_enabled().unwrap());
393 }
394
395 #[diesel_test_helper::test]
396 fn db_config_views_roundtrip() {
397 let conn = &mut connection();
398 conn.set_views_enabled(false).unwrap();
399 assert!(!conn.are_views_enabled().unwrap());
400 conn.set_views_enabled(true).unwrap();
401 assert!(conn.are_views_enabled().unwrap());
402 }
403
404 #[diesel_test_helper::test]
405 fn db_config_foreign_keys_roundtrip() {
406 let conn = &mut connection();
407 conn.set_foreign_keys_enabled(true).unwrap();
408 assert!(conn.are_foreign_keys_enabled().unwrap());
409 conn.set_foreign_keys_enabled(false).unwrap();
410 assert!(!conn.are_foreign_keys_enabled().unwrap());
411 }
412
413 #[diesel_test_helper::test]
414 fn db_config_dqs_dml_roundtrip() {
415 let conn = &mut connection();
416 conn.set_double_quoted_strings_dml(false).unwrap();
417 assert!(!conn.are_double_quoted_strings_dml_enabled().unwrap());
418 conn.set_double_quoted_strings_dml(true).unwrap();
419 assert!(conn.are_double_quoted_strings_dml_enabled().unwrap());
420 }
421
422 #[diesel_test_helper::test]
423 fn db_config_dqs_ddl_roundtrip() {
424 let conn = &mut connection();
425 conn.set_double_quoted_strings_ddl(false).unwrap();
426 assert!(!conn.are_double_quoted_strings_ddl_enabled().unwrap());
427 conn.set_double_quoted_strings_ddl(true).unwrap();
428 assert!(conn.are_double_quoted_strings_ddl_enabled().unwrap());
429 }
430
431 #[diesel_test_helper::test]
432 fn db_config_fts3_tokenizer_roundtrip() {
433 let conn = &mut connection();
434 conn.set_fts3_tokenizer_enabled(false).unwrap();
435 assert!(!conn.is_fts3_tokenizer_enabled().unwrap());
436 conn.set_fts3_tokenizer_enabled(true).unwrap();
437 assert!(conn.is_fts3_tokenizer_enabled().unwrap());
438 }
439
440 #[diesel_test_helper::test]
441 fn db_config_writable_schema_roundtrip() {
442 let conn = &mut connection();
443 conn.set_writable_schema(false).unwrap();
444 assert!(!conn.is_writable_schema().unwrap());
445 conn.set_writable_schema(true).unwrap();
446 assert!(conn.is_writable_schema().unwrap());
447 }
448
449 #[diesel_test_helper::test]
450 #[expect(unsafe_code, reason = "SQLite version lookup requires FFI")]
451 fn db_config_attach_create_roundtrip() {
452 if unsafe { ffi::sqlite3_libversion_number() } < 3_049_000 {
454 return;
455 }
456 let conn = &mut connection();
457 conn.set_attach_create_enabled(false).unwrap();
458 assert!(!conn.is_attach_create_enabled().unwrap());
459 conn.set_attach_create_enabled(true).unwrap();
460 assert!(conn.is_attach_create_enabled().unwrap());
461 }
462
463 #[diesel_test_helper::test]
464 #[expect(unsafe_code, reason = "SQLite version lookup requires FFI")]
465 fn db_config_attach_write_roundtrip() {
466 if unsafe { ffi::sqlite3_libversion_number() } < 3_049_000 {
468 return;
469 }
470 let conn = &mut connection();
471 conn.set_attach_write_enabled(false).unwrap();
472 assert!(!conn.is_attach_write_enabled().unwrap());
473 conn.set_attach_write_enabled(true).unwrap();
474 assert!(conn.is_attach_write_enabled().unwrap());
475 }
476
477 #[diesel_test_helper::test]
480 fn defensive_mode_blocks_writable_schema() {
481 let conn = &mut connection();
482 conn.set_defensive(true).unwrap();
483 let _ = crate::sql_query("PRAGMA writable_schema = ON").execute(conn);
485 assert!(!conn.is_writable_schema().unwrap());
486 }
487
488 #[diesel_test_helper::test]
489 fn foreign_keys_enabled_enforces_constraints() {
490 let conn = &mut connection();
491 conn.set_foreign_keys_enabled(true).unwrap();
492
493 crate::sql_query("CREATE TABLE parent (id INTEGER PRIMARY KEY)")
494 .execute(conn)
495 .unwrap();
496 crate::sql_query(
497 "CREATE TABLE child (id INTEGER PRIMARY KEY, parent_id INTEGER REFERENCES parent(id))",
498 )
499 .execute(conn)
500 .unwrap();
501
502 if cfg!(not(miri)) {
503 let result =
506 crate::sql_query("INSERT INTO child (id, parent_id) VALUES (1, 999)").execute(conn);
507 assert!(result.is_err());
508 }
509 }
510
511 #[diesel_test_helper::test]
512 fn views_disabled_blocks_view_queries() {
513 let conn = &mut connection();
514 crate::sql_query("CREATE TABLE base (id INTEGER PRIMARY KEY)")
515 .execute(conn)
516 .unwrap();
517 crate::sql_query("INSERT INTO base (id) VALUES (1)")
518 .execute(conn)
519 .unwrap();
520 crate::sql_query("CREATE VIEW base_view AS SELECT id FROM base")
521 .execute(conn)
522 .unwrap();
523
524 conn.set_views_enabled(true).unwrap();
526 assert!(
527 crate::sql_query("SELECT id FROM base_view")
528 .execute(conn)
529 .is_ok()
530 );
531
532 if cfg!(not(miri)) {
533 conn.set_views_enabled(false).unwrap();
536 assert!(
537 crate::sql_query("SELECT id FROM base_view")
538 .execute(conn)
539 .is_err()
540 );
541 }
542 }
543
544 #[diesel_test_helper::test]
545 fn triggers_disabled_prevents_firing() {
546 let conn = &mut connection();
547 crate::sql_query("CREATE TABLE source (id INTEGER PRIMARY KEY)")
548 .execute(conn)
549 .unwrap();
550 crate::sql_query("CREATE TABLE trigger_log (n INTEGER)")
551 .execute(conn)
552 .unwrap();
553 crate::sql_query("CREATE TRIGGER log_insert AFTER INSERT ON source BEGIN INSERT INTO trigger_log (n) VALUES (1); END")
554 .execute(conn)
555 .unwrap();
556
557 conn.set_triggers_enabled(false).unwrap();
559 crate::sql_query("INSERT INTO source (id) VALUES (1)")
560 .execute(conn)
561 .unwrap();
562 let count: i64 = sql::<crate::sql_types::BigInt>("SELECT COUNT(*) FROM trigger_log")
563 .get_result(conn)
564 .unwrap();
565 assert_eq!(0, count, "trigger should not fire while disabled");
566
567 conn.set_triggers_enabled(true).unwrap();
569 crate::sql_query("INSERT INTO source (id) VALUES (2)")
570 .execute(conn)
571 .unwrap();
572 let count: i64 = sql::<crate::sql_types::BigInt>("SELECT COUNT(*) FROM trigger_log")
573 .get_result(conn)
574 .unwrap();
575 assert_eq!(1, count, "trigger should fire while enabled");
576 }
577
578 #[cfg(not(miri))] #[diesel_test_helper::test]
580 fn dqs_dml_controls_double_quoted_string_literals() {
581 let conn = &mut connection();
582
583 conn.set_double_quoted_strings_dml(false).unwrap();
586
587 let disabled = sql::<Text>(r#"SELECT "bare_token""#).get_result::<String>(conn);
588 assert!(disabled.is_err());
589
590 conn.set_double_quoted_strings_dml(true).unwrap();
592 let enabled = sql::<Text>(r#"SELECT "bare_token""#).get_result::<String>(conn);
593 assert_eq!(Ok("bare_token".to_owned()), enabled);
594 }
595
596 #[diesel_test_helper::test]
597 fn dqs_ddl_controls_double_quoted_string_literals() {
598 let conn = &mut connection();
599
600 conn.set_double_quoted_strings_ddl(false).unwrap();
603 if cfg!(not(miri)) {
605 let disabled = crate::sql_query(
606 r#"CREATE TABLE dqs_off (name TEXT, CHECK (name <> "not_a_column"))"#,
607 )
608 .execute(conn);
609 assert!(disabled.is_err());
610 }
611
612 conn.set_double_quoted_strings_ddl(true).unwrap();
615 let enabled =
616 crate::sql_query(r#"CREATE TABLE dqs_on (name TEXT, CHECK (name <> "not_a_column"))"#)
617 .execute(conn);
618 assert!(enabled.is_ok());
619 }
620
621 #[diesel_test_helper::test]
622 fn writable_schema_controls_direct_sqlite_master_writes() {
623 let conn = &mut connection();
624 crate::sql_query("CREATE TABLE protected (id INTEGER PRIMARY KEY)")
625 .execute(conn)
626 .unwrap();
627
628 let update =
629 "UPDATE sqlite_master SET sql = sql WHERE type = 'table' AND name = 'protected'";
630
631 if cfg!(not(miri)) {
632 conn.set_writable_schema(false).unwrap();
635 assert!(crate::sql_query(update).execute(conn).is_err());
636 }
637
638 conn.set_writable_schema(true).unwrap();
640 assert!(crate::sql_query(update).execute(conn).is_ok());
641 }
642
643 #[cfg(not(miri))] #[diesel_test_helper::test]
645 fn fts3_tokenizer_disabled_blocks_the_function() {
646 let conn = &mut connection();
647
648 conn.set_fts3_tokenizer_enabled(true).unwrap();
650 let enabled = sql::<crate::sql_types::Binary>("SELECT fts3_tokenizer('simple')")
651 .get_result::<Vec<u8>>(conn);
652 if enabled.is_err() {
653 return;
655 }
656
657 conn.set_fts3_tokenizer_enabled(false).unwrap();
659 let disabled = sql::<crate::sql_types::Binary>("SELECT fts3_tokenizer('simple')")
660 .get_result::<Vec<u8>>(conn);
661 assert!(disabled.is_err());
662 }
663
664 #[cfg(not(any(all(target_family = "wasm", target_os = "unknown"), miri)))]
667 fn temp_db_path(name: &str) -> (tempfile::TempDir, std::path::PathBuf) {
668 let dir = tempfile::tempdir().unwrap();
669 let path = dir.path().join(name);
670 (dir, path)
671 }
672
673 #[cfg(not(any(all(target_family = "wasm", target_os = "unknown"), miri)))]
674 #[diesel_test_helper::test]
675 fn attach_create_disabled_blocks_new_database_files() {
676 let conn = &mut connection();
677
678 if conn.set_attach_create_enabled(false).is_err() {
681 return;
682 }
683
684 let (_dir, path) = temp_db_path("create.db");
685
686 assert!(
688 conn.attach_database(path.to_str().unwrap(), "aux_create")
689 .is_err()
690 );
691
692 conn.set_attach_create_enabled(true).unwrap();
694 conn.attach_database(path.to_str().unwrap(), "aux_create")
695 .unwrap();
696 conn.detach_database("aux_create").unwrap();
697 }
698
699 #[cfg(not(any(all(target_family = "wasm", target_os = "unknown"), miri)))]
700 #[diesel_test_helper::test]
701 fn attach_write_disabled_opens_attached_databases_read_only() {
702 let conn = &mut connection();
703
704 if conn.set_attach_write_enabled(false).is_err() {
708 return;
709 }
710
711 let (_dir, path) = temp_db_path("write.db");
713 {
714 let mut seed = SqliteConnection::establish(path.to_str().unwrap()).unwrap();
715 crate::sql_query("CREATE TABLE t (id INTEGER)")
716 .execute(&mut seed)
717 .unwrap();
718 }
719
720 conn.attach_database(path.to_str().unwrap(), "aux_write")
722 .unwrap();
723 assert!(
724 crate::sql_query("INSERT INTO aux_write.t (id) VALUES (1)")
725 .execute(conn)
726 .is_err()
727 );
728 conn.detach_database("aux_write").unwrap();
729
730 conn.set_attach_write_enabled(true).unwrap();
732 conn.attach_database(path.to_str().unwrap(), "aux_write")
733 .unwrap();
734 crate::sql_query("INSERT INTO aux_write.t (id) VALUES (1)")
735 .execute(conn)
736 .unwrap();
737 conn.detach_database("aux_write").unwrap();
738 }
739}