1use super::SqliteConnection;
2use super::update_hook::SqliteUpdateRouter;
3use core::num::NonZeroU32;
4
5pub(super) use super::authorizer::{AuthorizerContext, AuthorizerDecision};
6pub(super) use super::collation_needed::CollationNeededContext;
7pub(super) use super::{BusyDecision, CommitDecision, ProgressDecision};
8use super::{SqliteTraceEvent, SqliteTraceFlags};
9
10impl SqliteConnection {
11 pub fn on_update(&mut self, router: SqliteUpdateRouter) {
76 self.raw_connection.set_update_hook(router.into_hook());
77 }
78
79 pub fn remove_update_hook(&mut self) {
84 self.raw_connection.remove_update_hook();
85 }
86
87 pub fn on_commit<F>(&mut self, hook: F)
140 where
141 F: FnMut() -> CommitDecision + Send + 'static,
142 {
143 self.raw_connection.set_commit_hook(hook);
144 }
145
146 pub fn remove_commit_hook(&mut self) {
151 self.raw_connection.remove_commit_hook();
152 }
153
154 pub fn on_rollback<F>(&mut self, hook: F)
196 where
197 F: FnMut() + Send + 'static,
198 {
199 self.raw_connection.set_rollback_hook(hook);
200 }
201
202 pub fn remove_rollback_hook(&mut self) {
207 self.raw_connection.remove_rollback_hook();
208 }
209
210 pub fn on_progress<F>(&mut self, n: NonZeroU32, hook: F)
256 where
257 F: FnMut() -> ProgressDecision + Send + 'static,
258 {
259 self.raw_connection.set_progress_handler(n, hook);
260 }
261
262 pub fn remove_progress_handler(&mut self) {
267 self.raw_connection.remove_progress_handler();
268 }
269
270 pub fn on_wal<F>(&mut self, hook: F)
307 where
308 F: Fn(&mut SqliteConnection, &str, u32) + Send + 'static,
309 {
310 self.raw_connection.set_wal_hook(hook);
311 }
312
313 pub fn remove_wal_hook(&mut self) {
317 self.raw_connection.remove_wal_hook();
318 }
319
320 pub fn on_busy<F>(&mut self, hook: F)
365 where
366 F: FnMut(i32) -> BusyDecision + Send + 'static,
367 {
368 self.raw_connection.set_busy_handler(hook);
369 }
370
371 pub fn remove_busy_handler(&mut self) {
375 self.raw_connection.remove_busy_handler();
376 }
377
378 pub fn set_busy_timeout(&mut self, ms: i32) {
401 self.raw_connection.set_busy_timeout(ms);
402 }
403
404 pub fn on_authorize<F>(&mut self, hook: F)
450 where
451 F: FnMut(AuthorizerContext<'_>) -> AuthorizerDecision + Send + 'static,
452 {
453 self.raw_connection.set_authorizer(hook);
458 self.statement_cache.clear();
462 }
463
464 pub fn remove_authorizer(&mut self) {
468 self.raw_connection.remove_authorizer();
469 self.statement_cache.clear();
477 }
478
479 pub fn on_trace<F>(&mut self, mask: SqliteTraceFlags, hook: F)
517 where
518 F: FnMut(SqliteTraceEvent<'_>) + Send + 'static,
519 {
520 self.raw_connection.set_trace(mask, hook);
521 }
522
523 pub fn remove_trace(&mut self) {
527 self.raw_connection.remove_trace();
528 }
529
530 pub fn on_collation_needed<F>(&mut self, hook: F)
567 where
568 F: Fn(&mut SqliteConnection, CollationNeededContext<'_>) + Send + 'static,
569 {
570 self.raw_connection.set_collation_needed_hook(hook);
571 }
572
573 pub fn remove_collation_needed_hook(&mut self) {
578 self.raw_connection.remove_collation_needed_hook();
579 }
580}
581
582#[cfg(test)]
583mod tests {
584 use super::super::update_hook::{SqliteChangeOp, SqliteChangeOps, SqliteUpdateRouter};
585 use super::*;
586 use crate::connection::Connection;
587 use crate::prelude::*;
588 use crate::query_dsl::RunQueryDsl;
589 use std::sync::Arc;
590 use std::sync::atomic::{AtomicU32, Ordering};
591
592 fn connection() -> SqliteConnection {
593 SqliteConnection::establish(":memory:").unwrap()
594 }
595
596 #[derive(crate::QueryableByName)]
597 struct CountResult {
598 #[diesel(sql_type = crate::sql_types::BigInt)]
599 c: i64,
600 }
601
602 table! {
607 hook_users {
608 id -> Integer,
609 name -> Text,
610 }
611 }
612
613 table! {
614 hook_posts {
615 id -> Integer,
616 title -> Text,
617 }
618 }
619
620 fn setup_hook_tables(conn: &mut SqliteConnection) {
621 crate::sql_query(
622 "CREATE TABLE hook_users (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL)",
623 )
624 .execute(conn)
625 .unwrap();
626 crate::sql_query(
627 "CREATE TABLE hook_posts (id INTEGER PRIMARY KEY AUTOINCREMENT, title TEXT NOT NULL)",
628 )
629 .execute(conn)
630 .unwrap();
631 }
632
633 #[diesel_test_helper::test]
636 fn router_on_matches_schema_qualified_table() {
637 use std::sync::{Arc, Mutex};
638
639 table! {
640 attached.shared_items (id) {
641 id -> Integer,
642 }
643 }
644
645 let conn = &mut connection();
646 crate::sql_query("ATTACH DATABASE ':memory:' AS attached")
647 .execute(conn)
648 .unwrap();
649 crate::sql_query("CREATE TABLE shared_items (id INTEGER PRIMARY KEY)")
650 .execute(conn)
651 .unwrap();
652 crate::sql_query("CREATE TABLE attached.shared_items (id INTEGER PRIMARY KEY)")
653 .execute(conn)
654 .unwrap();
655
656 let fired = Arc::new(Mutex::new(Vec::new()));
657 let f2 = fired.clone();
658 conn.on_update(SqliteUpdateRouter::new().on(
659 shared_items::table,
660 SqliteChangeOps::ALL,
661 move |ev| {
662 f2.lock().unwrap().push((ev.db_name.to_owned(), ev.rowid));
663 },
664 ));
665
666 crate::sql_query("INSERT INTO main.shared_items (id) VALUES (1)")
668 .execute(conn)
669 .unwrap();
670 crate::sql_query("INSERT INTO attached.shared_items (id) VALUES (2)")
672 .execute(conn)
673 .unwrap();
674
675 assert_eq!(
676 *fired.lock().unwrap(),
677 vec![("attached".to_owned(), 2)],
678 "a schema-qualified route matches only its attached database"
679 );
680 }
681
682 #[diesel_test_helper::test]
683 fn router_on_dispatches_to_typed_table() {
684 use std::sync::{Arc, Mutex};
685 let conn = &mut connection();
686 setup_hook_tables(conn);
687
688 let fired = Arc::new(Mutex::new(Vec::new()));
689 let fired2 = fired.clone();
690
691 conn.on_update(SqliteUpdateRouter::new().on(
692 hook_users::table,
693 SqliteChangeOps::INSERT,
694 move |change| {
695 fired2.lock().unwrap().push((change.op, change.rowid));
696 },
697 ));
698
699 crate::sql_query("INSERT INTO hook_users (name) VALUES ('Alice')")
701 .execute(conn)
702 .unwrap();
703
704 let events = fired.lock().unwrap().clone();
705 assert_eq!(events.len(), 1);
706 assert_eq!(events[0].0, SqliteChangeOp::Insert);
707 assert_eq!(events[0].1, 1); }
709
710 #[diesel_test_helper::test]
711 fn on_delete_fires_only_for_delete() {
712 use std::sync::{Arc, Mutex};
713 let conn = &mut connection();
714 setup_hook_tables(conn);
715
716 let fired = Arc::new(Mutex::new(Vec::new()));
717 let fired2 = fired.clone();
718
719 conn.on_update(SqliteUpdateRouter::new().on(
720 hook_users::table,
721 SqliteChangeOps::DELETE,
722 move |change| {
723 fired2.lock().unwrap().push(change.op);
724 },
725 ));
726
727 crate::sql_query("INSERT INTO hook_users (name) VALUES ('Alice')")
729 .execute(conn)
730 .unwrap();
731 crate::sql_query("UPDATE hook_users SET name = 'Bob' WHERE id = 1")
732 .execute(conn)
733 .unwrap();
734 crate::sql_query("DELETE FROM hook_users WHERE id = 1")
735 .execute(conn)
736 .unwrap();
737
738 let events = fired.lock().unwrap().clone();
739 assert_eq!(events.len(), 1);
741 assert_eq!(events[0], SqliteChangeOp::Delete);
742 }
743
744 #[diesel_test_helper::test]
745 fn every_matching_route_fires_in_order() {
746 use std::sync::{Arc, Mutex};
747 let conn = &mut connection();
748 setup_hook_tables(conn);
749
750 let order = Arc::new(Mutex::new(Vec::new()));
751 let o1 = order.clone();
752 let o2 = order.clone();
753
754 conn.on_update(
755 SqliteUpdateRouter::new()
756 .on(hook_users::table, SqliteChangeOps::INSERT, move |_| {
757 o1.lock().unwrap().push(1);
758 })
759 .on(hook_users::table, SqliteChangeOps::INSERT, move |_| {
760 o2.lock().unwrap().push(2);
761 }),
762 );
763
764 crate::sql_query("INSERT INTO hook_users (name) VALUES ('X')")
765 .execute(conn)
766 .unwrap();
767
768 assert_eq!(*order.lock().unwrap(), vec![1, 2]);
769 }
770
771 #[diesel_test_helper::test]
772 fn remove_update_stops_dispatch() {
773 use std::sync::{Arc, Mutex};
774 let conn = &mut connection();
775 setup_hook_tables(conn);
776
777 let fired = Arc::new(Mutex::new(0u32));
778 let f2 = fired.clone();
779
780 conn.on_update(
781 SqliteUpdateRouter::new().on_any(SqliteChangeOps::ALL, move |_| {
782 *f2.lock().unwrap() += 1;
783 }),
784 );
785
786 crate::sql_query("INSERT INTO hook_users (name) VALUES ('A')")
787 .execute(conn)
788 .unwrap();
789 assert_eq!(*fired.lock().unwrap(), 1);
790
791 conn.remove_update_hook();
793
794 crate::sql_query("INSERT INTO hook_users (name) VALUES ('B')")
795 .execute(conn)
796 .unwrap();
797 assert_eq!(*fired.lock().unwrap(), 1);
799 }
800
801 #[diesel_test_helper::test]
802 fn events_fire_immediately_during_statement() {
803 use std::sync::{Arc, Mutex};
804 let conn = &mut connection();
805 setup_hook_tables(conn);
806
807 crate::sql_query("INSERT INTO hook_users (name) VALUES ('Z')")
809 .execute(conn)
810 .unwrap();
811
812 let fired = Arc::new(Mutex::new(Vec::new()));
813 let f2 = fired.clone();
814
815 conn.on_update(SqliteUpdateRouter::new().on(
816 hook_users::table,
817 SqliteChangeOps::UPDATE,
818 move |event| {
819 f2.lock().unwrap().push(event.rowid);
820 },
821 ));
822
823 crate::sql_query("UPDATE hook_users SET name = 'W' WHERE id = 1")
825 .execute(conn)
826 .unwrap();
827
828 assert_eq!(*fired.lock().unwrap(), vec![1i64]);
829 }
830
831 #[diesel_test_helper::test]
832 fn on_update_fires_for_update_only() {
833 use std::sync::{Arc, Mutex};
834 let conn = &mut connection();
835 setup_hook_tables(conn);
836
837 let count = Arc::new(Mutex::new(0u32));
838 let c2 = count.clone();
839
840 conn.on_update(SqliteUpdateRouter::new().on(
841 hook_users::table,
842 SqliteChangeOps::UPDATE,
843 move |event| {
844 assert_eq!(event.op, SqliteChangeOp::Update);
845 *c2.lock().unwrap() += 1;
846 },
847 ));
848
849 crate::sql_query("INSERT INTO hook_users (name) VALUES ('A')")
850 .execute(conn)
851 .unwrap();
852 crate::sql_query("UPDATE hook_users SET name = 'B' WHERE id = 1")
853 .execute(conn)
854 .unwrap();
855 crate::sql_query("DELETE FROM hook_users WHERE id = 1")
856 .execute(conn)
857 .unwrap();
858
859 assert_eq!(*count.lock().unwrap(), 1);
860 }
861
862 #[diesel_test_helper::test]
863 fn on_update_receives_every_change() {
864 use std::sync::{Arc, Mutex};
865 let conn = &mut connection();
866 setup_hook_tables(conn);
867
868 let events = Arc::new(Mutex::new(Vec::new()));
869 let e2 = events.clone();
870
871 conn.on_update(
872 SqliteUpdateRouter::new().on_any(SqliteChangeOps::ALL, move |ev| {
873 e2.lock().unwrap().push((ev.op, ev.table_name.to_owned()));
874 }),
875 );
876
877 crate::sql_query("INSERT INTO hook_users (name) VALUES ('A')")
878 .execute(conn)
879 .unwrap();
880 crate::sql_query("INSERT INTO hook_posts (title) VALUES ('P')")
881 .execute(conn)
882 .unwrap();
883 crate::sql_query("UPDATE hook_users SET name = 'B' WHERE id = 1")
884 .execute(conn)
885 .unwrap();
886 crate::sql_query("DELETE FROM hook_posts WHERE id = 1")
887 .execute(conn)
888 .unwrap();
889
890 let evts = events.lock().unwrap().clone();
891 assert_eq!(evts.len(), 4);
892 assert_eq!(evts[0], (SqliteChangeOp::Insert, "hook_users".to_owned()));
893 assert_eq!(evts[1], (SqliteChangeOp::Insert, "hook_posts".to_owned()));
894 assert_eq!(evts[2], (SqliteChangeOp::Update, "hook_users".to_owned()));
895 assert_eq!(evts[3], (SqliteChangeOp::Delete, "hook_posts".to_owned()));
896 }
897
898 #[diesel_test_helper::test]
899 fn router_filters_by_op_mask() {
900 use std::sync::{Arc, Mutex};
901 let conn = &mut connection();
902 setup_hook_tables(conn);
903
904 let count = Arc::new(Mutex::new(0u32));
905 let c2 = count.clone();
906
907 conn.on_update(SqliteUpdateRouter::new().on_any(
908 SqliteChangeOps::INSERT | SqliteChangeOps::DELETE,
909 move |_| {
910 *c2.lock().unwrap() += 1;
911 },
912 ));
913
914 crate::sql_query("INSERT INTO hook_users (name) VALUES ('A')")
915 .execute(conn)
916 .unwrap();
917 crate::sql_query("UPDATE hook_users SET name = 'B' WHERE id = 1")
918 .execute(conn)
919 .unwrap();
920 crate::sql_query("DELETE FROM hook_users WHERE id = 1")
921 .execute(conn)
922 .unwrap();
923
924 assert_eq!(*count.lock().unwrap(), 2);
926 }
927
928 #[diesel_test_helper::test]
929 fn router_dispatches_to_multiple_tables() {
930 use std::sync::{Arc, Mutex};
931 let conn = &mut connection();
932 setup_hook_tables(conn);
933
934 let user_count = Arc::new(Mutex::new(0u32));
935 let post_count = Arc::new(Mutex::new(0u32));
936 let uc = user_count.clone();
937 let pc = post_count.clone();
938
939 conn.on_update(
940 SqliteUpdateRouter::new()
941 .on(hook_users::table, SqliteChangeOps::ALL, move |_| {
942 *uc.lock().unwrap() += 1;
943 })
944 .on(hook_posts::table, SqliteChangeOps::ALL, move |_| {
945 *pc.lock().unwrap() += 1;
946 }),
947 );
948
949 crate::sql_query("INSERT INTO hook_users (name) VALUES ('X')")
950 .execute(conn)
951 .unwrap();
952 crate::sql_query("INSERT INTO hook_posts (title) VALUES ('Y')")
953 .execute(conn)
954 .unwrap();
955
956 assert_eq!(*user_count.lock().unwrap(), 1);
957 assert_eq!(*post_count.lock().unwrap(), 1);
958 }
959
960 #[diesel_test_helper::test]
961 fn on_any_audit_plus_specific_route() {
962 use std::sync::{Arc, Mutex};
963 let conn = &mut connection();
964 setup_hook_tables(conn);
965
966 let audit_count = Arc::new(Mutex::new(0u32));
967 let user_insert_count = Arc::new(Mutex::new(0u32));
968 let ac = audit_count.clone();
969 let uic = user_insert_count.clone();
970
971 conn.on_update(
972 SqliteUpdateRouter::new()
973 .on_any(SqliteChangeOps::ALL, move |_| {
974 *ac.lock().unwrap() += 1;
975 })
976 .on(hook_users::table, SqliteChangeOps::INSERT, move |_| {
977 *uic.lock().unwrap() += 1;
978 }),
979 );
980
981 crate::sql_query("INSERT INTO hook_users (name) VALUES ('X')")
983 .execute(conn)
984 .unwrap();
985 crate::sql_query("INSERT INTO hook_posts (title) VALUES ('Y')")
987 .execute(conn)
988 .unwrap();
989
990 assert_eq!(*audit_count.lock().unwrap(), 2);
991 assert_eq!(*user_insert_count.lock().unwrap(), 1);
992 }
993
994 #[diesel_test_helper::test]
995 fn rowid_in_filters_by_table() {
996 use std::sync::{Arc, Mutex};
997 let conn = &mut connection();
998 setup_hook_tables(conn);
999
1000 let captured = Arc::new(Mutex::new(Vec::new()));
1001 let c2 = captured.clone();
1002
1003 conn.on_update(
1004 SqliteUpdateRouter::new().on_any(SqliteChangeOps::ALL, move |change| {
1005 if let Some(rowid) = change.rowid_in(hook_users::table) {
1006 c2.lock().unwrap().push(rowid);
1007 }
1008 }),
1009 );
1010
1011 crate::sql_query("INSERT INTO hook_users (name) VALUES ('A')")
1012 .execute(conn)
1013 .unwrap();
1014 crate::sql_query("INSERT INTO hook_posts (title) VALUES ('P')")
1015 .execute(conn)
1016 .unwrap();
1017
1018 assert_eq!(*captured.lock().unwrap(), vec![1i64]);
1020 }
1021
1022 #[diesel_test_helper::test]
1023 fn is_from_matches_table_marker() {
1024 use std::sync::{Arc, Mutex};
1025 let conn = &mut connection();
1026 setup_hook_tables(conn);
1027
1028 let captured = Arc::new(Mutex::new(Vec::new()));
1029 let c2 = captured.clone();
1030
1031 conn.on_update(
1032 SqliteUpdateRouter::new().on_any(SqliteChangeOps::ALL, move |change| {
1033 c2.lock().unwrap().push((
1034 change.is_from(hook_users::table),
1035 change.is_from(hook_posts::table),
1036 ));
1037 }),
1038 );
1039
1040 crate::sql_query("INSERT INTO hook_users (name) VALUES ('A')")
1041 .execute(conn)
1042 .unwrap();
1043
1044 assert_eq!(*captured.lock().unwrap(), vec![(true, false)]);
1045 }
1046
1047 #[diesel_test_helper::test]
1048 fn hooks_fire_across_transactions() {
1049 use std::sync::{Arc, Mutex};
1050 let conn = &mut connection();
1051 setup_hook_tables(conn);
1052
1053 let fired = Arc::new(Mutex::new(Vec::new()));
1054 let f2 = fired.clone();
1055
1056 conn.on_update(SqliteUpdateRouter::new().on(
1058 hook_users::table,
1059 SqliteChangeOps::INSERT,
1060 move |event| {
1061 f2.lock().unwrap().push(event.rowid);
1062 },
1063 ));
1064
1065 conn.immediate_transaction(|conn| {
1066 crate::sql_query("INSERT INTO hook_users (name) VALUES ('TxUser')")
1067 .execute(conn)
1068 .unwrap();
1069 Ok::<_, crate::result::Error>(())
1070 })
1071 .unwrap();
1072
1073 assert_eq!(fired.lock().unwrap().len(), 1);
1074 }
1075
1076 #[diesel_test_helper::test]
1086 fn update_hook_silent_for_without_rowid_tables() {
1087 use std::sync::{Arc, Mutex};
1088 let conn = &mut connection();
1089
1090 crate::sql_query("CREATE TABLE kv (key TEXT PRIMARY KEY, val TEXT NOT NULL) WITHOUT ROWID")
1091 .execute(conn)
1092 .unwrap();
1093
1094 let events: Arc<Mutex<Vec<SqliteChangeOp>>> = Arc::new(Mutex::new(Vec::new()));
1095 let e2 = events.clone();
1096
1097 conn.on_update(
1098 SqliteUpdateRouter::new().on_any(SqliteChangeOps::ALL, move |ev| {
1099 if ev.table_name == "kv" {
1100 e2.lock().unwrap().push(ev.op);
1101 }
1102 }),
1103 );
1104
1105 crate::sql_query("INSERT INTO kv (key, val) VALUES ('a', '1')")
1106 .execute(conn)
1107 .unwrap();
1108 crate::sql_query("UPDATE kv SET val = '2' WHERE key = 'a'")
1109 .execute(conn)
1110 .unwrap();
1111 crate::sql_query("DELETE FROM kv WHERE key = 'a'")
1112 .execute(conn)
1113 .unwrap();
1114
1115 assert!(
1116 events.lock().unwrap().is_empty(),
1117 "update hook must not fire for WITHOUT ROWID tables"
1118 );
1119 }
1120
1121 #[diesel_test_helper::test]
1126 fn update_hook_silent_for_on_conflict_replace_deletion() {
1127 use std::sync::{Arc, Mutex};
1128 let conn = &mut connection();
1129
1130 crate::sql_query("CREATE TABLE uq (id INTEGER PRIMARY KEY, val TEXT NOT NULL UNIQUE)")
1131 .execute(conn)
1132 .unwrap();
1133
1134 crate::sql_query("INSERT INTO uq (id, val) VALUES (1, 'original')")
1135 .execute(conn)
1136 .unwrap();
1137
1138 let events: Arc<Mutex<Vec<(SqliteChangeOp, i64)>>> = Arc::new(Mutex::new(Vec::new()));
1139 let e2 = events.clone();
1140
1141 conn.on_update(
1142 SqliteUpdateRouter::new().on_any(SqliteChangeOps::ALL, move |ev| {
1143 if ev.table_name == "uq" {
1144 e2.lock().unwrap().push((ev.op, ev.rowid));
1145 }
1146 }),
1147 );
1148
1149 crate::sql_query("INSERT OR REPLACE INTO uq (id, val) VALUES (2, 'original')")
1153 .execute(conn)
1154 .unwrap();
1155
1156 let recorded = events.lock().unwrap();
1157 assert_eq!(
1158 recorded.len(),
1159 1,
1160 "expected only 1 event (INSERT), got: {:?}",
1161 *recorded
1162 );
1163 assert_eq!(recorded[0].0, SqliteChangeOp::Insert);
1164 assert_eq!(recorded[0].1, 2, "new row should have rowid 2");
1165 }
1166
1167 #[diesel_test_helper::test]
1171 fn update_hook_silent_for_truncate_optimization() {
1172 use std::sync::{Arc, Mutex};
1173 let conn = &mut connection();
1174
1175 crate::sql_query("CREATE TABLE bulk (id INTEGER PRIMARY KEY, data TEXT NOT NULL)")
1180 .execute(conn)
1181 .unwrap();
1182
1183 crate::sql_query("INSERT INTO bulk (data) VALUES ('a'), ('b'), ('c')")
1184 .execute(conn)
1185 .unwrap();
1186
1187 let events: Arc<Mutex<Vec<SqliteChangeOp>>> = Arc::new(Mutex::new(Vec::new()));
1188 let e2 = events.clone();
1189
1190 conn.on_update(
1191 SqliteUpdateRouter::new().on_any(SqliteChangeOps::ALL, move |ev| {
1192 if ev.table_name == "bulk" {
1193 e2.lock().unwrap().push(ev.op);
1194 }
1195 }),
1196 );
1197
1198 crate::sql_query("DELETE FROM bulk").execute(conn).unwrap();
1200
1201 assert!(
1202 events.lock().unwrap().is_empty(),
1203 "truncate optimization should bypass the update hook"
1204 );
1205 }
1206
1207 #[diesel_test_helper::test]
1210 fn update_hook_fires_for_delete_all_when_triggers_disable_truncate() {
1211 use std::sync::{Arc, Mutex};
1212 let conn = &mut connection();
1213
1214 crate::sql_query("CREATE TABLE triggered (id INTEGER PRIMARY KEY, data TEXT NOT NULL)")
1215 .execute(conn)
1216 .unwrap();
1217 crate::sql_query(
1219 "CREATE TRIGGER trg_triggered BEFORE DELETE ON triggered \
1220 BEGIN SELECT 1; END",
1221 )
1222 .execute(conn)
1223 .unwrap();
1224
1225 crate::sql_query("INSERT INTO triggered (data) VALUES ('x'), ('y'), ('z')")
1226 .execute(conn)
1227 .unwrap();
1228
1229 let deletes: Arc<Mutex<Vec<i64>>> = Arc::new(Mutex::new(Vec::new()));
1230 let d2 = deletes.clone();
1231
1232 conn.on_update(
1233 SqliteUpdateRouter::new().on_any(SqliteChangeOps::ALL, move |ev| {
1234 if ev.table_name == "triggered" && ev.op == SqliteChangeOp::Delete {
1235 d2.lock().unwrap().push(ev.rowid);
1236 }
1237 }),
1238 );
1239
1240 crate::sql_query("DELETE FROM triggered")
1242 .execute(conn)
1243 .unwrap();
1244
1245 assert_eq!(
1246 deletes.lock().unwrap().len(),
1247 3,
1248 "with triggers present, DELETE without WHERE fires per-row hooks"
1249 );
1250 }
1251
1252 #[diesel_test_helper::test]
1256 fn update_hook_silent_for_internal_sqlite_sequence() {
1257 use std::sync::{Arc, Mutex};
1258 let conn = &mut connection();
1259
1260 crate::sql_query(
1262 "CREATE TABLE seq_test (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL)",
1263 )
1264 .execute(conn)
1265 .unwrap();
1266
1267 let tables: Arc<Mutex<Vec<String>>> = Arc::new(Mutex::new(Vec::new()));
1268 let t2 = tables.clone();
1269
1270 conn.on_update(
1271 SqliteUpdateRouter::new().on_any(SqliteChangeOps::ALL, move |ev| {
1272 t2.lock().unwrap().push(ev.table_name.to_owned());
1273 }),
1274 );
1275
1276 crate::sql_query("INSERT INTO seq_test (name) VALUES ('row1')")
1277 .execute(conn)
1278 .unwrap();
1279
1280 let recorded = tables.lock().unwrap();
1281 assert!(
1283 recorded.iter().all(|t| t == "seq_test"),
1284 "expected only 'seq_test' events, got: {:?}",
1285 *recorded
1286 );
1287 assert!(
1288 !recorded.iter().any(|t| t == "sqlite_sequence"),
1289 "sqlite_sequence modifications must not trigger the update hook"
1290 );
1291 }
1292
1293 #[diesel_test_helper::test]
1297 fn update_hook_silent_for_replace_into_on_pk_conflict() {
1298 use std::sync::{Arc, Mutex};
1299 let conn = &mut connection();
1300
1301 crate::sql_query("CREATE TABLE rep (id INTEGER PRIMARY KEY, val TEXT NOT NULL)")
1302 .execute(conn)
1303 .unwrap();
1304
1305 crate::sql_query("INSERT INTO rep (id, val) VALUES (1, 'old')")
1306 .execute(conn)
1307 .unwrap();
1308
1309 let events: Arc<Mutex<Vec<(SqliteChangeOp, i64)>>> = Arc::new(Mutex::new(Vec::new()));
1310 let e2 = events.clone();
1311
1312 conn.on_update(
1313 SqliteUpdateRouter::new().on_any(SqliteChangeOps::ALL, move |ev| {
1314 if ev.table_name == "rep" {
1315 e2.lock().unwrap().push((ev.op, ev.rowid));
1316 }
1317 }),
1318 );
1319
1320 crate::sql_query("REPLACE INTO rep (id, val) VALUES (1, 'new')")
1322 .execute(conn)
1323 .unwrap();
1324
1325 let recorded = events.lock().unwrap();
1326 assert_eq!(
1328 recorded.len(),
1329 1,
1330 "expected 1 event for REPLACE INTO, got: {:?}",
1331 *recorded
1332 );
1333 assert_eq!(recorded[0].0, SqliteChangeOp::Insert);
1334 assert_eq!(recorded[0].1, 1);
1335 }
1336
1337 #[diesel_test_helper::test]
1349 fn change_hook_fires_after_connection_move() {
1350 use std::sync::{Arc, Mutex};
1351
1352 let count = Arc::new(Mutex::new(0u32));
1353 let count2 = count.clone();
1354
1355 let mut conn = connection();
1356 setup_hook_tables(&mut conn);
1357 conn.on_update(SqliteUpdateRouter::new().on(
1358 hook_users::table,
1359 SqliteChangeOps::INSERT,
1360 move |_| {
1361 *count2.lock().unwrap() += 1;
1362 },
1363 ));
1364
1365 let mut boxed = Box::new(conn);
1367
1368 crate::sql_query("INSERT INTO hook_users (name) VALUES ('Alice')")
1369 .execute(&mut *boxed)
1370 .unwrap();
1371
1372 assert_eq!(
1373 *count.lock().unwrap(),
1374 1,
1375 "change hook did not fire after the connection was moved"
1376 );
1377 }
1378
1379 #[diesel_test_helper::test]
1380 fn router_filters_table_and_op() {
1381 use std::sync::{Arc, Mutex};
1382 let conn = &mut connection();
1383 setup_hook_tables(conn);
1384
1385 let fired = Arc::new(Mutex::new(Vec::new()));
1386 let fired2 = fired.clone();
1387
1388 conn.on_update(SqliteUpdateRouter::new().on(
1390 hook_users::table,
1391 SqliteChangeOps::INSERT | SqliteChangeOps::UPDATE,
1392 move |event| fired2.lock().unwrap().push(event.op),
1393 ));
1394
1395 crate::sql_query("INSERT INTO hook_users (name) VALUES ('Alice')")
1396 .execute(conn)
1397 .unwrap();
1398 crate::sql_query("UPDATE hook_users SET name = 'Bob' WHERE id = 1")
1399 .execute(conn)
1400 .unwrap();
1401 crate::sql_query("DELETE FROM hook_users WHERE id = 1")
1402 .execute(conn)
1403 .unwrap();
1404 crate::sql_query("INSERT INTO hook_posts (title) VALUES ('Hello')")
1406 .execute(conn)
1407 .unwrap();
1408
1409 let events = fired.lock().unwrap().clone();
1410 assert_eq!(events, vec![SqliteChangeOp::Insert, SqliteChangeOp::Update]);
1411 }
1412
1413 #[diesel_test_helper::test]
1414 fn on_commit_fires_on_commit() {
1415 let conn = &mut connection();
1416
1417 let count = Arc::new(AtomicU32::new(0));
1418 let c2 = count.clone();
1419
1420 conn.on_commit(move || {
1421 c2.fetch_add(1, Ordering::Relaxed);
1422 CommitDecision::Proceed
1423 });
1424
1425 conn.immediate_transaction(|conn| {
1426 crate::sql_query("CREATE TABLE t1 (id INTEGER PRIMARY KEY)")
1427 .execute(conn)
1428 .unwrap();
1429 Ok::<_, crate::result::Error>(())
1430 })
1431 .unwrap();
1432
1433 assert_eq!(count.load(Ordering::Relaxed), 1);
1434 }
1435
1436 #[diesel_test_helper::test]
1437 fn on_commit_returning_true_forces_rollback() {
1438 let conn = &mut connection();
1439
1440 crate::sql_query("CREATE TABLE t_commit (id INTEGER PRIMARY KEY)")
1441 .execute(conn)
1442 .unwrap();
1443
1444 conn.on_commit(|| CommitDecision::Rollback);
1445
1446 let result = conn.immediate_transaction(|conn| {
1451 crate::sql_query("INSERT INTO t_commit (id) VALUES (1)")
1452 .execute(conn)
1453 .unwrap();
1454 Ok::<_, crate::result::Error>(())
1455 });
1456
1457 assert!(result.is_err());
1459
1460 conn.remove_commit_hook();
1462
1463 let cnt: i64 = crate::sql_query("SELECT COUNT(*) as c FROM t_commit")
1465 .get_result::<CountResult>(conn)
1466 .unwrap()
1467 .c;
1468 assert_eq!(cnt, 0);
1469 }
1470
1471 #[diesel_test_helper::test]
1472 fn replacing_commit_hook_drops_old() {
1473 let conn = &mut connection();
1474
1475 let old_count = Arc::new(AtomicU32::new(0));
1476 let new_count = Arc::new(AtomicU32::new(0));
1477 let oc = old_count.clone();
1478 let nc = new_count.clone();
1479
1480 conn.on_commit(move || {
1481 oc.fetch_add(1, Ordering::Relaxed);
1482 CommitDecision::Proceed
1483 });
1484
1485 conn.on_commit(move || {
1487 nc.fetch_add(1, Ordering::Relaxed);
1488 CommitDecision::Proceed
1489 });
1490
1491 conn.immediate_transaction(|conn| {
1492 crate::sql_query("CREATE TABLE t_replace (id INTEGER PRIMARY KEY)")
1493 .execute(conn)
1494 .unwrap();
1495 Ok::<_, crate::result::Error>(())
1496 })
1497 .unwrap();
1498
1499 assert_eq!(old_count.load(Ordering::Relaxed), 0);
1500 assert_eq!(new_count.load(Ordering::Relaxed), 1);
1501 }
1502
1503 #[diesel_test_helper::test]
1504 fn remove_commit_hook_disables_callback() {
1505 let conn = &mut connection();
1506
1507 let count = Arc::new(AtomicU32::new(0));
1508 let c2 = count.clone();
1509
1510 conn.on_commit(move || {
1511 c2.fetch_add(1, Ordering::Relaxed);
1512 CommitDecision::Proceed
1513 });
1514
1515 conn.remove_commit_hook();
1516
1517 conn.immediate_transaction(|conn| {
1518 crate::sql_query("CREATE TABLE t_rem (id INTEGER PRIMARY KEY)")
1519 .execute(conn)
1520 .unwrap();
1521 Ok::<_, crate::result::Error>(())
1522 })
1523 .unwrap();
1524
1525 assert_eq!(count.load(Ordering::Relaxed), 0);
1526 }
1527
1528 #[diesel_test_helper::test]
1529 fn on_rollback_fires_on_explicit_rollback() {
1530 let conn = &mut connection();
1531
1532 crate::sql_query("CREATE TABLE t_rb (id INTEGER PRIMARY KEY)")
1533 .execute(conn)
1534 .unwrap();
1535
1536 let count = Arc::new(AtomicU32::new(0));
1537 let c2 = count.clone();
1538
1539 conn.on_rollback(move || {
1540 c2.fetch_add(1, Ordering::Relaxed);
1541 });
1542
1543 let _ = conn.immediate_transaction(|conn| {
1545 crate::sql_query("INSERT INTO t_rb (id) VALUES (1)")
1546 .execute(conn)
1547 .unwrap();
1548 Err::<(), _>(crate::result::Error::RollbackTransaction)
1549 });
1550
1551 assert_eq!(count.load(Ordering::Relaxed), 1);
1552 }
1553
1554 #[diesel_test_helper::test]
1555 fn on_rollback_fires_when_commit_hook_forces_rollback() {
1556 let conn = &mut connection();
1557
1558 crate::sql_query("CREATE TABLE t_rb2 (id INTEGER PRIMARY KEY)")
1559 .execute(conn)
1560 .unwrap();
1561
1562 let rb_count = Arc::new(AtomicU32::new(0));
1563 let rb2 = rb_count.clone();
1564
1565 conn.on_commit(|| CommitDecision::Rollback);
1566 conn.on_rollback(move || {
1567 rb2.fetch_add(1, Ordering::Relaxed);
1568 });
1569
1570 let _ = conn.immediate_transaction(|conn| {
1571 crate::sql_query("INSERT INTO t_rb2 (id) VALUES (1)")
1572 .execute(conn)
1573 .unwrap();
1574 Ok::<_, crate::result::Error>(())
1575 });
1576
1577 assert_eq!(rb_count.load(Ordering::Relaxed), 1);
1579
1580 conn.remove_commit_hook();
1581 conn.remove_rollback_hook();
1582
1583 let cnt: i64 = crate::sql_query("SELECT COUNT(*) as c FROM t_rb2")
1585 .get_result::<CountResult>(conn)
1586 .unwrap()
1587 .c;
1588 assert_eq!(cnt, 0);
1589 }
1590
1591 #[diesel_test_helper::test]
1592 fn on_rollback_does_not_fire_on_connection_close() {
1593 let count = Arc::new(AtomicU32::new(0));
1594 let c2 = count.clone();
1595
1596 {
1597 let conn = &mut connection();
1598 conn.on_rollback(move || {
1599 c2.fetch_add(1, Ordering::Relaxed);
1600 });
1601 }
1603
1604 assert_eq!(count.load(Ordering::Relaxed), 0);
1605 }
1606
1607 #[diesel_test_helper::test]
1608 fn remove_rollback_hook_disables_callback() {
1609 let conn = &mut connection();
1610
1611 crate::sql_query("CREATE TABLE t_rem_rb (id INTEGER PRIMARY KEY)")
1612 .execute(conn)
1613 .unwrap();
1614
1615 let count = Arc::new(AtomicU32::new(0));
1616 let c2 = count.clone();
1617
1618 conn.on_rollback(move || {
1619 c2.fetch_add(1, Ordering::Relaxed);
1620 });
1621
1622 conn.remove_rollback_hook();
1623
1624 let _ = conn.immediate_transaction(|conn| {
1625 crate::sql_query("INSERT INTO t_rem_rb (id) VALUES (1)")
1626 .execute(conn)
1627 .unwrap();
1628 Err::<(), _>(crate::result::Error::RollbackTransaction)
1629 });
1630
1631 assert_eq!(count.load(Ordering::Relaxed), 0);
1632 }
1633
1634 const HEAVY_QUERY: &str = "WITH RECURSIVE c(x) AS \
1636 (SELECT 1 UNION ALL SELECT x + 1 FROM c WHERE x < 100000) SELECT count(*) FROM c";
1637
1638 #[diesel_test_helper::test]
1639 fn on_progress_interrupts_query() {
1640 let conn = &mut connection();
1641
1642 conn.on_progress(NonZeroU32::new(1).unwrap(), || ProgressDecision::Interrupt);
1643
1644 let result = crate::sql_query(HEAVY_QUERY).execute(conn);
1645 assert!(
1646 result.is_err(),
1647 "the query should be interrupted by the progress handler"
1648 );
1649 }
1650
1651 #[diesel_test_helper::test]
1652 fn remove_progress_handler_stops_interruption() {
1653 let conn = &mut connection();
1654
1655 conn.on_progress(NonZeroU32::new(1).unwrap(), || ProgressDecision::Interrupt);
1656 conn.remove_progress_handler();
1657
1658 let result = crate::sql_query(HEAVY_QUERY).execute(conn);
1660 assert!(
1661 result.is_ok(),
1662 "the query should complete after the handler is removed"
1663 );
1664 }
1665
1666 #[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
1677 fn wal_connection() -> (SqliteConnection, tempfile::TempDir) {
1680 let dir = tempfile::tempdir().unwrap();
1681 let path = dir.path().join("test.db");
1682 let mut conn = SqliteConnection::establish(path.to_str().unwrap()).unwrap();
1683 crate::sql_query("PRAGMA journal_mode=WAL")
1684 .execute(&mut conn)
1685 .unwrap();
1686 (conn, dir)
1687 }
1688
1689 #[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
1690 #[diesel_test_helper::test]
1691 fn on_wal_fires_in_wal_mode() {
1692 let (conn, _dir) = &mut wal_connection();
1693
1694 crate::sql_query("CREATE TABLE t_wal (id INTEGER PRIMARY KEY)")
1695 .execute(conn)
1696 .unwrap();
1697
1698 let events: Arc<std::sync::Mutex<Vec<(String, u32)>>> =
1699 Arc::new(std::sync::Mutex::new(Vec::new()));
1700 let events2 = events.clone();
1701
1702 conn.on_wal(move |_, db_name, n_pages| {
1703 events2.lock().unwrap().push((db_name.to_owned(), n_pages));
1704 });
1705
1706 crate::sql_query("INSERT INTO t_wal (id) VALUES (1)")
1707 .execute(conn)
1708 .unwrap();
1709
1710 let events = events.lock().unwrap();
1711 assert!(
1712 !events.is_empty(),
1713 "WAL hook should have fired at least once"
1714 );
1715 assert!(
1716 events.iter().all(|(db_name, _)| db_name == "main"),
1717 "db_name should always be \"main\""
1718 );
1719 assert!(
1720 events.iter().any(|(_, n_pages)| *n_pages > 0),
1721 "n_pages should be positive"
1722 );
1723 }
1724
1725 #[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
1726 #[diesel_test_helper::test]
1727 fn replacing_wal_hook_drops_old() {
1728 let (conn, _dir) = &mut wal_connection();
1729
1730 crate::sql_query("CREATE TABLE t_wal2 (id INTEGER PRIMARY KEY)")
1731 .execute(conn)
1732 .unwrap();
1733
1734 let old_count = Arc::new(AtomicU32::new(0));
1735 let new_count = Arc::new(AtomicU32::new(0));
1736
1737 let c_old = old_count.clone();
1738 conn.on_wal(move |_, _, _| {
1739 c_old.fetch_add(1, Ordering::Relaxed);
1740 });
1741
1742 let c_new = new_count.clone();
1744 conn.on_wal(move |_, _, _| {
1745 c_new.fetch_add(1, Ordering::Relaxed);
1746 });
1747
1748 crate::sql_query("INSERT INTO t_wal2 (id) VALUES (1)")
1749 .execute(conn)
1750 .unwrap();
1751
1752 let old_before = old_count.load(Ordering::Relaxed);
1754 crate::sql_query("INSERT INTO t_wal2 (id) VALUES (2)")
1755 .execute(conn)
1756 .unwrap();
1757 assert_eq!(
1758 old_count.load(Ordering::Relaxed),
1759 old_before,
1760 "old WAL hook should not fire after replacement"
1761 );
1762 assert!(
1763 new_count.load(Ordering::Relaxed) > 0,
1764 "new WAL hook should fire"
1765 );
1766 }
1767
1768 #[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
1769 #[diesel_test_helper::test]
1770 fn remove_wal_hook_disables_callback() {
1771 let (conn, _dir) = &mut wal_connection();
1772
1773 crate::sql_query("CREATE TABLE t_wal3 (id INTEGER PRIMARY KEY)")
1774 .execute(conn)
1775 .unwrap();
1776
1777 let count = Arc::new(AtomicU32::new(0));
1778 let c2 = count.clone();
1779
1780 conn.on_wal(move |_, _, _| {
1781 c2.fetch_add(1, Ordering::Relaxed);
1782 });
1783
1784 conn.remove_wal_hook();
1785
1786 crate::sql_query("INSERT INTO t_wal3 (id) VALUES (1)")
1787 .execute(conn)
1788 .unwrap();
1789
1790 assert_eq!(count.load(Ordering::Relaxed), 0);
1791 }
1792
1793 #[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
1794 #[diesel_test_helper::test]
1795 fn wal_hook_does_not_fire_in_default_journal_mode() {
1796 let dir = tempfile::tempdir().unwrap();
1799 let path = dir.path().join("test.db");
1800 let conn = &mut SqliteConnection::establish(path.to_str().unwrap()).unwrap();
1801
1802 crate::sql_query("CREATE TABLE t_wal4 (id INTEGER PRIMARY KEY)")
1803 .execute(conn)
1804 .unwrap();
1805
1806 let count = Arc::new(AtomicU32::new(0));
1807 let c2 = count.clone();
1808
1809 conn.on_wal(move |_, _, _| {
1810 c2.fetch_add(1, Ordering::Relaxed);
1811 });
1812
1813 crate::sql_query("INSERT INTO t_wal4 (id) VALUES (1)")
1814 .execute(conn)
1815 .unwrap();
1816
1817 assert_eq!(
1818 count.load(Ordering::Relaxed),
1819 0,
1820 "WAL hook should not fire when not in WAL mode"
1821 );
1822 }
1823
1824 #[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
1825 #[diesel_test_helper::test]
1826 fn on_wal_can_use_borrowed_connection() {
1827 let (conn, _dir) = &mut wal_connection();
1828
1829 crate::sql_query("CREATE TABLE t_wal_use (id INTEGER PRIMARY KEY)")
1830 .execute(conn)
1831 .unwrap();
1832
1833 let counts: Arc<std::sync::Mutex<Vec<i64>>> = Arc::new(std::sync::Mutex::new(Vec::new()));
1834 let counts2 = counts.clone();
1835
1836 conn.on_wal(move |conn, _db_name, _n_pages| {
1837 let c = crate::sql_query("SELECT COUNT(*) AS c FROM t_wal_use")
1840 .get_result::<CountResult>(conn)
1841 .unwrap()
1842 .c;
1843 counts2.lock().unwrap().push(c);
1844 });
1845
1846 crate::sql_query("INSERT INTO t_wal_use (id) VALUES (1)")
1847 .execute(conn)
1848 .unwrap();
1849
1850 let observed = counts.lock().unwrap();
1851 assert!(!observed.is_empty(), "WAL hook should have fired");
1852 assert!(
1853 observed.contains(&1),
1854 "callback should observe the committed row through the connection"
1855 );
1856 }
1857
1858 #[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
1859 #[diesel_test_helper::test]
1860 fn on_wal_callback_write_re_enters_hook() {
1861 let (conn, _dir) = &mut wal_connection();
1862
1863 crate::sql_query("CREATE TABLE t_wal_re (id INTEGER PRIMARY KEY)")
1864 .execute(conn)
1865 .unwrap();
1866 crate::sql_query("CREATE TABLE t_wal_log (id INTEGER PRIMARY KEY AUTOINCREMENT)")
1867 .execute(conn)
1868 .unwrap();
1869
1870 let calls = Arc::new(AtomicU32::new(0));
1871 let calls2 = calls.clone();
1872
1873 conn.on_wal(move |conn, _db_name, _n_pages| {
1874 let n = calls2.fetch_add(1, Ordering::Relaxed);
1875 if n == 0 {
1879 crate::sql_query("INSERT INTO t_wal_log DEFAULT VALUES")
1880 .execute(conn)
1881 .unwrap();
1882 }
1883 });
1884
1885 crate::sql_query("INSERT INTO t_wal_re (id) VALUES (1)")
1886 .execute(conn)
1887 .unwrap();
1888
1889 assert_eq!(
1892 calls.load(Ordering::Relaxed),
1893 2,
1894 "a committing write inside the callback re-enters the hook"
1895 );
1896
1897 let logged: i64 = crate::sql_query("SELECT COUNT(*) AS c FROM t_wal_log")
1899 .get_result::<CountResult>(conn)
1900 .unwrap()
1901 .c;
1902 assert_eq!(
1903 logged, 1,
1904 "the write performed inside the callback should persist"
1905 );
1906 }
1907
1908 #[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
1909 #[diesel_test_helper::test]
1910 fn on_wal_fires_once_per_transaction_commit() {
1911 let (conn, _dir) = &mut wal_connection();
1912
1913 crate::sql_query("CREATE TABLE t_wal_txn (id INTEGER PRIMARY KEY)")
1914 .execute(conn)
1915 .unwrap();
1916
1917 let count = Arc::new(AtomicU32::new(0));
1918 let c2 = count.clone();
1919
1920 conn.on_wal(move |_, _, _| {
1921 c2.fetch_add(1, Ordering::Relaxed);
1922 });
1923
1924 conn.immediate_transaction(|conn| {
1927 crate::sql_query("INSERT INTO t_wal_txn (id) VALUES (1)").execute(conn)?;
1928 crate::sql_query("INSERT INTO t_wal_txn (id) VALUES (2)").execute(conn)?;
1929 crate::sql_query("INSERT INTO t_wal_txn (id) VALUES (3)").execute(conn)?;
1930 Ok::<_, crate::result::Error>(())
1931 })
1932 .unwrap();
1933
1934 assert_eq!(
1935 count.load(Ordering::Relaxed),
1936 1,
1937 "the WAL hook should fire once per transaction commit, not per statement"
1938 );
1939 }
1940
1941 #[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
1947 #[diesel_test_helper::test]
1948 fn on_busy_handler_is_invoked_on_lock_contention() {
1949 let dir = tempfile::tempdir().unwrap();
1950 let path = dir.path().join("busy.db");
1951 let url = path.to_str().unwrap();
1952
1953 let mut holder = SqliteConnection::establish(url).unwrap();
1955 crate::sql_query("CREATE TABLE t_busy (id INTEGER PRIMARY KEY)")
1956 .execute(&mut holder)
1957 .unwrap();
1958 crate::sql_query("BEGIN IMMEDIATE")
1959 .execute(&mut holder)
1960 .unwrap();
1961
1962 let mut contender = SqliteConnection::establish(url).unwrap();
1965 let calls = Arc::new(AtomicU32::new(0));
1966 let calls2 = calls.clone();
1967 contender.on_busy(move |_retry_count| {
1968 calls2.fetch_add(1, Ordering::Relaxed);
1969 BusyDecision::GiveUp
1970 });
1971
1972 let result = crate::sql_query("INSERT INTO t_busy (id) VALUES (1)").execute(&mut contender);
1975
1976 assert!(
1977 result.is_err(),
1978 "the contended write should fail once the busy handler gives up"
1979 );
1980 assert!(
1981 calls.load(Ordering::Relaxed) >= 1,
1982 "the busy handler should have been invoked at least once"
1983 );
1984 }
1985
1986 #[diesel_test_helper::test]
1987 fn on_authorize_deny_rejects_statement() {
1988 let conn = &mut connection();
1989 crate::sql_query("CREATE TABLE auth_basic (id INTEGER PRIMARY KEY)")
1990 .execute(conn)
1991 .unwrap();
1992
1993 let calls = Arc::new(AtomicU32::new(0));
1994 let calls2 = calls.clone();
1995
1996 conn.on_authorize(move |_ctx| {
1997 calls2.fetch_add(1, Ordering::Relaxed);
1998 AuthorizerDecision::Deny
1999 });
2000
2001 let denied = crate::sql_query("SELECT id FROM auth_basic").execute(conn);
2004 assert!(denied.is_err(), "a denied statement should fail to prepare");
2005 assert!(
2006 calls.load(Ordering::Relaxed) > 0,
2007 "the authorizer callback should have been invoked"
2008 );
2009
2010 conn.remove_authorizer();
2012 crate::sql_query("SELECT id FROM auth_basic")
2013 .execute(conn)
2014 .unwrap();
2015 }
2016
2017 #[diesel_test_helper::test]
2018 fn remove_authorizer_re_prepares_cached_statements() {
2019 use crate::prelude::*;
2020 use crate::sqlite::AuthorizerContext;
2021
2022 crate::table! {
2023 auth_ignore_items (id) {
2024 id -> Integer,
2025 }
2026 }
2027
2028 let conn = &mut connection();
2029 crate::sql_query("CREATE TABLE auth_ignore_items (id INTEGER PRIMARY KEY)")
2030 .execute(conn)
2031 .unwrap();
2032 crate::sql_query("INSERT INTO auth_ignore_items (id) VALUES (42)")
2033 .execute(conn)
2034 .unwrap();
2035
2036 conn.on_authorize(|ctx| match ctx {
2040 AuthorizerContext::Read(_) => AuthorizerDecision::Ignore,
2041 _ => AuthorizerDecision::Allow,
2042 });
2043 let ignored = auth_ignore_items::table
2044 .select(auth_ignore_items::id.nullable())
2045 .load::<Option<i32>>(conn)
2046 .unwrap();
2047 assert_eq!(
2048 ignored,
2049 vec![None],
2050 "Ignore substitutes NULL for the column"
2051 );
2052
2053 conn.remove_authorizer();
2057 let restored = auth_ignore_items::table
2058 .select(auth_ignore_items::id.nullable())
2059 .load::<Option<i32>>(conn)
2060 .unwrap();
2061 assert_eq!(
2062 restored,
2063 vec![Some(42)],
2064 "after removing the authorizer the real value is returned"
2065 );
2066 }
2067
2068 #[diesel_test_helper::test]
2069 fn on_authorize_re_prepares_cached_statements() {
2070 use crate::prelude::*;
2071 use crate::sqlite::AuthorizerContext;
2072
2073 crate::table! {
2074 auth_replace_items (id) {
2075 id -> Integer,
2076 }
2077 }
2078
2079 let conn = &mut connection();
2080 crate::sql_query("CREATE TABLE auth_replace_items (id INTEGER PRIMARY KEY)")
2081 .execute(conn)
2082 .unwrap();
2083 crate::sql_query("INSERT INTO auth_replace_items (id) VALUES (42)")
2084 .execute(conn)
2085 .unwrap();
2086
2087 conn.on_authorize(|_ctx| AuthorizerDecision::Allow);
2090 let allowed = auth_replace_items::table
2091 .select(auth_replace_items::id.nullable())
2092 .load::<Option<i32>>(conn)
2093 .unwrap();
2094 assert_eq!(
2095 allowed,
2096 vec![Some(42)],
2097 "the allow-all authorizer returns the real value"
2098 );
2099
2100 conn.on_authorize(|ctx| match ctx {
2106 AuthorizerContext::Read(_) => AuthorizerDecision::Ignore,
2107 _ => AuthorizerDecision::Allow,
2108 });
2109 let ignored = auth_replace_items::table
2110 .select(auth_replace_items::id.nullable())
2111 .load::<Option<i32>>(conn)
2112 .unwrap();
2113 assert_eq!(
2114 ignored,
2115 vec![None],
2116 "after replacing the authorizer the new decision takes effect"
2117 );
2118 }
2119
2120 #[diesel_test_helper::test]
2121 fn on_trace_reports_statement_and_profile() {
2122 use std::sync::Mutex;
2123
2124 let conn = &mut connection();
2125 crate::sql_query("CREATE TABLE t_trace (id INTEGER PRIMARY KEY)")
2126 .execute(conn)
2127 .unwrap();
2128
2129 let stmts: Arc<Mutex<Vec<(String, bool)>>> = Arc::new(Mutex::new(Vec::new()));
2131 let profiled: Arc<Mutex<Vec<String>>> = Arc::new(Mutex::new(Vec::new()));
2132 let stmts2 = stmts.clone();
2133 let profiled2 = profiled.clone();
2134
2135 conn.on_trace(
2136 SqliteTraceFlags::STMT | SqliteTraceFlags::PROFILE,
2137 move |event| match event {
2138 SqliteTraceEvent::Statement { sql, readonly } => {
2139 stmts2.lock().unwrap().push((sql.to_owned(), readonly));
2140 }
2141 SqliteTraceEvent::Profile { sql, .. } => {
2142 profiled2.lock().unwrap().push(sql.to_owned());
2143 }
2144 _ => {}
2145 },
2146 );
2147
2148 crate::sql_query("SELECT id FROM t_trace")
2149 .execute(conn)
2150 .unwrap();
2151 crate::sql_query("INSERT INTO t_trace (id) VALUES (1)")
2152 .execute(conn)
2153 .unwrap();
2154
2155 let stmts = stmts.lock().unwrap();
2156 assert!(
2157 stmts
2158 .iter()
2159 .any(|(sql, ro)| sql.contains("SELECT id FROM t_trace") && *ro),
2160 "the SELECT should be traced and reported read-only"
2161 );
2162 assert!(
2163 stmts
2164 .iter()
2165 .any(|(sql, ro)| sql.contains("INSERT INTO t_trace") && !*ro),
2166 "the INSERT should be traced and reported not read-only"
2167 );
2168 assert!(
2169 !profiled.lock().unwrap().is_empty(),
2170 "at least one Profile event should have fired"
2171 );
2172 }
2173
2174 #[diesel_test_helper::test]
2175 fn remove_trace_stops_events() {
2176 use std::sync::atomic::AtomicUsize;
2177
2178 let conn = &mut connection();
2179 let count: Arc<AtomicUsize> = Arc::new(AtomicUsize::new(0));
2180 let count2 = count.clone();
2181
2182 conn.on_trace(SqliteTraceFlags::STMT, move |_event| {
2183 count2.fetch_add(1, Ordering::Relaxed);
2184 });
2185 crate::sql_query("SELECT 1").execute(conn).unwrap();
2186 let after_first = count.load(Ordering::Relaxed);
2187 assert!(after_first > 0, "trace should fire while registered");
2188
2189 conn.remove_trace();
2190 crate::sql_query("SELECT 1").execute(conn).unwrap();
2191 assert_eq!(
2192 count.load(Ordering::Relaxed),
2193 after_first,
2194 "no trace events should fire after remove_trace"
2195 );
2196 }
2197
2198 #[diesel_test_helper::test]
2199 fn on_collation_needed_registration_is_safe() {
2200 let conn = &mut connection();
2201
2202 conn.on_collation_needed(|_conn, _ctx| {});
2203 conn.remove_collation_needed_hook();
2204
2205 crate::sql_query("SELECT 1").execute(conn).unwrap();
2206 }
2207
2208 #[diesel_test_helper::test]
2209 fn replacing_collation_needed_hook_drops_old() {
2210 use std::sync::atomic::AtomicBool;
2211
2212 let conn = &mut connection();
2213
2214 let first_fired = Arc::new(AtomicBool::new(false));
2215 let first_fired2 = first_fired.clone();
2216 conn.on_collation_needed(move |_conn, _ctx| {
2217 first_fired2.store(true, Ordering::Relaxed);
2218 });
2219
2220 let second_fired = Arc::new(AtomicBool::new(false));
2221 let second_fired2 = second_fired.clone();
2222 conn.on_collation_needed(move |conn, ctx| {
2223 conn.register_collation(ctx.name, |a, b| a.cmp(b)).unwrap();
2224 second_fired2.store(true, Ordering::Relaxed);
2225 });
2226
2227 crate::sql_query("CREATE TABLE t_replace (x TEXT)")
2230 .execute(conn)
2231 .unwrap();
2232 crate::sql_query("CREATE INDEX i_replace ON t_replace (x COLLATE REPLACE_ME_COLL)")
2233 .execute(conn)
2234 .unwrap();
2235
2236 assert!(
2237 !first_fired.load(Ordering::Relaxed),
2238 "the replaced hook must not have been invoked"
2239 );
2240 assert!(
2241 second_fired.load(Ordering::Relaxed),
2242 "the current hook should have fired"
2243 );
2244 }
2245
2246 #[diesel_test_helper::test]
2247 fn collation_needed_fires_and_registers_collation() {
2248 use crate::sqlite::SqliteTextRep;
2249 use std::sync::atomic::AtomicBool;
2250
2251 let conn = &mut connection();
2252
2253 let fired = Arc::new(AtomicBool::new(false));
2256 let saw_name = Arc::new(AtomicBool::new(false));
2257 let saw_utf8 = Arc::new(AtomicBool::new(false));
2258 let fired2 = fired.clone();
2259 let saw_name2 = saw_name.clone();
2260 let saw_utf8_2 = saw_utf8.clone();
2261
2262 conn.on_collation_needed(move |conn, ctx| {
2263 fired2.store(true, Ordering::Relaxed);
2264 if ctx.name == "MYCOLL" {
2265 saw_name2.store(true, Ordering::Relaxed);
2266 }
2267 if ctx.text_rep == SqliteTextRep::Utf8 {
2268 saw_utf8_2.store(true, Ordering::Relaxed);
2269 }
2270 conn.register_collation(ctx.name, |a, b| a.cmp(b)).unwrap();
2271 });
2272
2273 crate::sql_query("CREATE TABLE t_fires (x TEXT)")
2275 .execute(conn)
2276 .unwrap();
2277 crate::sql_query("CREATE INDEX i_fires ON t_fires (x COLLATE MYCOLL)")
2278 .execute(conn)
2279 .unwrap();
2280
2281 assert!(
2282 fired.load(Ordering::Relaxed),
2283 "collation_needed callback should fire"
2284 );
2285 assert!(
2286 saw_name.load(Ordering::Relaxed),
2287 "callback should observe the exact missing collation name"
2288 );
2289 assert!(
2290 saw_utf8.load(Ordering::Relaxed),
2291 "SQLite should request the UTF-8 encoding for a plain TEXT column"
2292 );
2293 }
2294
2295 #[diesel_test_helper::test]
2296 fn remove_collation_needed_hook_without_registration_is_noop() {
2297 let conn = &mut connection();
2298
2299 conn.remove_collation_needed_hook();
2300 crate::sql_query("SELECT 1").execute(conn).unwrap();
2301 }
2302
2303 #[diesel_test_helper::test]
2304 fn callback_can_be_reentered_from_within_its_own_body() {
2305 use std::sync::atomic::AtomicBool;
2306
2307 let conn = &mut connection();
2308
2309 let saw_outer = Arc::new(AtomicBool::new(false));
2315 let saw_inner = Arc::new(AtomicBool::new(false));
2316 let saw_outer2 = saw_outer.clone();
2317 let saw_inner2 = saw_inner.clone();
2318
2319 conn.on_collation_needed(move |conn, ctx| {
2320 if ctx.name.eq_ignore_ascii_case("OUTER_COLL") {
2321 saw_outer2.store(true, Ordering::Relaxed);
2322 conn.register_collation("OUTER_COLL", |a, b| a.cmp(b))
2323 .unwrap();
2324 crate::sql_query("CREATE TABLE t_reent_inner (x TEXT)")
2328 .execute(conn)
2329 .unwrap();
2330 crate::sql_query(
2331 "CREATE INDEX i_reent_inner ON t_reent_inner (x COLLATE INNER_COLL)",
2332 )
2333 .execute(conn)
2334 .unwrap();
2335 } else if ctx.name.eq_ignore_ascii_case("INNER_COLL") {
2336 saw_inner2.store(true, Ordering::Relaxed);
2337 conn.register_collation("INNER_COLL", |a, b| a.cmp(b))
2338 .unwrap();
2339 } else {
2340 panic!("unexpected collation name: {}", ctx.name);
2341 }
2342 });
2343
2344 crate::sql_query("CREATE TABLE t_reent_outer (x TEXT)")
2345 .execute(conn)
2346 .unwrap();
2347 crate::sql_query("CREATE INDEX i_reent_outer ON t_reent_outer (x COLLATE OUTER_COLL)")
2348 .execute(conn)
2349 .unwrap();
2350
2351 assert!(
2352 saw_outer.load(Ordering::Relaxed),
2353 "outer callback should fire for OUTER_COLL"
2354 );
2355 assert!(
2356 saw_inner.load(Ordering::Relaxed),
2357 "inner callback should fire re-entrantly from within the outer body"
2358 );
2359 }
2360
2361 #[diesel_test_helper::test]
2362 fn remove_collation_needed_hook_stops_future_callbacks() {
2363 let conn = &mut connection();
2364 let calls: Arc<AtomicU32> = Arc::new(AtomicU32::new(0));
2365 let calls2 = calls.clone();
2366
2367 conn.on_collation_needed(move |conn, ctx| {
2368 calls2.fetch_add(1, Ordering::Relaxed);
2369 conn.register_collation(ctx.name, |a, b| a.cmp(b)).unwrap();
2370 });
2371
2372 crate::sql_query("CREATE TABLE t_stop (x TEXT)")
2374 .execute(conn)
2375 .unwrap();
2376 crate::sql_query("CREATE INDEX i_stop ON t_stop (x COLLATE MYCOLL_STOP)")
2377 .execute(conn)
2378 .unwrap();
2379 let after_first = calls.load(Ordering::Relaxed);
2380 assert!(after_first > 0, "callback should fire while registered");
2381
2382 conn.remove_collation_needed_hook();
2383
2384 let result = crate::sql_query("CREATE INDEX i_stop2 ON t_stop (x COLLATE YOURCOLL_STOP)")
2389 .execute(conn);
2390 assert!(
2391 result.is_err(),
2392 "SQL referencing an unregistered collation should fail after remove"
2393 );
2394 assert_eq!(
2395 calls.load(Ordering::Relaxed),
2396 after_first,
2397 "callback must not fire after remove_collation_needed_hook"
2398 );
2399 }
2400}