From 72435af170dfd1a193c3e2878933ba72489e5528 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Sun, 22 Oct 2023 18:34:45 +0200 Subject: [PATCH] Checkpoint --- Cargo.lock | 23 +++ Cargo.toml | 5 +- crates/db2/Cargo.toml | 33 ++++ crates/db2/README.md | 5 + crates/db2/src/db2.rs | 327 ++++++++++++++++++++++++++++++++++++++++ crates/db2/src/kvp.rs | 62 ++++++++ crates/db2/src/query.rs | 314 ++++++++++++++++++++++++++++++++++++++ crates/zed2/Cargo.toml | 2 +- crates/zed2/src/main.rs | 39 ++--- 9 files changed, 789 insertions(+), 21 deletions(-) create mode 100644 crates/db2/Cargo.toml create mode 100644 crates/db2/README.md create mode 100644 crates/db2/src/db2.rs create mode 100644 crates/db2/src/kvp.rs create mode 100644 crates/db2/src/query.rs diff --git a/Cargo.lock b/Cargo.lock index 196c97b5ed..78e7802199 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2209,6 +2209,28 @@ dependencies = [ "util", ] +[[package]] +name = "db2" +version = "0.1.0" +dependencies = [ + "anyhow", + "async-trait", + "collections", + "env_logger 0.9.3", + "gpui2", + "indoc", + "lazy_static", + "log", + "parking_lot 0.11.2", + "serde", + "serde_derive", + "smol", + "sqlez", + "sqlez_macros", + "tempdir", + "util", +] + [[package]] name = "deflate" version = "0.8.6" @@ -10427,6 +10449,7 @@ dependencies = [ "client2", "collections", "ctor", + "db2", "env_logger 0.9.3", "feature_flags", "fs", diff --git a/Cargo.toml b/Cargo.toml index db7a5eb2e8..d5d7bafceb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,11 +21,14 @@ members = [ "crates/copilot", "crates/copilot_button", "crates/db", + "crates/db2", "crates/refineable", "crates/refineable/derive_refineable", "crates/diagnostics", "crates/drag_and_drop", "crates/editor", + "crates/feature_flags", + "crates/feature_flags2", "crates/feedback", "crates/file_finder", "crates/fs", @@ -62,10 +65,10 @@ members = [ "crates/rpc", "crates/search", "crates/settings", + "crates/settings2", "crates/snippet", "crates/sqlez", "crates/sqlez_macros", - "crates/feature_flags", "crates/rich_text", "crates/storybook2", "crates/sum_tree", diff --git a/crates/db2/Cargo.toml b/crates/db2/Cargo.toml new file mode 100644 index 0000000000..6ef8ec0874 --- /dev/null +++ b/crates/db2/Cargo.toml @@ -0,0 +1,33 @@ +[package] +name = "db2" +version = "0.1.0" +edition = "2021" +publish = false + +[lib] +path = "src/db2.rs" +doctest = false + +[features] +test-support = [] + +[dependencies] +collections = { path = "../collections" } +gpui2 = { path = "../gpui2" } +sqlez = { path = "../sqlez" } +sqlez_macros = { path = "../sqlez_macros" } +util = { path = "../util" } +anyhow.workspace = true +indoc.workspace = true +async-trait.workspace = true +lazy_static.workspace = true +log.workspace = true +parking_lot.workspace = true +serde.workspace = true +serde_derive.workspace = true +smol.workspace = true + +[dev-dependencies] +gpui2 = { path = "../gpui2", features = ["test-support"] } +env_logger.workspace = true +tempdir.workspace = true diff --git a/crates/db2/README.md b/crates/db2/README.md new file mode 100644 index 0000000000..d4ea2fee39 --- /dev/null +++ b/crates/db2/README.md @@ -0,0 +1,5 @@ +# Building Queries + +First, craft your test data. The examples folder shows a template for building a test-db, and can be ran with `cargo run --example [your-example]`. + +To actually use and test your queries, import the generated DB file into https://sqliteonline.com/ \ No newline at end of file diff --git a/crates/db2/src/db2.rs b/crates/db2/src/db2.rs new file mode 100644 index 0000000000..d2746ce67e --- /dev/null +++ b/crates/db2/src/db2.rs @@ -0,0 +1,327 @@ +pub mod kvp; +pub mod query; + +// Re-export +pub use anyhow; +use anyhow::Context; +use gpui2::AppContext; +pub use indoc::indoc; +pub use lazy_static; +pub use smol; +pub use sqlez; +pub use sqlez_macros; +pub use util::channel::{RELEASE_CHANNEL, RELEASE_CHANNEL_NAME}; +pub use util::paths::DB_DIR; + +use sqlez::domain::Migrator; +use sqlez::thread_safe_connection::ThreadSafeConnection; +use sqlez_macros::sql; +use std::future::Future; +use std::path::{Path, PathBuf}; +use std::sync::atomic::{AtomicBool, Ordering}; +use util::channel::ReleaseChannel; +use util::{async_iife, ResultExt}; + +const CONNECTION_INITIALIZE_QUERY: &'static str = sql!( + PRAGMA foreign_keys=TRUE; +); + +const DB_INITIALIZE_QUERY: &'static str = sql!( + PRAGMA journal_mode=WAL; + PRAGMA busy_timeout=1; + PRAGMA case_sensitive_like=TRUE; + PRAGMA synchronous=NORMAL; +); + +const FALLBACK_DB_NAME: &'static str = "FALLBACK_MEMORY_DB"; + +const DB_FILE_NAME: &'static str = "db.sqlite"; + +lazy_static::lazy_static! { + pub static ref ZED_STATELESS: bool = std::env::var("ZED_STATELESS").map_or(false, |v| !v.is_empty()); + pub static ref ALL_FILE_DB_FAILED: AtomicBool = AtomicBool::new(false); +} + +/// Open or create a database at the given directory path. +/// This will retry a couple times if there are failures. If opening fails once, the db directory +/// is moved to a backup folder and a new one is created. If that fails, a shared in memory db is created. +/// In either case, static variables are set so that the user can be notified. +pub async fn open_db( + db_dir: &Path, + release_channel: &ReleaseChannel, +) -> ThreadSafeConnection { + if *ZED_STATELESS { + return open_fallback_db().await; + } + + let release_channel_name = release_channel.dev_name(); + let main_db_dir = db_dir.join(Path::new(&format!("0-{}", release_channel_name))); + + let connection = async_iife!({ + smol::fs::create_dir_all(&main_db_dir) + .await + .context("Could not create db directory") + .log_err()?; + let db_path = main_db_dir.join(Path::new(DB_FILE_NAME)); + open_main_db(&db_path).await + }) + .await; + + if let Some(connection) = connection { + return connection; + } + + // Set another static ref so that we can escalate the notification + ALL_FILE_DB_FAILED.store(true, Ordering::Release); + + // If still failed, create an in memory db with a known name + open_fallback_db().await +} + +async fn open_main_db(db_path: &PathBuf) -> Option> { + log::info!("Opening main db"); + ThreadSafeConnection::::builder(db_path.to_string_lossy().as_ref(), true) + .with_db_initialization_query(DB_INITIALIZE_QUERY) + .with_connection_initialize_query(CONNECTION_INITIALIZE_QUERY) + .build() + .await + .log_err() +} + +async fn open_fallback_db() -> ThreadSafeConnection { + log::info!("Opening fallback db"); + ThreadSafeConnection::::builder(FALLBACK_DB_NAME, false) + .with_db_initialization_query(DB_INITIALIZE_QUERY) + .with_connection_initialize_query(CONNECTION_INITIALIZE_QUERY) + .build() + .await + .expect( + "Fallback in memory database failed. Likely initialization queries or migrations have fundamental errors", + ) +} + +#[cfg(any(test, feature = "test-support"))] +pub async fn open_test_db(db_name: &str) -> ThreadSafeConnection { + use sqlez::thread_safe_connection::locking_queue; + + ThreadSafeConnection::::builder(db_name, false) + .with_db_initialization_query(DB_INITIALIZE_QUERY) + .with_connection_initialize_query(CONNECTION_INITIALIZE_QUERY) + // Serialize queued writes via a mutex and run them synchronously + .with_write_queue_constructor(locking_queue()) + .build() + .await + .unwrap() +} + +/// Implements a basic DB wrapper for a given domain +#[macro_export] +macro_rules! define_connection { + (pub static ref $id:ident: $t:ident<()> = $migrations:expr;) => { + pub struct $t($crate::sqlez::thread_safe_connection::ThreadSafeConnection<$t>); + + impl ::std::ops::Deref for $t { + type Target = $crate::sqlez::thread_safe_connection::ThreadSafeConnection<$t>; + + fn deref(&self) -> &Self::Target { + &self.0 + } + } + + impl $crate::sqlez::domain::Domain for $t { + fn name() -> &'static str { + stringify!($t) + } + + fn migrations() -> &'static [&'static str] { + $migrations + } + } + + #[cfg(any(test, feature = "test-support"))] + $crate::lazy_static::lazy_static! { + pub static ref $id: $t = $t($crate::smol::block_on($crate::open_test_db(stringify!($id)))); + } + + #[cfg(not(any(test, feature = "test-support")))] + $crate::lazy_static::lazy_static! { + pub static ref $id: $t = $t($crate::smol::block_on($crate::open_db(&$crate::DB_DIR, &$crate::RELEASE_CHANNEL))); + } + }; + (pub static ref $id:ident: $t:ident<$($d:ty),+> = $migrations:expr;) => { + pub struct $t($crate::sqlez::thread_safe_connection::ThreadSafeConnection<( $($d),+, $t )>); + + impl ::std::ops::Deref for $t { + type Target = $crate::sqlez::thread_safe_connection::ThreadSafeConnection<($($d),+, $t)>; + + fn deref(&self) -> &Self::Target { + &self.0 + } + } + + impl $crate::sqlez::domain::Domain for $t { + fn name() -> &'static str { + stringify!($t) + } + + fn migrations() -> &'static [&'static str] { + $migrations + } + } + + #[cfg(any(test, feature = "test-support"))] + $crate::lazy_static::lazy_static! { + pub static ref $id: $t = $t($crate::smol::block_on($crate::open_test_db(stringify!($id)))); + } + + #[cfg(not(any(test, feature = "test-support")))] + $crate::lazy_static::lazy_static! { + pub static ref $id: $t = $t($crate::smol::block_on($crate::open_db(&$crate::DB_DIR, &$crate::RELEASE_CHANNEL))); + } + }; +} + +pub fn write_and_log(cx: &mut AppContext, db_write: impl FnOnce() -> F + Send + 'static) +where + F: Future> + Send, +{ + cx.executor() + .spawn(async move { db_write().await.log_err() }) + .detach() +} + +// #[cfg(test)] +// mod tests { +// use std::thread; + +// use sqlez::domain::Domain; +// use sqlez_macros::sql; +// use tempdir::TempDir; + +// use crate::open_db; + +// // Test bad migration panics +// #[gpui::test] +// #[should_panic] +// async fn test_bad_migration_panics() { +// enum BadDB {} + +// impl Domain for BadDB { +// fn name() -> &'static str { +// "db_tests" +// } + +// fn migrations() -> &'static [&'static str] { +// &[ +// sql!(CREATE TABLE test(value);), +// // failure because test already exists +// sql!(CREATE TABLE test(value);), +// ] +// } +// } + +// let tempdir = TempDir::new("DbTests").unwrap(); +// let _bad_db = open_db::(tempdir.path(), &util::channel::ReleaseChannel::Dev).await; +// } + +// /// Test that DB exists but corrupted (causing recreate) +// #[gpui::test] +// async fn test_db_corruption() { +// enum CorruptedDB {} + +// impl Domain for CorruptedDB { +// fn name() -> &'static str { +// "db_tests" +// } + +// fn migrations() -> &'static [&'static str] { +// &[sql!(CREATE TABLE test(value);)] +// } +// } + +// enum GoodDB {} + +// impl Domain for GoodDB { +// fn name() -> &'static str { +// "db_tests" //Notice same name +// } + +// fn migrations() -> &'static [&'static str] { +// &[sql!(CREATE TABLE test2(value);)] //But different migration +// } +// } + +// let tempdir = TempDir::new("DbTests").unwrap(); +// { +// let corrupt_db = +// open_db::(tempdir.path(), &util::channel::ReleaseChannel::Dev).await; +// assert!(corrupt_db.persistent()); +// } + +// let good_db = open_db::(tempdir.path(), &util::channel::ReleaseChannel::Dev).await; +// assert!( +// good_db.select_row::("SELECT * FROM test2").unwrap()() +// .unwrap() +// .is_none() +// ); +// } + +// /// Test that DB exists but corrupted (causing recreate) +// #[gpui::test(iterations = 30)] +// async fn test_simultaneous_db_corruption() { +// enum CorruptedDB {} + +// impl Domain for CorruptedDB { +// fn name() -> &'static str { +// "db_tests" +// } + +// fn migrations() -> &'static [&'static str] { +// &[sql!(CREATE TABLE test(value);)] +// } +// } + +// enum GoodDB {} + +// impl Domain for GoodDB { +// fn name() -> &'static str { +// "db_tests" //Notice same name +// } + +// fn migrations() -> &'static [&'static str] { +// &[sql!(CREATE TABLE test2(value);)] //But different migration +// } +// } + +// let tempdir = TempDir::new("DbTests").unwrap(); +// { +// // Setup the bad database +// let corrupt_db = +// open_db::(tempdir.path(), &util::channel::ReleaseChannel::Dev).await; +// assert!(corrupt_db.persistent()); +// } + +// // Try to connect to it a bunch of times at once +// let mut guards = vec![]; +// for _ in 0..10 { +// let tmp_path = tempdir.path().to_path_buf(); +// let guard = thread::spawn(move || { +// let good_db = smol::block_on(open_db::( +// tmp_path.as_path(), +// &util::channel::ReleaseChannel::Dev, +// )); +// assert!( +// good_db.select_row::("SELECT * FROM test2").unwrap()() +// .unwrap() +// .is_none() +// ); +// }); + +// guards.push(guard); +// } + +// for guard in guards.into_iter() { +// assert!(guard.join().is_ok()); +// } +// } +// } diff --git a/crates/db2/src/kvp.rs b/crates/db2/src/kvp.rs new file mode 100644 index 0000000000..254d91689d --- /dev/null +++ b/crates/db2/src/kvp.rs @@ -0,0 +1,62 @@ +use sqlez_macros::sql; + +use crate::{define_connection, query}; + +define_connection!(pub static ref KEY_VALUE_STORE: KeyValueStore<()> = + &[sql!( + CREATE TABLE IF NOT EXISTS kv_store( + key TEXT PRIMARY KEY, + value TEXT NOT NULL + ) STRICT; + )]; +); + +impl KeyValueStore { + query! { + pub fn read_kvp(key: &str) -> Result> { + SELECT value FROM kv_store WHERE key = (?) + } + } + + query! { + pub async fn write_kvp(key: String, value: String) -> Result<()> { + INSERT OR REPLACE INTO kv_store(key, value) VALUES ((?), (?)) + } + } + + query! { + pub async fn delete_kvp(key: String) -> Result<()> { + DELETE FROM kv_store WHERE key = (?) + } + } +} + +// #[cfg(test)] +// mod tests { +// use crate::kvp::KeyValueStore; + +// #[gpui::test] +// async fn test_kvp() { +// let db = KeyValueStore(crate::open_test_db("test_kvp").await); + +// assert_eq!(db.read_kvp("key-1").unwrap(), None); + +// db.write_kvp("key-1".to_string(), "one".to_string()) +// .await +// .unwrap(); +// assert_eq!(db.read_kvp("key-1").unwrap(), Some("one".to_string())); + +// db.write_kvp("key-1".to_string(), "one-2".to_string()) +// .await +// .unwrap(); +// assert_eq!(db.read_kvp("key-1").unwrap(), Some("one-2".to_string())); + +// db.write_kvp("key-2".to_string(), "two".to_string()) +// .await +// .unwrap(); +// assert_eq!(db.read_kvp("key-2").unwrap(), Some("two".to_string())); + +// db.delete_kvp("key-1".to_string()).await.unwrap(); +// assert_eq!(db.read_kvp("key-1").unwrap(), None); +// } +// } diff --git a/crates/db2/src/query.rs b/crates/db2/src/query.rs new file mode 100644 index 0000000000..27d94ade9e --- /dev/null +++ b/crates/db2/src/query.rs @@ -0,0 +1,314 @@ +#[macro_export] +macro_rules! query { + ($vis:vis fn $id:ident() -> Result<()> { $($sql:tt)+ }) => { + $vis fn $id(&self) -> $crate::anyhow::Result<()> { + use $crate::anyhow::Context; + + let sql_stmt = $crate::sqlez_macros::sql!($($sql)+); + + self.exec(sql_stmt)?().context(::std::format!( + "Error in {}, exec failed to execute or parse for: {}", + ::std::stringify!($id), + sql_stmt, + )) + } + }; + ($vis:vis async fn $id:ident() -> Result<()> { $($sql:tt)+ }) => { + $vis async fn $id(&self) -> $crate::anyhow::Result<()> { + use $crate::anyhow::Context; + + self.write(|connection| { + let sql_stmt = $crate::sqlez_macros::sql!($($sql)+); + + connection.exec(sql_stmt)?().context(::std::format!( + "Error in {}, exec failed to execute or parse for: {}", + ::std::stringify!($id), + sql_stmt + )) + }).await + } + }; + ($vis:vis fn $id:ident($($arg:ident: $arg_type:ty),+) -> Result<()> { $($sql:tt)+ }) => { + $vis fn $id(&self, $($arg: $arg_type),+) -> $crate::anyhow::Result<()> { + use $crate::anyhow::Context; + + let sql_stmt = $crate::sqlez_macros::sql!($($sql)+); + + self.exec_bound::<($($arg_type),+)>(sql_stmt)?(($($arg),+)) + .context(::std::format!( + "Error in {}, exec_bound failed to execute or parse for: {}", + ::std::stringify!($id), + sql_stmt + )) + } + }; + ($vis:vis async fn $id:ident($arg:ident: $arg_type:ty) -> Result<()> { $($sql:tt)+ }) => { + $vis async fn $id(&self, $arg: $arg_type) -> $crate::anyhow::Result<()> { + use $crate::anyhow::Context; + + self.write(move |connection| { + let sql_stmt = $crate::sqlez_macros::sql!($($sql)+); + + connection.exec_bound::<$arg_type>(sql_stmt)?($arg) + .context(::std::format!( + "Error in {}, exec_bound failed to execute or parse for: {}", + ::std::stringify!($id), + sql_stmt + )) + }).await + } + }; + ($vis:vis async fn $id:ident($($arg:ident: $arg_type:ty),+) -> Result<()> { $($sql:tt)+ }) => { + $vis async fn $id(&self, $($arg: $arg_type),+) -> $crate::anyhow::Result<()> { + use $crate::anyhow::Context; + + self.write(move |connection| { + let sql_stmt = $crate::sqlez_macros::sql!($($sql)+); + + connection.exec_bound::<($($arg_type),+)>(sql_stmt)?(($($arg),+)) + .context(::std::format!( + "Error in {}, exec_bound failed to execute or parse for: {}", + ::std::stringify!($id), + sql_stmt + )) + }).await + } + }; + ($vis:vis fn $id:ident() -> Result> { $($sql:tt)+ }) => { + $vis fn $id(&self) -> $crate::anyhow::Result> { + use $crate::anyhow::Context; + + let sql_stmt = $crate::sqlez_macros::sql!($($sql)+); + + self.select::<$return_type>(sql_stmt)?() + .context(::std::format!( + "Error in {}, select_row failed to execute or parse for: {}", + ::std::stringify!($id), + sql_stmt + )) + } + }; + ($vis:vis async fn $id:ident() -> Result> { $($sql:tt)+ }) => { + pub async fn $id(&self) -> $crate::anyhow::Result> { + use $crate::anyhow::Context; + + self.write(|connection| { + let sql_stmt = $crate::sqlez_macros::sql!($($sql)+); + + connection.select::<$return_type>(sql_stmt)?() + .context(::std::format!( + "Error in {}, select_row failed to execute or parse for: {}", + ::std::stringify!($id), + sql_stmt + )) + }).await + } + }; + ($vis:vis fn $id:ident($($arg:ident: $arg_type:ty),+) -> Result> { $($sql:tt)+ }) => { + $vis fn $id(&self, $($arg: $arg_type),+) -> $crate::anyhow::Result> { + use $crate::anyhow::Context; + + let sql_stmt = $crate::sqlez_macros::sql!($($sql)+); + + self.select_bound::<($($arg_type),+), $return_type>(sql_stmt)?(($($arg),+)) + .context(::std::format!( + "Error in {}, exec_bound failed to execute or parse for: {}", + ::std::stringify!($id), + sql_stmt + )) + } + }; + ($vis:vis async fn $id:ident($($arg:ident: $arg_type:ty),+) -> Result> { $($sql:tt)+ }) => { + $vis async fn $id(&self, $($arg: $arg_type),+) -> $crate::anyhow::Result> { + use $crate::anyhow::Context; + + self.write(|connection| { + let sql_stmt = $crate::sqlez_macros::sql!($($sql)+); + + connection.select_bound::<($($arg_type),+), $return_type>(sql_stmt)?(($($arg),+)) + .context(::std::format!( + "Error in {}, exec_bound failed to execute or parse for: {}", + ::std::stringify!($id), + sql_stmt + )) + }).await + } + }; + ($vis:vis fn $id:ident() -> Result> { $($sql:tt)+ }) => { + $vis fn $id(&self) -> $crate::anyhow::Result> { + use $crate::anyhow::Context; + + let sql_stmt = $crate::sqlez_macros::sql!($($sql)+); + + self.select_row::<$return_type>(sql_stmt)?() + .context(::std::format!( + "Error in {}, select_row failed to execute or parse for: {}", + ::std::stringify!($id), + sql_stmt + )) + } + }; + ($vis:vis async fn $id:ident() -> Result> { $($sql:tt)+ }) => { + $vis async fn $id(&self) -> $crate::anyhow::Result> { + use $crate::anyhow::Context; + + self.write(|connection| { + let sql_stmt = $crate::sqlez_macros::sql!($($sql)+); + + connection.select_row::<$return_type>(sql_stmt)?() + .context(::std::format!( + "Error in {}, select_row failed to execute or parse for: {}", + ::std::stringify!($id), + sql_stmt + )) + }).await + } + }; + ($vis:vis fn $id:ident($arg:ident: $arg_type:ty) -> Result> { $($sql:tt)+ }) => { + $vis fn $id(&self, $arg: $arg_type) -> $crate::anyhow::Result> { + use $crate::anyhow::Context; + + let sql_stmt = $crate::sqlez_macros::sql!($($sql)+); + + self.select_row_bound::<$arg_type, $return_type>(sql_stmt)?($arg) + .context(::std::format!( + "Error in {}, select_row_bound failed to execute or parse for: {}", + ::std::stringify!($id), + sql_stmt + )) + + } + }; + ($vis:vis fn $id:ident($($arg:ident: $arg_type:ty),+) -> Result> { $($sql:tt)+ }) => { + $vis fn $id(&self, $($arg: $arg_type),+) -> $crate::anyhow::Result> { + use $crate::anyhow::Context; + + let sql_stmt = $crate::sqlez_macros::sql!($($sql)+); + + self.select_row_bound::<($($arg_type),+), $return_type>(sql_stmt)?(($($arg),+)) + .context(::std::format!( + "Error in {}, select_row_bound failed to execute or parse for: {}", + ::std::stringify!($id), + sql_stmt + )) + + } + }; + ($vis:vis async fn $id:ident($($arg:ident: $arg_type:ty),+) -> Result> { $($sql:tt)+ }) => { + $vis async fn $id(&self, $($arg: $arg_type),+) -> $crate::anyhow::Result> { + use $crate::anyhow::Context; + + + self.write(move |connection| { + let sql_stmt = $crate::sqlez_macros::sql!($($sql)+); + + connection.select_row_bound::<($($arg_type),+), $return_type>(sql_stmt)?(($($arg),+)) + .context(::std::format!( + "Error in {}, select_row_bound failed to execute or parse for: {}", + ::std::stringify!($id), + sql_stmt + )) + }).await + } + }; + ($vis:vis fn $id:ident() -> Result<$return_type:ty> { $($sql:tt)+ }) => { + $vis fn $id(&self) -> $crate::anyhow::Result<$return_type> { + use $crate::anyhow::Context; + + let sql_stmt = $crate::sqlez_macros::sql!($($sql)+); + + self.select_row::<$return_type>(indoc! { $sql })?() + .context(::std::format!( + "Error in {}, select_row_bound failed to execute or parse for: {}", + ::std::stringify!($id), + sql_stmt + ))? + .context(::std::format!( + "Error in {}, select_row_bound expected single row result but found none for: {}", + ::std::stringify!($id), + sql_stmt + )) + } + }; + ($vis:vis async fn $id:ident() -> Result<$return_type:ty> { $($sql:tt)+ }) => { + $vis async fn $id(&self) -> $crate::anyhow::Result<$return_type> { + use $crate::anyhow::Context; + + self.write(|connection| { + let sql_stmt = $crate::sqlez_macros::sql!($($sql)+); + + connection.select_row::<$return_type>(sql_stmt)?() + .context(::std::format!( + "Error in {}, select_row_bound failed to execute or parse for: {}", + ::std::stringify!($id), + sql_stmt + ))? + .context(::std::format!( + "Error in {}, select_row_bound expected single row result but found none for: {}", + ::std::stringify!($id), + sql_stmt + )) + }).await + } + }; + ($vis:vis fn $id:ident($arg:ident: $arg_type:ty) -> Result<$return_type:ty> { $($sql:tt)+ }) => { + pub fn $id(&self, $arg: $arg_type) -> $crate::anyhow::Result<$return_type> { + use $crate::anyhow::Context; + + let sql_stmt = $crate::sqlez_macros::sql!($($sql)+); + + self.select_row_bound::<$arg_type, $return_type>(sql_stmt)?($arg) + .context(::std::format!( + "Error in {}, select_row_bound failed to execute or parse for: {}", + ::std::stringify!($id), + sql_stmt + ))? + .context(::std::format!( + "Error in {}, select_row_bound expected single row result but found none for: {}", + ::std::stringify!($id), + sql_stmt + )) + } + }; + ($vis:vis fn $id:ident($($arg:ident: $arg_type:ty),+) -> Result<$return_type:ty> { $($sql:tt)+ }) => { + $vis fn $id(&self, $($arg: $arg_type),+) -> $crate::anyhow::Result<$return_type> { + use $crate::anyhow::Context; + + let sql_stmt = $crate::sqlez_macros::sql!($($sql)+); + + self.select_row_bound::<($($arg_type),+), $return_type>(sql_stmt)?(($($arg),+)) + .context(::std::format!( + "Error in {}, select_row_bound failed to execute or parse for: {}", + ::std::stringify!($id), + sql_stmt + ))? + .context(::std::format!( + "Error in {}, select_row_bound expected single row result but found none for: {}", + ::std::stringify!($id), + sql_stmt + )) + } + }; + ($vis:vis fn async $id:ident($($arg:ident: $arg_type:ty),+) -> Result<$return_type:ty> { $($sql:tt)+ }) => { + $vis async fn $id(&self, $($arg: $arg_type),+) -> $crate::anyhow::Result<$return_type> { + use $crate::anyhow::Context; + + + self.write(|connection| { + let sql_stmt = $crate::sqlez_macros::sql!($($sql)+); + + connection.select_row_bound::<($($arg_type),+), $return_type>(sql_stmt)?(($($arg),+)) + .context(::std::format!( + "Error in {}, select_row_bound failed to execute or parse for: {}", + ::std::stringify!($id), + sql_stmt + ))? + .context(::std::format!( + "Error in {}, select_row_bound expected single row result but found none for: {}", + ::std::stringify!($id), + sql_stmt + )) + }).await + } + }; +} diff --git a/crates/zed2/Cargo.toml b/crates/zed2/Cargo.toml index dc9204e241..171676e5c6 100644 --- a/crates/zed2/Cargo.toml +++ b/crates/zed2/Cargo.toml @@ -32,7 +32,7 @@ client2 = { path = "../client2" } # copilot = { path = "../copilot" } # copilot_button = { path = "../copilot_button" } # diagnostics = { path = "../diagnostics" } -# db = { path = "../db" } +db2 = { path = "../db2" } # editor = { path = "../editor" } # feedback = { path = "../feedback" } # file_finder = { path = "../file_finder" } diff --git a/crates/zed2/src/main.rs b/crates/zed2/src/main.rs index 91aed9e666..5826078431 100644 --- a/crates/zed2/src/main.rs +++ b/crates/zed2/src/main.rs @@ -8,6 +8,7 @@ use cli::{ ipc::{self, IpcSender}, CliRequest, CliResponse, IpcHandshake, FORCE_CLI_MODE_ENV_VAR_NAME, }; +use db2::kvp::KEY_VALUE_STORE; use fs::RealFs; use futures::{channel::mpsc, SinkExt, StreamExt}; use gpui2::{App, AppContext, AssetSource, AsyncAppContext, SemanticVersion, Task}; @@ -35,7 +36,7 @@ use std::{ }; use util::{ channel::{parse_zed_link, ReleaseChannel, RELEASE_CHANNEL}, - http::HttpClient, + http::{self, HttpClient}, paths, ResultExt, }; use uuid::Uuid; @@ -49,7 +50,7 @@ use zed2::{ensure_only_instance, AppState, Assets, IsOnlyInstance}; mod open_listener; fn main() { - // let http = http::client(); + let http = http::client(); init_paths(); init_logger(); @@ -60,9 +61,9 @@ fn main() { log::info!("========== starting zed =========="); let app = App::production(Arc::new(Assets)); - // let installation_id = app.executor().block(installation_id()).ok(); - // let session_id = Uuid::new_v4().to_string(); - // init_panic_hook(&app, installation_id.clone(), session_id.clone()); + let installation_id = app.executor().block(installation_id()).ok(); + let session_id = Uuid::new_v4().to_string(); + init_panic_hook(&app, installation_id.clone(), session_id.clone()); load_embedded_fonts(&app); @@ -107,7 +108,7 @@ fn main() { handle_settings_file_changes(user_settings_file_rx, cx); // handle_keymap_file_changes(user_keymap_file_rx, cx); - // let client = client::Client::new(http.clone(), cx); + let client = client2::Client::new(http.clone(), cx); // let mut languages = LanguageRegistry::new(login_shell_env_loaded); // let copilot_language_server_id = languages.next_language_server_id(); // languages.set_executor(cx.background().clone()); @@ -204,7 +205,7 @@ fn main() { listener.open_urls(urls) } } else { - // upload_previous_panics(http.clone(), cx); + upload_previous_panics(http.clone(), cx); // TODO Development mode that forces the CLI mode usually runs Zed binary as is instead // of an *app, hence gets no specific callbacks run. Emulate them here, if needed. @@ -296,21 +297,21 @@ fn main() { // Ok::<_, anyhow::Error>(()) // } -// async fn installation_id() -> Result { -// let legacy_key_name = "device_id"; +async fn installation_id() -> Result { + let legacy_key_name = "device_id"; -// if let Ok(Some(installation_id)) = KEY_VALUE_STORE.read_kvp(legacy_key_name) { -// Ok(installation_id) -// } else { -// let installation_id = Uuid::new_v4().to_string(); + if let Ok(Some(installation_id)) = KEY_VALUE_STORE.read_kvp(legacy_key_name) { + Ok(installation_id) + } else { + let installation_id = Uuid::new_v4().to_string(); -// KEY_VALUE_STORE -// .write_kvp(legacy_key_name.to_string(), installation_id.clone()) -// .await?; + KEY_VALUE_STORE + .write_kvp(legacy_key_name.to_string(), installation_id.clone()) + .await?; -// Ok(installation_id) -// } -// } + Ok(installation_id) + } +} async fn restore_or_create_workspace(_app_state: &Arc, mut _cx: AsyncAppContext) { todo!("workspace")