• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

keruDev / postit-rs / #11

17 Apr 2025 07:52PM UTC coverage: 97.97% (+0.4%) from 97.6%
#11

push

keruDev
🧹 Clippy lint for stable toolchain

0 of 1 new or added line in 1 file covered. (0.0%)

13 existing lines in 4 files now uncovered.

772 of 788 relevant lines covered (97.97%)

1.29 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

92.13
/src/persisters/db/sqlite.rs
1
//! Utilities to handle `SQLite` files.
2
//!
3
//! The `Sqlite` struct implements the [`DbPersister`] trait.
4

5
use sqlite::{Connection, State, Statement};
6

7
use crate::models::{Task, Todo};
8
use crate::traits::DbPersister;
9
use crate::{Action, Config};
10

11
/// Representation of a `SQLite` database.
12
pub struct Sqlite {
13
    /// Connection string used to connect to the `SQLite` file.
14
    conn_str: String,
15
    /// Connection to the `SQLite` file.
16
    connection: Connection,
17
}
18

19
impl Clone for Sqlite {
20
    fn clone(&self) -> Self {
1✔
21
        Self {
22
            conn_str: self.conn_str.clone(),
1✔
23
            connection: sqlite::open(&self.conn_str).unwrap(),
2✔
24
        }
25
    }
26
}
27

28
impl Sqlite {
29
    /// Creates a `Sqlite` instance from a connection string.
30
    ///
31
    /// # Panics
32
    /// If the path can't be converted to str.
33
    /// If a connection to the `SQLite` file can't be opened.
34
    pub fn from(conn: &str) -> Self {
1✔
35
        let path = Config::build_path(conn);
1✔
36
        let path_str = path.to_str().unwrap();
2✔
37

38
        let instance = Self {
39
            conn_str: String::from(path_str),
1✔
40
            connection: sqlite::open(path).unwrap(),
2✔
41
        };
42

43
        if !instance.exists() {
2✔
44
            instance.create();
2✔
45
        }
46

47
        instance
1✔
48
    }
49

50
    /// Returns the desired ids format to be used in a query.
51
    pub fn format_ids(&self, ids: &[u32]) -> String {
1✔
52
        ids.iter()
2✔
53
            .map(|&n| n.to_string())
2✔
54
            .collect::<Vec<String>>()
55
            .join(", ")
56
    }
57

58
    /// Reads one row from the current statement.
59
    ///
60
    /// # Panics
61
    /// If a value can't be unwrapped.
62
    pub fn read_row(&self, stmt: &Statement) -> String {
1✔
63
        format!(
4✔
64
            "{},{},{},{}",
65
            stmt.read::<i64, _>("id").unwrap(),
1✔
66
            stmt.read::<String, _>("content").unwrap(),
1✔
67
            stmt.read::<String, _>("priority").unwrap(),
1✔
68
            stmt.read::<String, _>("checked").unwrap()
1✔
69
        )
70
    }
71

72
    /// Resets the autoincrement value.
73
    ///
74
    /// # Panics
75
    /// If a value can't be unwrapped.
76
    pub fn reset_autoincrement(&self, table: &str) {
1✔
77
        #[rustfmt::skip]
78
        let query = format!("
1✔
79
            UPDATE sqlite_sequence
80
            SET SEQ=0
81
            WHERE NAME='{table}'
82
        ");
83

84
        let mut stmt = self.connection.prepare(query).unwrap();
1✔
85

86
        if let Err(e) = stmt.next() {
2✔
UNCOV
87
            eprintln!("Error while cleaning table: {e}");
×
88
        }
89
    }
90
}
91

92
impl DbPersister for Sqlite {
93
    fn conn(&self) -> String {
1✔
94
        self.conn_str.clone()
1✔
95
    }
96

97
    fn boxed(self) -> Box<dyn DbPersister> {
1✔
98
        Box::new(self)
1✔
99
    }
100

101
    /// Checks if a table exists.
102
    ///
103
    /// # Panics
104
    /// In case the statement can't be prepared.
105
    fn exists(&self) -> bool {
1✔
106
        #[rustfmt::skip]
107
        let mut stmt = self.connection.prepare("
1✔
108
            SELECT *
109
            FROM sqlite_master
110
            WHERE type='table'
111
              AND name='tasks'
112
        ").unwrap();
113

114
        let mut result = vec![];
2✔
115

116
        while matches!(stmt.next(), Ok(State::Row)) {
2✔
117
            result.push(stmt.read::<String, _>("name").unwrap().to_string());
1✔
118
        }
119

120
        !result.is_empty()
1✔
121
    }
122

123
    fn count(&self) -> u32 {
1✔
124
        if !self.exists() {
1✔
UNCOV
125
            return 0_u32;
×
126
        }
127

128
        #[rustfmt::skip]
129
        let mut stmt = self.connection.prepare("
1✔
130
            SELECT COUNT(*)
131
              AS count
132
            FROM tasks
133
        ").unwrap();
134

135
        if matches!(stmt.next(), Ok(State::Row)) {
3✔
136
            stmt.read::<i64, _>("count").unwrap_or(0) as u32
1✔
137
        } else {
UNCOV
138
            0
×
139
        }
140
    }
141

142
    fn tasks(&self) -> Vec<Task> {
1✔
143
        self.select().iter().map(|row| Task::from(row)).collect()
4✔
144
    }
145

146
    fn create(&self) {
1✔
147
        #[rustfmt::skip]
148
        self.connection.execute("
1✔
149
            CREATE TABLE IF NOT EXISTS tasks (
150
                id          INTEGER PRIMARY KEY AUTOINCREMENT,
151
                content     TEXT NOT NULL,
152
                priority    TEXT NOT NULL,
153
                checked     BOOLEAN NOT NULL CHECK (checked IN (0, 1))
154
            )
155
        ").unwrap();
156
    }
157

158
    fn select(&self) -> Vec<String> {
1✔
159
        let mut stmt = self.connection.prepare("SELECT * FROM tasks").unwrap();
1✔
160

161
        let mut result = vec![];
2✔
162

163
        while matches!(stmt.next(), Ok(State::Row)) {
2✔
164
            result.push(self.read_row(&stmt));
1✔
165
        }
166

167
        result
1✔
168
    }
169

170
    fn insert(&self, todo: &Todo) {
1✔
171
        todo.tasks.iter().for_each(|task| {
2✔
172
            #[rustfmt::skip]
173
            let mut stmt = self.connection.prepare("
1✔
174
                INSERT INTO tasks (content, priority, checked)
175
                VALUES (?, ?, ?)
176
            ").unwrap();
177

178
            #[rustfmt::skip]
179
            stmt.bind(&[
2✔
180
                &task.content,
181
                &*task.priority,
2✔
182
                &*i32::from(task.checked).to_string()
1✔
183
            ][..]).unwrap();
2✔
184

185
            if let Err(e) = stmt.next() {
1✔
UNCOV
186
                eprintln!("Error while inserting value: {e}");
×
187
            }
188
        });
189
    }
190

191
    fn update(&self, todo: &Todo, ids: &[u32], action: Action) {
1✔
192
        if matches!(action, Action::Drop) {
1✔
193
            return self.delete(ids);
1✔
194
        }
195

196
        let (field, value) = match action {
2✔
197
            Action::Check => ("checked", "1"),
1✔
198
            Action::Uncheck => ("checked", "0"),
1✔
199
            Action::SetContent => ("content", todo.get(ids)[0].content.as_str()),
1✔
200
            Action::SetPriority => ("priority", todo.get(ids)[0].priority.to_str()),
1✔
201
            Action::Drop => unreachable!(),
202
        };
203

204
        let ids = self.format_ids(ids);
1✔
205

206
        #[rustfmt::skip]
207
        let query = format!("
2✔
208
            UPDATE tasks
209
            SET {field} = \"{value}\"
210
            WHERE id
211
            IN ({ids})
212
        ");
213

214
        let mut stmt = self.connection.prepare(query).unwrap();
1✔
215

216
        if let Err(e) = stmt.next() {
2✔
UNCOV
217
            eprintln!("Error while updating value: {e}");
×
218
        }
219
    }
220

221
    fn delete(&self, ids: &[u32]) {
1✔
222
        let ids = self.format_ids(ids);
1✔
223

224
        #[rustfmt::skip]
225
        let query = format!("
2✔
226
            DELETE FROM tasks
227
            WHERE id
228
            IN ({ids})
229
        ");
230

231
        let mut stmt = self.connection.prepare(query).unwrap();
1✔
232

233
        if let Err(e) = stmt.next() {
2✔
UNCOV
234
            eprintln!("Error while dropping value: {e}");
×
235
        }
236
    }
237

238
    fn drop_database(&self) {
1✔
239
        std::fs::remove_file(self.conn()).expect("Couldn't drop the database");
1✔
240
    }
241

242
    fn clean(&self) {
1✔
243
        let table = String::from("tasks");
1✔
244
        let query = format!("DELETE FROM {table}");
2✔
245

246
        let mut stmt = self.connection.prepare(query).unwrap();
1✔
247

248
        if let Err(e) = stmt.next() {
2✔
UNCOV
249
            eprintln!("Error while cleaning table: {e}");
×
250
        }
251

252
        self.reset_autoincrement(&table);
2✔
253
    }
254
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc