pkg_rs/
db.rs

1use std::{collections::HashMap, fmt::Debug, path::PathBuf};
2
3use miette::{Diagnostic, IntoDiagnostic, Result};
4use rusqlite::{Connection, Error as RusqliteError};
5use thiserror::Error;
6
7use crate::input::PkgDeclaration;
8
9pub type EntryPoint = PathBuf;
10
11#[derive(Debug)]
12pub enum PkgType {
13    SingleExecutable,
14    Directory(EntryPoint),
15}
16
17#[derive(Debug)]
18pub struct Version {
19    pub first_cell: String,
20    pub second_cell: String,
21    pub third_cell: String,
22}
23
24pub type Verstion = Version;
25
26#[derive(Debug)]
27pub struct Pkg {
28    pub name: String,
29    pub version: Version,
30    pub path: PathBuf,
31    pub pkg_type: PkgType,
32}
33
34#[derive(Debug)]
35pub struct Db {
36    pub conn: Connection,
37    pub path: PathBuf,
38}
39
40#[derive(Error, Debug, Diagnostic)]
41pub enum DbError {
42    #[error(transparent)]
43    #[diagnostic(code(db::sqlite_error))]
44    SqliteError(#[from] RusqliteError),
45
46    #[error(transparent)]
47    #[diagnostic(code(db::io_error))]
48    IoError(#[from] std::io::Error),
49
50    #[error("Invalid UTF-8 in package path")]
51    #[diagnostic(code(db::invalid_utf8))]
52    InvalidPath,
53}
54
55mod sql {
56    pub const CREATE_PKGS_TABLE: &str = r#"
57    CREATE TABLE IF NOT EXISTS packages (
58        name TEXT NOT NULL,
59        version TEXT NOT NULL,
60        path TEXT NOT NULL,
61        pkg_type TEXT NOT NULL,
62        entry_point TEXT NOT NULL,
63        bridge TEXT NOT NULL,
64        PRIMARY KEY (name)
65    );
66    "#; // NOTE: installing a package twice with or without a deficient version are not allowd in this implementing. and this is just my decision
67    pub const GET_PKGS: &str = r#"
68    SELECT name, version, path, pkg_type, entry_point FROM packages;
69    "#;
70
71    pub const GET_PKGS_BY_NAME: &str = r#"
72    SELECT name, version, path, pkg_type FROM packages WHERE name = ?;
73    "#;
74
75    pub const GET_PKGS_BY_NAMES: &str = r#"
76    SELECT name, version, path, pkg_type, entry_point FROM packages WHERE name IN ({});
77    "#;
78    pub const INSERT_PKGS: &str = r#"
79    INSERT INTO packages (name, version, path, pkg_type, entry_point, bridge)
80    VALUES (?, ?, ?, ?, ?, ?);
81    "#;
82    pub const DELETE_PKGS: &str = r#"
83    DELETE FROM packages WHERE name = ?;
84    "#;
85    pub const GET_PKG_BRIDGE_BY_NAME: &str = r#"
86    SELECT bridge FROM packages WHERE name = ?;
87    "#;
88    pub const GET_PKGS_BY_BRIDGE: &str = r#"
89    SELECT name, version, path, pkg_type, entry_point FROM packages WHERE bridge = ?;
90    "#;
91    pub const GET_BRIDGES: &str = r#"
92    SELECT bridge FROM packages GROUP BY bridge;
93    "#;
94}
95
96impl Pkg {
97    // NOTE: if pkg is removed form the input, so it's logical to loss the
98    // attributes, i think that's not a bug
99    pub fn to_pkg_declaration_with_empty_attributes(&self) -> PkgDeclaration {
100        PkgDeclaration {
101            name: self.name.clone(),
102            input: self.path.to_str().unwrap().to_string(),
103            attributes: HashMap::new(),
104        }
105    }
106}
107
108impl Db {
109    pub fn new(path: &PathBuf) -> Result<Self> {
110        let parent = path.parent().ok_or(DbError::InvalidPath)?;
111        std::fs::create_dir_all(parent).into_diagnostic()?;
112
113        let conn = Connection::open(path).into_diagnostic()?;
114
115        conn.execute(sql::CREATE_PKGS_TABLE, []).into_diagnostic()?;
116
117        Ok(Self {
118            conn,
119            path: path.clone(),
120        })
121    }
122
123    pub fn get_bridges(&self) -> Result<Vec<String>> {
124        let mut stmt = self.conn.prepare(sql::GET_BRIDGES).into_diagnostic()?;
125        let rows = stmt
126            .query_map([], |row| {
127                let bridge: String = row.get(0)?;
128                Ok(bridge)
129            })
130            .into_diagnostic()?;
131
132        let mut bridges = Vec::new();
133
134        for bridge in rows {
135            bridges.push(bridge.into_diagnostic()?);
136        }
137        Ok(bridges)
138    }
139
140    pub fn get_pkg_bridge_by_name(&self, pkg_name: &str) -> Result<String> {
141        let mut stmt = self
142            .conn
143            .prepare(sql::GET_PKG_BRIDGE_BY_NAME)
144            .into_diagnostic()?;
145
146        let bridge = stmt
147            .query_row([&pkg_name], |row| row.get(0))
148            .into_diagnostic()?;
149
150        Ok(bridge)
151    }
152
153    // wiil to be clean i don't understand everything here because my code make a lifetime
154    // error so ai fix it with this code that has this weird 'a syntax
155    pub fn which_pkgs_are_installed<'a>(&'a self, pkgs: &'a [String]) -> Result<Vec<&'a String>> {
156        let mut installed_pkgs = Vec::new();
157        let mut stmt = self.conn.prepare(sql::GET_PKGS_BY_NAME).into_diagnostic()?;
158
159        for pkg in pkgs {
160            // We only care if the query returns any rows, not the actual data
161            let exists = stmt.exists([&pkg]).into_diagnostic()?;
162            if exists {
163                installed_pkgs.push(pkg);
164            }
165        }
166
167        Ok(installed_pkgs)
168    }
169
170    pub fn install_bridge_pkgs(&self, pkgs: &[&Pkg], bridge: &String) -> Result<()> {
171        let mut stmt = self.conn.prepare(sql::INSERT_PKGS).into_diagnostic()?;
172
173        for pkg in pkgs {
174            let pkg_version = format!(
175                "{}.{}.{}",
176                pkg.version.first_cell, pkg.version.second_cell, pkg.version.third_cell
177            );
178
179            let pkg_type = match &pkg.pkg_type {
180                PkgType::SingleExecutable => "SingleExecutable".to_string(),
181                PkgType::Directory(_) => "Directory".to_string(),
182            };
183
184            let pkg_path = pkg.path.to_str().ok_or(DbError::InvalidPath)?.to_string();
185
186            let entry_point = match &pkg.pkg_type {
187                PkgType::SingleExecutable => pkg_path.to_string(), // Convert &str to String
188                PkgType::Directory(ep) => ep.to_string_lossy().into_owned(), // Handle path conversion
189            };
190
191            stmt.execute([
192                &pkg.name,
193                &pkg_version,
194                &pkg_path,
195                &pkg_type,
196                &entry_point,
197                bridge,
198            ])
199            .into_diagnostic()?;
200        }
201
202        Ok(())
203    }
204
205    pub fn remove_pkgs(&self, pkgs_names: &[String]) -> Result<()> {
206        let mut stmt = self.conn.prepare(sql::DELETE_PKGS).into_diagnostic()?;
207
208        for pkg_name in pkgs_names {
209            stmt.execute([&pkg_name]).into_diagnostic()?;
210        }
211
212        Ok(())
213    }
214
215    pub fn get_pkgs(&self) -> Result<Vec<Pkg>> {
216        let mut stmt = self.conn.prepare(sql::GET_PKGS).into_diagnostic()?;
217        let rows = stmt
218            .query_map([], |row| {
219                let name: String = row.get(0)?;
220                let version: String = row.get(1)?;
221                let path: String = row.get(2)?;
222                let pkg_type: String = row.get(3)?;
223                let entry_point: String = row.get(4)?;
224
225                // Parse version string into components
226                let version_parts: Vec<&str> = version.split('.').collect();
227                if version_parts.len() != 3 {
228                    return Err(RusqliteError::InvalidQuery);
229                }
230
231                // Parse package type
232                let pkg_type = match pkg_type.as_str() {
233                    "SingleExecutable" => PkgType::SingleExecutable,
234                    "Directory" => PkgType::Directory(PathBuf::from(&entry_point)),
235                    _ => return Err(RusqliteError::InvalidQuery),
236                };
237
238                Ok(Pkg {
239                    name,
240                    version: Version {
241                        first_cell: version_parts[0].to_string(),
242                        second_cell: version_parts[1].to_string(),
243                        third_cell: version_parts[2].to_string(),
244                    },
245                    path: PathBuf::from(path),
246                    pkg_type,
247                })
248            })
249            .into_diagnostic()?;
250
251        let mut pkgs = Vec::new();
252        for pkg in rows {
253            pkgs.push(pkg.into_diagnostic()?);
254        }
255
256        Ok(pkgs)
257    }
258
259    pub fn get_pkgs_by_name(&self, pkg_names: &[String]) -> Result<Vec<Pkg>> {
260        if pkg_names.is_empty() {
261            return Ok(Vec::new());
262        }
263
264        let placeholders = pkg_names.iter().map(|_| "?").collect::<Vec<_>>().join(",");
265        let sql = sql::GET_PKGS_BY_NAMES.replace("{}", &placeholders);
266
267        let mut stmt = self.conn.prepare(&sql).into_diagnostic()?;
268
269        let params: Vec<&str> = pkg_names.iter().map(|s| s.as_str()).collect();
270
271        let rows = stmt
272            .query_map(rusqlite::params_from_iter(params.iter()), |row| {
273                let name: String = row.get(0)?;
274                let version: String = row.get(1)?;
275                let path: String = row.get(2)?;
276                let pkg_type: String = row.get(3)?;
277                let entry_point: String = row.get(4)?;
278
279                // Parse version string into components
280                let version_parts: Vec<&str> = version.split('.').collect();
281                if version_parts.len() != 3 {
282                    return Err(RusqliteError::InvalidQuery);
283                }
284
285                // Parse package type
286                let pkg_type = match pkg_type.as_str() {
287                    "SingleExecutable" => PkgType::SingleExecutable,
288                    "Directory" => PkgType::Directory(PathBuf::from(&entry_point)),
289                    _ => return Err(RusqliteError::InvalidQuery),
290                };
291
292                Ok(Pkg {
293                    name,
294                    version: Version {
295                        first_cell: version_parts[0].to_string(),
296                        second_cell: version_parts[1].to_string(),
297                        third_cell: version_parts[2].to_string(),
298                    },
299                    path: PathBuf::from(path),
300                    pkg_type,
301                })
302            })
303            .into_diagnostic()?;
304
305        let mut pkgs = Vec::new();
306        for pkg in rows {
307            pkgs.push(pkg.into_diagnostic()?);
308        }
309
310        Ok(pkgs)
311    }
312
313    pub fn get_pkgs_by_bridge(&self, bridge_name: &String) -> Result<Vec<Pkg>> {
314        let mut stmt = self
315            .conn
316            .prepare(sql::GET_PKGS_BY_BRIDGE)
317            .into_diagnostic()?;
318
319        let rows = stmt
320            .query_map([&bridge_name], |row| {
321                let name: String = row.get(0)?;
322                let version: String = row.get(1)?;
323                let path: String = row.get(2)?;
324                let pkg_type: String = row.get(3)?;
325                let entry_point: String = row.get(4)?;
326
327                // Parse version string into components
328                let version_parts: Vec<&str> = version.split('.').collect();
329                if version_parts.len() != 3 {
330                    return Err(RusqliteError::InvalidQuery);
331                }
332
333                // Parse package type
334                let pkg_type = match pkg_type.as_str() {
335                    "SingleExecutable" => PkgType::SingleExecutable,
336                    "Directory" => PkgType::Directory(PathBuf::from(&entry_point)),
337                    _ => return Err(RusqliteError::InvalidQuery),
338                };
339
340                Ok(Pkg {
341                    name,
342                    version: Version {
343                        first_cell: version_parts[0].to_string(),
344                        second_cell: version_parts[1].to_string(),
345                        third_cell: version_parts[2].to_string(),
346                    },
347                    path: PathBuf::from(path),
348                    pkg_type,
349                })
350            })
351            .into_diagnostic()?;
352
353        let mut pkgs = Vec::new();
354        for pkg in rows {
355            pkgs.push(pkg.into_diagnostic()?);
356        }
357
358        Ok(pkgs)
359    }
360
361    pub fn which_pkgs_are_not_installed<'a>(
362        &'a self,
363        pkgs: &'a [String],
364    ) -> Result<Vec<&'a String>> {
365        let installed_pkgs = self.get_pkgs()?;
366        let mut not_installed_pkgs = Vec::new();
367
368        for pkg in pkgs {
369            if !installed_pkgs
370                .iter()
371                .any(|installed| installed.name == pkg.clone())
372            {
373                not_installed_pkgs.push(pkg);
374            }
375        }
376
377        Ok(not_installed_pkgs)
378    }
379}