midas_core/sequel/
sqlite.rs

1use std::fs;
2use std::path::Path;
3
4use indoc::indoc;
5use rusqlite::Connection;
6
7use super::{
8  AnyhowResult,
9  Driver as SequelDriver,
10  VecSerial,
11};
12
13/// The Sqlite struct definition
14pub struct Sqlite {
15  /// Implement the Sqlite struct
16  conn: Connection,
17  /// The file URL of the SQLite database
18  file_url: String,
19}
20
21/// Implement the Sqlite struct
22impl Sqlite {
23  /// Create a new instance of Sqlite
24  /// # Arguments
25  /// * `file_url` - The file URL of the SQLite database
26  /// # Returns
27  /// * An instance of Sqlite
28  /// # Example
29  /// ```
30  /// let db = Sqlite::new("sqlite://./db.sqlite");
31  /// ```
32  pub fn new(file_url: &str) -> AnyhowResult<Self> {
33    log::trace!("Opening SQLite database connection: {file_url}");
34
35    // Strip file:// and file: prefix
36    // Also, strip sqlite:// and sqlite: similar prefix
37    let file_url = file_url
38      .replace("file://", "")
39      .replace("file:", "")
40      .replace("sqlite://", "")
41      .replace("sqlite:", "");
42
43    // If the file_url starts with "/" it means it's an absolute path
44    // Otherwise, it's a relative path
45    let file_url: &str = if file_url.starts_with("/") {
46      &file_url.to_string()
47    } else {
48      &format!("./{}", file_url)
49    };
50
51    // Open the connection
52    let conn = Connection::open(file_url)?;
53    let mut db: Sqlite = Sqlite {
54      conn,
55      file_url: file_url.to_string(),
56    };
57
58    // Ensure the midas schema migration table exists
59    db.ensure_midas_schema()?;
60    Ok(db)
61  }
62}
63
64/// Implement the SequelDriver trait for Sqlite
65impl SequelDriver for Sqlite {
66  /// Ensure the __schema_migrations table exists
67  /// If it doesn't exist, create it
68  fn ensure_midas_schema(&mut self) -> AnyhowResult<()> {
69    let payload = indoc! {"
70      CREATE TABLE IF NOT EXISTS __schema_migrations (
71        id INTEGER PRIMARY KEY AUTOINCREMENT,
72        migration BIGINT
73      );
74    "};
75    self.conn.execute(payload, ())?;
76    Ok(())
77  }
78
79  /// Drop the __schema_migrations table
80  fn drop_migration_table(&mut self) -> AnyhowResult<()> {
81    let payload = "DROP TABLE __schema_migrations";
82    self.conn.execute(payload, ())?;
83    Ok(())
84  }
85
86  /// Drop the database
87  fn drop_database(&mut self, _: &str) -> AnyhowResult<()> {
88    // SQLite does not support dropping databases
89    // Instead, we can delete the file
90    let path = Path::new(&self.file_url);
91    if path.exists() {
92      fs::remove_file(path)?;
93    }
94
95    // Re-create file through connection
96    Connection::open(path)?;
97    Ok(())
98  }
99
100  /// Count the number of migrations
101  fn count_migrations(&mut self) -> AnyhowResult<i64> {
102    log::trace!("Retrieving migrations count");
103    let payload = "SELECT COUNT(*) as count FROM __schema_migrations";
104    let mut stmt = self.conn.prepare(payload)?;
105    let result = stmt.query_row((), |row| row.get(0))?;
106    Ok(result)
107  }
108
109  /// Get all completed migrations
110  fn get_completed_migrations(&mut self) -> AnyhowResult<VecSerial> {
111    log::trace!("Retrieving all completed migrations");
112    let payload = "SELECT migration FROM __schema_migrations ORDER BY id ASC";
113    let mut stmt = self.conn.prepare(payload)?;
114    let it = stmt.query_map((), |row| row.get(0))?;
115    let result = it.map(|r| r.unwrap()).collect::<VecSerial>();
116    Ok(result)
117  }
118
119  /// Get the last completed migration
120  fn get_last_completed_migration(&mut self) -> AnyhowResult<i64> {
121    log::trace!("Checking and retrieving the last migration stored on migrations table");
122    let payload = "SELECT migration FROM __schema_migrations ORDER BY id DESC LIMIT 1";
123    let mut stmt = self.conn.prepare(payload)?;
124    let result = stmt.query_row((), |row| row.get(0))?;
125    Ok(result)
126  }
127
128  /// Add a completed migration
129  fn add_completed_migration(&mut self, migration_number: i64) -> AnyhowResult<()> {
130    log::trace!("Adding migration to migrations table");
131    let payload = "INSERT INTO __schema_migrations (migration) VALUES ($1)";
132    self.conn.execute(payload, [&migration_number])?;
133    Ok(())
134  }
135
136  /// Delete a completed migration
137  fn delete_completed_migration(&mut self, migration_number: i64) -> AnyhowResult<()> {
138    log::trace!("Removing a migration in the migrations table");
139    let payload = "DELETE FROM __schema_migrations WHERE migration = $1";
140    self.conn.execute(payload, [&migration_number])?;
141    Ok(())
142  }
143
144  /// Delete the last completed migration
145  fn delete_last_completed_migration(&mut self) -> AnyhowResult<()> {
146    let payload = "DELETE FROM __schema_migrations WHERE id=(SELECT MAX(id) FROM __schema_migrations);";
147    self.conn.execute(payload, ())?;
148    Ok(())
149  }
150
151  /// Run a migration
152  fn migrate(&mut self, query: &str, _migration_number: i64) -> AnyhowResult<()> {
153    self.conn.execute(query, ())?;
154    Ok(())
155  }
156
157  /// Get the database name
158  fn db_name(&self) -> &str {
159    "sqlite"
160  }
161}