1use rusqlite::Connection;
2use thiserror::Error;
3
4const TRIGGER_NAME: &str = "prevent_delete_on_analytics_events";
5
6#[derive(Debug, Error)]
7pub enum TriggerError {
8 #[error("trigger already exists: {0}")]
9 AlreadyExists(&'static str),
10 #[error("trigger does not exist: {0}")]
11 NotFound(&'static str),
12 #[error("sqlite error: {0}")]
13 Sqlite(#[from] rusqlite::Error),
14}
15
16fn trigger_exists(conn: &Connection) -> rusqlite::Result<bool> {
17 let mut stmt =
18 conn.prepare("SELECT 1 FROM sqlite_master WHERE type='trigger' AND name = ?1 LIMIT 1;")?;
19 let mut rows = stmt.query([TRIGGER_NAME])?;
20 Ok(rows.next()?.is_some())
21}
22
23pub fn install_analytics_events_trigger(conn: &Connection) -> Result<(), TriggerError> {
24 if trigger_exists(conn)? {
25 return Err(TriggerError::AlreadyExists(TRIGGER_NAME));
26 }
27 conn.execute_batch(
28 "CREATE TRIGGER prevent_delete_on_analytics_events
29 BEFORE DELETE ON AnalyticsEvents
30 BEGIN
31 SELECT RAISE(ABORT, 'Deletion is not allowed on AnalyticsEvents table');
32 END;",
33 )?;
34 Ok(())
35}
36
37pub fn remove_analytics_events_trigger(conn: &Connection) -> Result<(), TriggerError> {
38 if !trigger_exists(conn)? {
39 return Err(TriggerError::NotFound(TRIGGER_NAME));
40 }
41 conn.execute_batch("DROP TRIGGER prevent_delete_on_analytics_events;")?;
42 Ok(())
43}
44
45#[cfg(test)]
46mod tests {
47 use super::*;
48
49 fn setup_test_db() -> Connection {
50 let conn = Connection::open_in_memory().unwrap();
51 conn.execute_batch(
52 "CREATE TABLE AnalyticsEvents (
53 Id TEXT PRIMARY KEY,
54 Type TEXT NOT NULL,
55 Timestamp TEXT NOT NULL,
56 Attributes TEXT,
57 Metrics TEXT
58 );",
59 )
60 .unwrap();
61 conn
62 }
63
64 #[test]
65 fn test_trigger_install_and_delete_block() {
66 let conn = setup_test_db();
67 install_analytics_events_trigger(&conn).unwrap();
68 conn.execute(
69 "INSERT INTO AnalyticsEvents (Id, Type, Timestamp, Attributes, Metrics) VALUES (?1, ?2, ?3, ?4, ?5)",
70 ["row1", "AppStart", "2023-01-01T00:00:00Z", "", ""],
71 )
72 .unwrap();
73 let err = conn.execute("DELETE FROM AnalyticsEvents", []).unwrap_err();
74 let err_msg = err.to_string();
75 assert!(err_msg.contains("Deletion is not allowed"));
76 }
77
78 #[test]
79 fn test_trigger_remove() {
80 let conn = setup_test_db();
81 install_analytics_events_trigger(&conn).unwrap();
82 remove_analytics_events_trigger(&conn).unwrap();
83 conn.execute("DELETE FROM AnalyticsEvents", []).unwrap();
84 }
85
86 #[test]
87 fn test_trigger_install_existing() {
88 let conn = setup_test_db();
89 install_analytics_events_trigger(&conn).unwrap();
90 let err = install_analytics_events_trigger(&conn).unwrap_err();
91 assert!(matches!(err, TriggerError::AlreadyExists(_)));
92 }
93
94 #[test]
95 fn test_trigger_remove_missing() {
96 let conn = setup_test_db();
97 let err = remove_analytics_events_trigger(&conn).unwrap_err();
98 assert!(matches!(err, TriggerError::NotFound(_)));
99 }
100}