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 #[allow(unsafe_code)]
154 pub unsafe 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::connection::DatabaseVersion;
326 use crate::dsl::sql;
327 use crate::prelude::*;
328 #[cfg(not(miri))]
329 use crate::sql_types::Text;
330
331 fn connection() -> SqliteConnection {
332 SqliteConnection::establish(":memory:").unwrap()
333 }
334
335 #[diesel_test_helper::test]
338 fn db_config_defensive_roundtrip() {
339 let conn = &mut connection();
340 conn.set_defensive(true).unwrap();
341 assert!(conn.is_defensive().unwrap());
342 conn.set_defensive(false).unwrap();
343 assert!(!conn.is_defensive().unwrap());
344 }
345
346 #[diesel_test_helper::test]
347 fn db_config_trusted_schema_roundtrip() {
348 let conn = &mut connection();
349 conn.set_trusted_schema(false).unwrap();
350 assert!(!conn.is_trusted_schema().unwrap());
351 conn.set_trusted_schema(true).unwrap();
352 assert!(conn.is_trusted_schema().unwrap());
353 }
354
355 #[diesel_test_helper::test]
356 fn db_config_with_load_extension_enabled_scopes_the_flag() {
357 let conn = &mut connection();
358 conn.with_load_extension_enabled(|conn| {
359 assert!(conn.is_load_extension_enabled().unwrap());
361 QueryResult::Ok(())
362 })
363 .unwrap();
364 assert!(!conn.is_load_extension_enabled().unwrap());
366 }
367
368 #[cfg(all(
369 feature = "std",
370 not(all(target_family = "wasm", target_os = "unknown"))
371 ))]
372 #[diesel_test_helper::test]
373 fn with_load_extension_enabled_disables_after_panic() {
374 let conn = &mut connection();
375 let outcome = std::panic::catch_unwind(core::panic::AssertUnwindSafe(|| {
376 conn.with_load_extension_enabled(|_conn| -> QueryResult<()> {
377 panic!("boom inside closure");
378 })
379 }));
380 assert!(outcome.is_err(), "panic should propagate");
381 assert!(
382 !conn.is_load_extension_enabled().unwrap(),
383 "extension loading must be disabled again after a panic"
384 );
385 }
386
387 #[diesel_test_helper::test]
388 fn db_config_triggers_roundtrip() {
389 let conn = &mut connection();
390 conn.set_triggers_enabled(false).unwrap();
391 assert!(!conn.are_triggers_enabled().unwrap());
392 conn.set_triggers_enabled(true).unwrap();
393 assert!(conn.are_triggers_enabled().unwrap());
394 }
395
396 #[diesel_test_helper::test]
397 fn db_config_views_roundtrip() {
398 let conn = &mut connection();
399 conn.set_views_enabled(false).unwrap();
400 assert!(!conn.are_views_enabled().unwrap());
401 conn.set_views_enabled(true).unwrap();
402 assert!(conn.are_views_enabled().unwrap());
403 }
404
405 #[diesel_test_helper::test]
406 fn db_config_foreign_keys_roundtrip() {
407 let conn = &mut connection();
408 conn.set_foreign_keys_enabled(true).unwrap();
409 assert!(conn.are_foreign_keys_enabled().unwrap());
410 conn.set_foreign_keys_enabled(false).unwrap();
411 assert!(!conn.are_foreign_keys_enabled().unwrap());
412 }
413
414 #[diesel_test_helper::test]
415 fn db_config_dqs_dml_roundtrip() {
416 let conn = &mut connection();
417 conn.set_double_quoted_strings_dml(false).unwrap();
418 assert!(!conn.are_double_quoted_strings_dml_enabled().unwrap());
419 conn.set_double_quoted_strings_dml(true).unwrap();
420 assert!(conn.are_double_quoted_strings_dml_enabled().unwrap());
421 }
422
423 #[diesel_test_helper::test]
424 fn db_config_dqs_ddl_roundtrip() {
425 let conn = &mut connection();
426 conn.set_double_quoted_strings_ddl(false).unwrap();
427 assert!(!conn.are_double_quoted_strings_ddl_enabled().unwrap());
428 conn.set_double_quoted_strings_ddl(true).unwrap();
429 assert!(conn.are_double_quoted_strings_ddl_enabled().unwrap());
430 }
431
432 #[diesel_test_helper::test]
433 #[expect(unsafe_code, reason = "enabling the legacy tokenizer is unsafe")]
434 fn db_config_fts3_tokenizer_roundtrip() {
435 let conn = &mut connection();
436 unsafe { conn.set_fts3_tokenizer_enabled(false) }.unwrap();
438 assert!(!conn.is_fts3_tokenizer_enabled().unwrap());
439 unsafe { conn.set_fts3_tokenizer_enabled(true) }.unwrap();
441 assert!(conn.is_fts3_tokenizer_enabled().unwrap());
442 }
443
444 #[cfg(not(miri))] #[diesel_test_helper::test]
446 fn new_connection_rejects_a_tokenizer_pointer_from_sql_text() {
447 use crate::connection::SimpleConnection;
448
449 let conn = &mut connection();
450 assert!(!conn.is_fts3_tokenizer_enabled().unwrap());
451 assert!(
452 conn.batch_execute(
453 "SELECT fts3_tokenizer('evil', x'4141414141414141'); \
454 CREATE VIRTUAL TABLE evil_fts USING fts3(x, tokenize=evil);"
455 )
456 .is_err()
457 );
458 }
459
460 #[diesel_test_helper::test]
461 fn db_config_writable_schema_roundtrip() {
462 let conn = &mut connection();
463 conn.set_writable_schema(false).unwrap();
464 assert!(!conn.is_writable_schema().unwrap());
465 conn.set_writable_schema(true).unwrap();
466 assert!(conn.is_writable_schema().unwrap());
467 }
468
469 #[diesel_test_helper::test]
470 fn db_config_attach_create_roundtrip() {
471 let conn = &mut connection();
472 if conn.version() < DatabaseVersion::new(3, 49, 0) {
473 return;
474 }
475 conn.set_attach_create_enabled(false).unwrap();
476 assert!(!conn.is_attach_create_enabled().unwrap());
477 conn.set_attach_create_enabled(true).unwrap();
478 assert!(conn.is_attach_create_enabled().unwrap());
479 }
480
481 #[diesel_test_helper::test]
482 fn db_config_attach_write_roundtrip() {
483 let conn = &mut connection();
484 if conn.version() < DatabaseVersion::new(3, 49, 0) {
485 return;
486 }
487 conn.set_attach_write_enabled(false).unwrap();
488 assert!(!conn.is_attach_write_enabled().unwrap());
489 conn.set_attach_write_enabled(true).unwrap();
490 assert!(conn.is_attach_write_enabled().unwrap());
491 }
492
493 #[diesel_test_helper::test]
496 fn defensive_mode_blocks_writable_schema() {
497 let conn = &mut connection();
498 conn.set_defensive(true).unwrap();
499 let _ = crate::sql_query("PRAGMA writable_schema = ON").execute(conn);
501 assert!(!conn.is_writable_schema().unwrap());
502 }
503
504 #[diesel_test_helper::test]
505 fn foreign_keys_enabled_enforces_constraints() {
506 let conn = &mut connection();
507 conn.set_foreign_keys_enabled(true).unwrap();
508
509 crate::sql_query("CREATE TABLE parent (id INTEGER PRIMARY KEY)")
510 .execute(conn)
511 .unwrap();
512 crate::sql_query(
513 "CREATE TABLE child (id INTEGER PRIMARY KEY, parent_id INTEGER REFERENCES parent(id))",
514 )
515 .execute(conn)
516 .unwrap();
517
518 if cfg!(not(miri)) {
519 let result =
522 crate::sql_query("INSERT INTO child (id, parent_id) VALUES (1, 999)").execute(conn);
523 assert!(result.is_err());
524 }
525 }
526
527 #[diesel_test_helper::test]
528 fn views_disabled_blocks_view_queries() {
529 let conn = &mut connection();
530 crate::sql_query("CREATE TABLE base (id INTEGER PRIMARY KEY)")
531 .execute(conn)
532 .unwrap();
533 crate::sql_query("INSERT INTO base (id) VALUES (1)")
534 .execute(conn)
535 .unwrap();
536 crate::sql_query("CREATE VIEW base_view AS SELECT id FROM base")
537 .execute(conn)
538 .unwrap();
539
540 conn.set_views_enabled(true).unwrap();
542 assert!(
543 crate::sql_query("SELECT id FROM base_view")
544 .execute(conn)
545 .is_ok()
546 );
547
548 if cfg!(not(miri)) {
549 conn.set_views_enabled(false).unwrap();
552 assert!(
553 crate::sql_query("SELECT id FROM base_view")
554 .execute(conn)
555 .is_err()
556 );
557 }
558 }
559
560 #[diesel_test_helper::test]
561 fn triggers_disabled_prevents_firing() {
562 let conn = &mut connection();
563 crate::sql_query("CREATE TABLE source (id INTEGER PRIMARY KEY)")
564 .execute(conn)
565 .unwrap();
566 crate::sql_query("CREATE TABLE trigger_log (n INTEGER)")
567 .execute(conn)
568 .unwrap();
569 crate::sql_query("CREATE TRIGGER log_insert AFTER INSERT ON source BEGIN INSERT INTO trigger_log (n) VALUES (1); END")
570 .execute(conn)
571 .unwrap();
572
573 conn.set_triggers_enabled(false).unwrap();
575 crate::sql_query("INSERT INTO source (id) VALUES (1)")
576 .execute(conn)
577 .unwrap();
578 let count: i64 = sql::<crate::sql_types::BigInt>("SELECT COUNT(*) FROM trigger_log")
579 .get_result(conn)
580 .unwrap();
581 assert_eq!(0, count, "trigger should not fire while disabled");
582
583 conn.set_triggers_enabled(true).unwrap();
585 crate::sql_query("INSERT INTO source (id) VALUES (2)")
586 .execute(conn)
587 .unwrap();
588 let count: i64 = sql::<crate::sql_types::BigInt>("SELECT COUNT(*) FROM trigger_log")
589 .get_result(conn)
590 .unwrap();
591 assert_eq!(1, count, "trigger should fire while enabled");
592 }
593
594 #[cfg(not(miri))] #[diesel_test_helper::test]
596 fn dqs_dml_controls_double_quoted_string_literals() {
597 let conn = &mut connection();
598
599 conn.set_double_quoted_strings_dml(false).unwrap();
602
603 let disabled = sql::<Text>(r#"SELECT "bare_token""#).get_result::<String>(conn);
604 assert!(disabled.is_err());
605
606 conn.set_double_quoted_strings_dml(true).unwrap();
608 let enabled = sql::<Text>(r#"SELECT "bare_token""#).get_result::<String>(conn);
609 assert_eq!(Ok("bare_token".to_owned()), enabled);
610 }
611
612 #[diesel_test_helper::test]
613 fn dqs_ddl_controls_double_quoted_string_literals() {
614 let conn = &mut connection();
615
616 conn.set_double_quoted_strings_ddl(false).unwrap();
619 if cfg!(not(miri)) {
621 let disabled = crate::sql_query(
622 r#"CREATE TABLE dqs_off (name TEXT, CHECK (name <> "not_a_column"))"#,
623 )
624 .execute(conn);
625 assert!(disabled.is_err());
626 }
627
628 conn.set_double_quoted_strings_ddl(true).unwrap();
631 let enabled =
632 crate::sql_query(r#"CREATE TABLE dqs_on (name TEXT, CHECK (name <> "not_a_column"))"#)
633 .execute(conn);
634 assert!(enabled.is_ok());
635 }
636
637 #[diesel_test_helper::test]
638 fn writable_schema_controls_direct_sqlite_master_writes() {
639 let conn = &mut connection();
640 crate::sql_query("CREATE TABLE protected (id INTEGER PRIMARY KEY)")
641 .execute(conn)
642 .unwrap();
643
644 let update =
645 "UPDATE sqlite_master SET sql = sql WHERE type = 'table' AND name = 'protected'";
646
647 if cfg!(not(miri)) {
648 conn.set_writable_schema(false).unwrap();
651 assert!(crate::sql_query(update).execute(conn).is_err());
652 }
653
654 conn.set_writable_schema(true).unwrap();
656 assert!(crate::sql_query(update).execute(conn).is_ok());
657 }
658
659 #[cfg(not(miri))] #[diesel_test_helper::test]
661 #[expect(unsafe_code, reason = "enabling the legacy tokenizer is unsafe")]
662 fn fts3_tokenizer_disabled_blocks_the_function() {
663 let conn = &mut connection();
664
665 unsafe { conn.set_fts3_tokenizer_enabled(true) }.unwrap();
668 let enabled = sql::<crate::sql_types::Binary>("SELECT fts3_tokenizer('simple')")
669 .get_result::<Vec<u8>>(conn);
670 if enabled.is_err() {
671 return;
673 }
674
675 unsafe { conn.set_fts3_tokenizer_enabled(false) }.unwrap();
678 let disabled = sql::<crate::sql_types::Binary>("SELECT fts3_tokenizer('simple')")
679 .get_result::<Vec<u8>>(conn);
680 assert!(disabled.is_err());
681 }
682
683 #[cfg(not(any(all(target_family = "wasm", target_os = "unknown"), miri)))]
686 fn temp_db_path(name: &str) -> (tempfile::TempDir, std::path::PathBuf) {
687 let dir = tempfile::tempdir().unwrap();
688 let path = dir.path().join(name);
689 (dir, path)
690 }
691
692 #[cfg(not(any(all(target_family = "wasm", target_os = "unknown"), miri)))]
693 #[diesel_test_helper::test]
694 fn attach_create_disabled_blocks_new_database_files() {
695 let conn = &mut connection();
696
697 if conn.set_attach_create_enabled(false).is_err() {
700 return;
701 }
702
703 let (_dir, path) = temp_db_path("create.db");
704
705 assert!(
707 conn.attach_database(path.to_str().unwrap(), "aux_create")
708 .is_err()
709 );
710
711 conn.set_attach_create_enabled(true).unwrap();
713 conn.attach_database(path.to_str().unwrap(), "aux_create")
714 .unwrap();
715 conn.detach_database("aux_create").unwrap();
716 }
717
718 #[cfg(not(any(all(target_family = "wasm", target_os = "unknown"), miri)))]
719 #[diesel_test_helper::test]
720 fn attach_write_disabled_opens_attached_databases_read_only() {
721 let conn = &mut connection();
722
723 if conn.set_attach_write_enabled(false).is_err() {
727 return;
728 }
729
730 let (_dir, path) = temp_db_path("write.db");
732 {
733 let mut seed = SqliteConnection::establish(path.to_str().unwrap()).unwrap();
734 crate::sql_query("CREATE TABLE t (id INTEGER)")
735 .execute(&mut seed)
736 .unwrap();
737 }
738
739 conn.attach_database(path.to_str().unwrap(), "aux_write")
741 .unwrap();
742 assert!(
743 crate::sql_query("INSERT INTO aux_write.t (id) VALUES (1)")
744 .execute(conn)
745 .is_err()
746 );
747 conn.detach_database("aux_write").unwrap();
748
749 conn.set_attach_write_enabled(true).unwrap();
751 conn.attach_database(path.to_str().unwrap(), "aux_write")
752 .unwrap();
753 crate::sql_query("INSERT INTO aux_write.t (id) VALUES (1)")
754 .execute(conn)
755 .unwrap();
756 conn.detach_database("aux_write").unwrap();
757 }
758}