1use sqlite::{ConnectionThreadSafe, State};
2
3#[derive(Debug)]
4pub struct Counter {
5 pub name: String,
6 pub count: i64,
7 pub step: i64,
8 pub template: String,
9}
10
11impl Counter {
12 pub fn new(name: &str) -> Counter {
13 Counter {
14 name: name.to_string(),
15 count: 0,
16 step: 1,
17 template: String::from("{}"),
18 }
19 }
20
21 pub fn set_default(&self, conn: &ConnectionThreadSafe) -> sqlite::Result<()> {
22 conn.execute("DELETE FROM default_counter;")?;
23 let mut stmt = conn.prepare(
24 "INSERT INTO default_counter (name, timestamp) VALUES (?, CURRENT_TIMESTAMP);",
25 )?;
26 stmt.bind((1, self.name.as_str()))?;
27 stmt.next()?;
28 Ok(())
29 }
30
31 pub fn get_default(conn: &ConnectionThreadSafe) -> sqlite::Result<Option<String>> {
32 let mut stmt =
33 conn.prepare("SELECT name FROM default_counter ORDER BY timestamp DESC LIMIT 1;")?;
34 if let State::Row = stmt.next()? {
35 Ok(Some(stmt.read::<String, usize>(0)?))
36 } else {
37 Ok(None)
38 }
39 }
40
41 pub fn insert(&self, conn: &ConnectionThreadSafe) -> sqlite::Result<()> {
42 let mut stmt =
43 conn.prepare("INSERT INTO counters (name, count, step, template) VALUES (?, ?, ?, ?)")?;
44 stmt.bind((1, self.name.as_str()))?;
45 stmt.bind((2, self.count))?;
46 stmt.bind((3, self.step))?;
47 stmt.bind((4, self.template.as_str()))?;
48 stmt.next()?;
49
50 Ok(())
51 }
52
53 pub fn delete(conn: &ConnectionThreadSafe, name: &str) -> sqlite::Result<()> {
54 let mut stmt = conn.prepare("DELETE FROM counters WHERE name = ?")?;
55 stmt.bind((1, name))?;
56 stmt.next()?;
57 Ok(())
58 }
59
60 pub fn get(conn: &ConnectionThreadSafe, name: &str) -> sqlite::Result<Option<Counter>> {
61 let mut stmt =
62 conn.prepare("SELECT name, count, step, template FROM counters WHERE name = ?")?;
63 stmt.bind((1, name))?;
64
65 if let State::Row = stmt.next()? {
66 Ok(Some(Counter {
67 name: stmt.read::<String, _>(0)?.to_string(),
68 count: stmt.read::<i64, _>(1)?,
69 step: stmt.read::<i64, _>(2)?,
70 template: stmt.read::<String, _>(3)?.to_string(),
71 }))
72 } else {
73 Ok(None)
74 }
75 }
76
77 pub fn get_all(conn: &ConnectionThreadSafe) -> sqlite::Result<Vec<Counter>> {
78 let mut stmt = conn.prepare("SELECT name, count, step, template FROM counters")?;
79 let mut counters = Vec::new();
80
81 while let State::Row = stmt.next()? {
82 counters.push(Counter {
83 name: stmt.read::<String, usize>(0)?,
84 count: stmt.read::<i64, usize>(1)?,
85 step: stmt.read::<i64, usize>(2)?,
86 template: stmt.read::<String, usize>(3)?,
87 });
88 }
89
90 Ok(counters)
91 }
92
93 pub fn update(&self, conn: &ConnectionThreadSafe) -> sqlite::Result<()> {
94 let mut stmt =
95 conn.prepare("UPDATE counters SET count = ?, step = ?, template = ? WHERE name = ?")?;
96 stmt.bind((1, self.count))?;
97 stmt.bind((2, self.step))?;
98 stmt.bind((3, self.template.as_str()))?;
99 stmt.bind((4, self.name.as_str()))?;
100 stmt.next()?;
101 Ok(())
102 }
103}
104
105#[cfg(test)]
106mod tests {
107 use super::*;
108 use crate::database::Connection;
109 use tempfile::TempDir;
110
111 fn fresh_db() -> (TempDir, Connection) {
112 let dir = TempDir::new().unwrap();
113 let path = dir.path().join("test.db");
114 let conn = Connection::new(&path.to_string_lossy()).unwrap();
115 (dir, conn)
116 }
117
118 #[test]
119 fn new_has_sensible_defaults() {
120 let c = Counter::new("foo");
121 assert_eq!(c.name, "foo");
122 assert_eq!(c.count, 0);
123 assert_eq!(c.step, 1);
124 assert_eq!(c.template, "{}");
125 }
126
127 #[test]
128 fn insert_and_get_round_trip() {
129 let (_dir, conn) = fresh_db();
130 let c = Counter {
131 name: "foo".into(),
132 count: 7,
133 step: 2,
134 template: "x-{}".into(),
135 };
136 c.insert(conn.get()).unwrap();
137
138 let loaded = Counter::get(conn.get(), "foo").unwrap().unwrap();
139 assert_eq!(loaded.name, "foo");
140 assert_eq!(loaded.count, 7);
141 assert_eq!(loaded.step, 2);
142 assert_eq!(loaded.template, "x-{}");
143 }
144
145 #[test]
146 fn get_missing_returns_none() {
147 let (_dir, conn) = fresh_db();
148 assert!(Counter::get(conn.get(), "nope").unwrap().is_none());
149 }
150
151 #[test]
152 fn update_persists_changes() {
153 let (_dir, conn) = fresh_db();
154 let mut c = Counter::new("foo");
155 c.insert(conn.get()).unwrap();
156 c.count = 42;
157 c.step = 5;
158 c.update(conn.get()).unwrap();
159
160 let loaded = Counter::get(conn.get(), "foo").unwrap().unwrap();
161 assert_eq!(loaded.count, 42);
162 assert_eq!(loaded.step, 5);
163 }
164
165 #[test]
166 fn delete_removes_row() {
167 let (_dir, conn) = fresh_db();
168 Counter::new("foo").insert(conn.get()).unwrap();
169 Counter::delete(conn.get(), "foo").unwrap();
170 assert!(Counter::get(conn.get(), "foo").unwrap().is_none());
171 }
172
173 #[test]
174 fn get_all_returns_every_counter() {
175 let (_dir, conn) = fresh_db();
176 Counter::new("a").insert(conn.get()).unwrap();
177 Counter::new("b").insert(conn.get()).unwrap();
178 let mut names: Vec<String> = Counter::get_all(conn.get())
179 .unwrap()
180 .into_iter()
181 .map(|c| c.name)
182 .collect();
183 names.sort();
184 assert_eq!(names, vec!["a", "b", "tally"]);
185 }
186
187 #[test]
188 fn set_default_keeps_default_counter_single_row() {
189 let (_dir, conn) = fresh_db();
190 Counter::new("a").insert(conn.get()).unwrap();
191 Counter::new("b").insert(conn.get()).unwrap();
192
193 Counter::new("a").set_default(conn.get()).unwrap();
194 Counter::new("b").set_default(conn.get()).unwrap();
195 Counter::new("a").set_default(conn.get()).unwrap();
196
197 assert_eq!(
198 Counter::get_default(conn.get()).unwrap().as_deref(),
199 Some("a")
200 );
201
202 let mut stmt = conn
203 .get()
204 .prepare("SELECT COUNT(*) FROM default_counter;")
205 .unwrap();
206 stmt.next().unwrap();
207 let count: i64 = stmt.read(0).unwrap();
208 assert_eq!(count, 1);
209 }
210}