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