From 84c7aa9cad8079cb03915d07318c36157abe4b9a Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Tue, 18 Oct 2022 15:58:05 -0700 Subject: [PATCH] Finished up initial sqlite implemention Co-Authored-By: kay@zed.dev --- Cargo.lock | 12 +++ crates/auto_update/src/auto_update.rs | 6 +- crates/client/src/client.rs | 2 +- crates/client/src/telemetry.rs | 2 +- crates/db/Cargo.toml | 2 + crates/db/src/db.rs | 105 +++++++++++++++++++------- crates/db/src/kvp.rs | 53 ++++++++----- crates/db/src/migrations.rs | 4 +- crates/zed/src/main.rs | 8 +- crates/zed/src/paths.rs | 1 - crates/zed/src/zed.rs | 1 + 11 files changed, 136 insertions(+), 60 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6640037f3d..9360b1f4db 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1607,6 +1607,8 @@ dependencies = [ "parking_lot 0.11.2", "rusqlite", "rusqlite_migration", + "serde", + "serde_rusqlite", "tempdir", ] @@ -5267,6 +5269,16 @@ dependencies = [ "syn", ] +[[package]] +name = "serde_rusqlite" +version = "0.31.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "538b51f10ee271375cbd9caa04fa6e3e50af431a21db97caae48da92a074244a" +dependencies = [ + "rusqlite", + "serde", +] + [[package]] name = "serde_urlencoded" version = "0.7.1" diff --git a/crates/auto_update/src/auto_update.rs b/crates/auto_update/src/auto_update.rs index da3d35a2b8..efe36ccab8 100644 --- a/crates/auto_update/src/auto_update.rs +++ b/crates/auto_update/src/auto_update.rs @@ -40,7 +40,7 @@ pub struct AutoUpdater { current_version: AppVersion, http_client: Arc, pending_poll: Option>, - db: Arc, + db: project::Db, server_url: String, } @@ -55,7 +55,7 @@ impl Entity for AutoUpdater { } pub fn init( - db: Arc, + db: project::Db, http_client: Arc, server_url: String, cx: &mut MutableAppContext, @@ -116,7 +116,7 @@ impl AutoUpdater { fn new( current_version: AppVersion, - db: Arc, + db: project::Db, http_client: Arc, server_url: String, ) -> Self { diff --git a/crates/client/src/client.rs b/crates/client/src/client.rs index cc6bdf6279..4c0c3ca184 100644 --- a/crates/client/src/client.rs +++ b/crates/client/src/client.rs @@ -1072,7 +1072,7 @@ impl Client { self.peer.respond_with_error(receipt, error) } - pub fn start_telemetry(&self, db: Arc) { + pub fn start_telemetry(&self, db: Db) { self.telemetry.start(db); } diff --git a/crates/client/src/telemetry.rs b/crates/client/src/telemetry.rs index 1346b97f61..6829eab531 100644 --- a/crates/client/src/telemetry.rs +++ b/crates/client/src/telemetry.rs @@ -135,7 +135,7 @@ impl Telemetry { Some(self.state.lock().log_file.as_ref()?.path().to_path_buf()) } - pub fn start(self: &Arc, db: Arc) { + pub fn start(self: &Arc, db: Db) { let this = self.clone(); self.executor .spawn( diff --git a/crates/db/Cargo.toml b/crates/db/Cargo.toml index a2efe13261..1eeac03375 100644 --- a/crates/db/Cargo.toml +++ b/crates/db/Cargo.toml @@ -19,6 +19,8 @@ log = { version = "0.4.16", features = ["kv_unstable_serde"] } parking_lot = "0.11.1" rusqlite = { version = "0.28.0", features = ["bundled", "serde_json"] } rusqlite_migration = "1.0.0" +serde = { workspace = true } +serde_rusqlite = "0.31.0" [dev-dependencies] gpui = { path = "../gpui", features = ["test-support"] } diff --git a/crates/db/src/db.rs b/crates/db/src/db.rs index dcef0d659a..06776832b5 100644 --- a/crates/db/src/db.rs +++ b/crates/db/src/db.rs @@ -1,7 +1,8 @@ mod kvp; mod migrations; -use std::path::Path; +use std::fs; +use std::path::{Path, PathBuf}; use std::sync::Arc; use anyhow::Result; @@ -11,58 +12,108 @@ use rusqlite::Connection; use migrations::MIGRATIONS; +#[derive(Clone)] pub enum Db { - Real { - connection: Mutex, - in_memory: bool, - }, + Real(Arc), Null, } -// To make a migration: -// Add to the migrations directory, a file with the name: -// _.sql. Migrations are executed in order of number +pub struct RealDb { + connection: Mutex, + path: Option, +} impl Db { - /// Open or create a database at the given file path. Falls back to in memory database if the - /// database at the given path is corrupted - pub fn open(path: &Path) -> Arc { - Connection::open(path) + /// Open or create a database at the given directory path. + pub fn open(db_dir: &Path) -> Self { + // Use 0 for now. Will implement incrementing and clearing of old db files soon TM + let current_db_dir = db_dir.join(Path::new("0")); + fs::create_dir_all(¤t_db_dir) + .expect("Should be able to create the database directory"); + let db_path = current_db_dir.join(Path::new("db.sqlite")); + + Connection::open(db_path) .map_err(Into::into) - .and_then(|connection| Self::initialize(connection, false)) + .and_then(|connection| Self::initialize(connection)) + .map(|connection| { + Db::Real(Arc::new(RealDb { + connection, + path: Some(db_dir.to_path_buf()), + })) + }) .unwrap_or_else(|e| { error!( - "Connecting to db failed. Falling back to in memory db. {}", + "Connecting to file backed db failed. Reverting to null db. {}", e ); - Self::open_in_memory() + Self::Null }) } /// Open a in memory database for testing and as a fallback. - pub fn open_in_memory() -> Arc { + #[cfg(any(test, feature = "test-support"))] + pub fn open_in_memory() -> Self { Connection::open_in_memory() .map_err(Into::into) - .and_then(|connection| Self::initialize(connection, true)) + .and_then(|connection| Self::initialize(connection)) + .map(|connection| { + Db::Real(Arc::new(RealDb { + connection, + path: None, + })) + }) .unwrap_or_else(|e| { - error!("Connecting to in memory db failed. Reverting to null db. {}"); - Arc::new(Self::Null) + error!( + "Connecting to in memory db failed. Reverting to null db. {}", + e + ); + Self::Null }) } - fn initialize(mut conn: Connection, in_memory: bool) -> Result> { + fn initialize(mut conn: Connection) -> Result> { MIGRATIONS.to_latest(&mut conn)?; - Ok(Arc::new(Self::Real { - connection: Mutex::new(conn), - in_memory, - })) + conn.pragma_update(None, "journal_mode", "WAL")?; + conn.pragma_update(None, "synchronous", "NORMAL")?; + conn.pragma_update(None, "foreign_keys", true)?; + conn.pragma_update(None, "case_sensitive_like", true)?; + + Ok(Mutex::new(conn)) } - fn persisting(&self) -> bool { + pub fn persisting(&self) -> bool { + self.real().and_then(|db| db.path.as_ref()).is_some() + } + + pub fn real(&self) -> Option<&RealDb> { match self { - Db::Real { in_memory, .. } => *in_memory, - _ => false, + Db::Real(db) => Some(&db), + _ => None, } } } + +impl Drop for Db { + fn drop(&mut self) { + match self { + Db::Real(real_db) => { + let lock = real_db.connection.lock(); + + let _ = lock.pragma_update(None, "analysis_limit", "500"); + let _ = lock.pragma_update(None, "optimize", ""); + } + Db::Null => {} + } + } +} + +#[cfg(test)] +mod tests { + use crate::migrations::MIGRATIONS; + + #[test] + fn test_migrations() { + assert!(MIGRATIONS.validate().is_ok()); + } +} diff --git a/crates/db/src/kvp.rs b/crates/db/src/kvp.rs index ca1b15e45a..534577bc79 100644 --- a/crates/db/src/kvp.rs +++ b/crates/db/src/kvp.rs @@ -3,7 +3,7 @@ use rusqlite::OptionalExtension; use super::Db; -pub(crate) const KVP_M_1: &str = " +pub(crate) const KVP_M_1_UP: &str = " CREATE TABLE kv_store( key TEXT PRIMARY KEY, value TEXT NOT NULL @@ -12,31 +12,44 @@ CREATE TABLE kv_store( impl Db { pub fn read_kvp(&self, key: &str) -> Result> { - let lock = self.connection.lock(); - let mut stmt = lock.prepare_cached("SELECT value FROM kv_store WHERE key = (?)")?; + self.real() + .map(|db| { + let lock = db.connection.lock(); + let mut stmt = lock.prepare_cached("SELECT value FROM kv_store WHERE key = (?)")?; - Ok(stmt.query_row([key], |row| row.get(0)).optional()?) - } - - pub fn delete_kvp(&self, key: &str) -> Result<()> { - let lock = self.connection.lock(); - - let mut stmt = lock.prepare_cached("DELETE FROM kv_store WHERE key = (?)")?; - - stmt.execute([key])?; - - Ok(()) + Ok(stmt.query_row([key], |row| row.get(0)).optional()?) + }) + .unwrap_or(Ok(None)) } pub fn write_kvp(&self, key: &str, value: &str) -> Result<()> { - let lock = self.connection.lock(); + self.real() + .map(|db| { + let lock = db.connection.lock(); - let mut stmt = - lock.prepare_cached("INSERT OR REPLACE INTO kv_store(key, value) VALUES ((?), (?))")?; + let mut stmt = lock.prepare_cached( + "INSERT OR REPLACE INTO kv_store(key, value) VALUES ((?), (?))", + )?; - stmt.execute([key, value])?; + stmt.execute([key, value])?; - Ok(()) + Ok(()) + }) + .unwrap_or(Ok(())) + } + + pub fn delete_kvp(&self, key: &str) -> Result<()> { + self.real() + .map(|db| { + let lock = db.connection.lock(); + + let mut stmt = lock.prepare_cached("DELETE FROM kv_store WHERE key = (?)")?; + + stmt.execute([key])?; + + Ok(()) + }) + .unwrap_or(Ok(())) } } @@ -48,7 +61,7 @@ mod tests { #[test] fn test_kvp() -> Result<()> { - let db = Db::open_in_memory()?; + let db = Db::open_in_memory(); assert_eq!(db.read_kvp("key-1")?, None); diff --git a/crates/db/src/migrations.rs b/crates/db/src/migrations.rs index 1b7e2c30a8..1000543d8d 100644 --- a/crates/db/src/migrations.rs +++ b/crates/db/src/migrations.rs @@ -1,7 +1,7 @@ use rusqlite_migration::{Migrations, M}; // use crate::items::ITEMS_M_1; -use crate::kvp::KVP_M_1; +use crate::kvp::KVP_M_1_UP; // This must be ordered by development time! Only ever add new migrations to the end!! // Bad things will probably happen if you don't monotonically edit this vec!!!! @@ -9,7 +9,7 @@ use crate::kvp::KVP_M_1; // file system and so everything we do here is locked in _f_o_r_e_v_e_r_. lazy_static::lazy_static! { pub static ref MIGRATIONS: Migrations<'static> = Migrations::new(vec![ - M::up(KVP_M_1), + M::up(KVP_M_1_UP), // M::up(ITEMS_M_1), ]); } diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index 55cb203ee7..3fa6612856 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -52,11 +52,9 @@ fn main() { .or_else(|| app.platform().app_version().ok()) .map_or("dev".to_string(), |v| v.to_string()); init_panic_hook(app_version, http.clone(), app.background()); - let db = app.background().spawn(async move { - project::Db::open(&*zed::paths::DB) - .log_err() - .unwrap_or_else(project::Db::open_in_memory()) - }); + let db = app + .background() + .spawn(async move { project::Db::open(&*zed::paths::DB_DIR) }); load_embedded_fonts(&app); diff --git a/crates/zed/src/paths.rs b/crates/zed/src/paths.rs index 81ecc5be8a..a3abe1f673 100644 --- a/crates/zed/src/paths.rs +++ b/crates/zed/src/paths.rs @@ -6,7 +6,6 @@ lazy_static::lazy_static! { pub static ref LOGS_DIR: PathBuf = HOME.join("Library/Logs/Zed"); pub static ref LANGUAGES_DIR: PathBuf = HOME.join("Library/Application Support/Zed/languages"); pub static ref DB_DIR: PathBuf = HOME.join("Library/Application Support/Zed/db"); - pub static ref DB: PathBuf = DB_DIR.join("zed.sqlite"); pub static ref SETTINGS: PathBuf = CONFIG_DIR.join("settings.json"); pub static ref KEYMAP: PathBuf = CONFIG_DIR.join("keymap.json"); pub static ref LAST_USERNAME: PathBuf = CONFIG_DIR.join("last-username.txt"); diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index ef0c84909a..3319aebd09 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -63,6 +63,7 @@ actions!( DecreaseBufferFontSize, ResetBufferFontSize, InstallCommandLineInterface, + ResetDatabase, ] );