@@ -1,6 +1,6 @@
+mod items;
mod kvp;
mod migrations;
-mod serialized_item;
use anyhow::Result;
use migrations::MIGRATIONS;
@@ -9,8 +9,8 @@ use rusqlite::Connection;
use std::path::Path;
use std::sync::Arc;
+pub use items::*;
pub use kvp::*;
-pub use serialized_item::*;
pub struct Db {
connection: Mutex<Connection>,
@@ -46,39 +46,3 @@ impl Db {
}))
}
}
-
-#[cfg(test)]
-mod tests {
- use super::*;
- use tempdir::TempDir;
-
- #[gpui::test]
- fn test_db() {
- let dir = TempDir::new("db-test").unwrap();
- let fake_db = Db::open_in_memory().unwrap();
- let real_db = Db::open(&dir.path().join("test.db")).unwrap();
-
- for db in [&real_db, &fake_db] {
- assert_eq!(db.read_kvp("key-1").unwrap(), None);
-
- db.write_kvp("key-1", "one").unwrap();
- assert_eq!(db.read_kvp("key-1").unwrap(), Some("one".to_string()));
-
- db.write_kvp("key-2", "two").unwrap();
- assert_eq!(db.read_kvp("key-2").unwrap(), Some("two".to_string()));
-
- db.delete_kvp("key-1").unwrap();
- assert_eq!(db.read_kvp("key-1").unwrap(), None);
- }
-
- drop(real_db);
-
- let real_db = Db::open(&dir.path().join("test.db")).unwrap();
-
- real_db.write_kvp("key-1", "one").unwrap();
- assert_eq!(real_db.read_kvp("key-1").unwrap(), None);
-
- real_db.write_kvp("key-2", "two").unwrap();
- assert_eq!(real_db.read_kvp("key-2").unwrap(), Some("two".to_string()));
- }
-}
@@ -0,0 +1,236 @@
+use std::{ffi::OsStr, os::unix::prelude::OsStrExt, path::PathBuf, sync::Arc};
+
+use anyhow::Result;
+use rusqlite::{
+ named_params, params,
+ types::{FromSql, FromSqlError, FromSqlResult, ValueRef},
+};
+
+use super::Db;
+
+pub(crate) const ITEMS_M_1: &str = "
+CREATE TABLE items(
+ id INTEGER PRIMARY KEY,
+ kind TEXT
+) STRICT;
+CREATE TABLE item_path(
+ item_id INTEGER PRIMARY KEY,
+ path BLOB
+) STRICT;
+CREATE TABLE item_query(
+ item_id INTEGER PRIMARY KEY,
+ query TEXT
+) STRICT;
+";
+
+#[derive(PartialEq, Eq, Hash, Debug)]
+pub enum SerializedItemKind {
+ Editor,
+ Terminal,
+ ProjectSearch,
+ Diagnostics,
+}
+
+#[derive(Clone, Debug, PartialEq, Eq)]
+pub enum SerializedItem {
+ Editor(usize, PathBuf),
+ Terminal(usize),
+ ProjectSearch(usize, String),
+ Diagnostics(usize),
+}
+
+impl FromSql for SerializedItemKind {
+ fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
+ match value {
+ ValueRef::Null => Err(FromSqlError::InvalidType),
+ ValueRef::Integer(_) => Err(FromSqlError::InvalidType),
+ ValueRef::Real(_) => Err(FromSqlError::InvalidType),
+ ValueRef::Text(bytes) => {
+ let str = std::str::from_utf8(bytes).map_err(|_| FromSqlError::InvalidType)?;
+ match str {
+ "Editor" => Ok(SerializedItemKind::Editor),
+ "Terminal" => Ok(SerializedItemKind::Terminal),
+ "ProjectSearch" => Ok(SerializedItemKind::ProjectSearch),
+ "Diagnostics" => Ok(SerializedItemKind::Diagnostics),
+ _ => Err(FromSqlError::InvalidType),
+ }
+ }
+ ValueRef::Blob(_) => Err(FromSqlError::InvalidType),
+ }
+ }
+}
+
+impl SerializedItem {
+ fn kind(&self) -> SerializedItemKind {
+ match self {
+ SerializedItem::Editor(_, _) => SerializedItemKind::Editor,
+ SerializedItem::Terminal(_) => SerializedItemKind::Terminal,
+ SerializedItem::ProjectSearch(_, _) => SerializedItemKind::ProjectSearch,
+ SerializedItem::Diagnostics(_) => SerializedItemKind::Diagnostics,
+ }
+ }
+
+ fn id(&self) -> usize {
+ match self {
+ SerializedItem::Editor(id, _)
+ | SerializedItem::Terminal(id)
+ | SerializedItem::ProjectSearch(id, _)
+ | SerializedItem::Diagnostics(id) => *id,
+ }
+ }
+}
+
+impl Db {
+ fn write_item(&self, serialized_item: SerializedItem) -> Result<()> {
+ let mut lock = self.connection.lock();
+ let tx = lock.transaction()?;
+
+ // Serialize the item
+ let id = serialized_item.id();
+ {
+ let kind = format!("{:?}", serialized_item.kind());
+
+ let mut stmt =
+ tx.prepare_cached("INSERT OR REPLACE INTO items(id, kind) VALUES ((?), (?))")?;
+
+ stmt.execute(params![id, kind])?;
+ }
+
+ // Serialize item data
+ match &serialized_item {
+ SerializedItem::Editor(_, path) => {
+ let mut stmt = tx.prepare_cached(
+ "INSERT OR REPLACE INTO item_path(item_id, path) VALUES ((?), (?))",
+ )?;
+
+ let path_bytes = path.as_os_str().as_bytes();
+ stmt.execute(params![id, path_bytes])?;
+ }
+ SerializedItem::ProjectSearch(_, query) => {
+ let mut stmt = tx.prepare_cached(
+ "INSERT OR REPLACE INTO item_query(item_id, query) VALUES ((?), (?))",
+ )?;
+
+ stmt.execute(params![id, query])?;
+ }
+ _ => {}
+ }
+
+ tx.commit()?;
+
+ Ok(())
+ }
+
+ fn delete_item(&self, item_id: usize) -> Result<()> {
+ let lock = self.connection.lock();
+
+ let mut stmt = lock.prepare_cached(
+ "
+ DELETE FROM items WHERE id = (:id);
+ DELETE FROM item_path WHERE id = (:id);
+ DELETE FROM item_query WHERE id = (:id);
+ ",
+ )?;
+
+ stmt.execute(named_params! {":id": item_id})?;
+
+ Ok(())
+ }
+
+ fn take_items(&self) -> Result<Vec<SerializedItem>> {
+ let mut lock = self.connection.lock();
+
+ let tx = lock.transaction()?;
+
+ // When working with transactions in rusqlite, need to make this kind of scope
+ // To make the borrow stuff work correctly. Don't know why, rust is wild.
+ let result = {
+ let mut read_stmt = tx.prepare_cached(
+ "
+ SELECT items.id, items.kind, item_path.path, item_query.query
+ FROM items
+ LEFT JOIN item_path
+ ON items.id = item_path.item_id
+ LEFT JOIN item_query
+ ON items.id = item_query.item_id
+ ORDER BY items.id
+ ",
+ )?;
+
+ let result = read_stmt
+ .query_map([], |row| {
+ let id: usize = row.get(0)?;
+ let kind: SerializedItemKind = row.get(1)?;
+
+ match kind {
+ SerializedItemKind::Editor => {
+ let buf: Vec<u8> = row.get(2)?;
+ let path: PathBuf = OsStr::from_bytes(&buf).into();
+
+ Ok(SerializedItem::Editor(id, path))
+ }
+ SerializedItemKind::Terminal => Ok(SerializedItem::Terminal(id)),
+ SerializedItemKind::ProjectSearch => {
+ let query: Arc<str> = row.get(3)?;
+ Ok(SerializedItem::ProjectSearch(id, query.to_string()))
+ }
+ SerializedItemKind::Diagnostics => Ok(SerializedItem::Diagnostics(id)),
+ }
+ })?
+ .collect::<Result<Vec<SerializedItem>, rusqlite::Error>>()?;
+
+ let mut delete_stmt = tx.prepare_cached(
+ "DELETE FROM items;
+ DELETE FROM item_path;
+ DELETE FROM item_query;",
+ )?;
+
+ delete_stmt.execute([])?;
+
+ result
+ };
+
+ tx.commit()?;
+
+ Ok(result)
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use anyhow::Result;
+
+ use super::*;
+
+ #[test]
+ fn test_items_round_trip() -> Result<()> {
+ let db = Db::open_in_memory()?;
+
+ let mut items = vec![
+ SerializedItem::Editor(0, PathBuf::from("/tmp/test.txt")),
+ SerializedItem::Terminal(1),
+ SerializedItem::ProjectSearch(2, "Test query!".to_string()),
+ SerializedItem::Diagnostics(3),
+ ];
+
+ for item in items.iter() {
+ db.write_item(item.clone())?;
+ }
+
+ assert_eq!(items, db.take_items()?);
+
+ // Check that it's empty, as expected
+ assert_eq!(Vec::<SerializedItem>::new(), db.take_items()?);
+
+ for item in items.iter() {
+ db.write_item(item.clone())?;
+ }
+
+ items.remove(2);
+ db.delete_item(2)?;
+
+ assert_eq!(items, db.take_items()?);
+
+ Ok(())
+ }
+}
@@ -3,6 +3,13 @@ use rusqlite::OptionalExtension;
use super::Db;
+pub(crate) const KVP_M_1: &str = "
+CREATE TABLE kv_store(
+ key TEXT PRIMARY KEY,
+ value TEXT NOT NULL
+) STRICT;
+";
+
impl Db {
pub fn read_kvp(&self, key: &str) -> Result<Option<String>> {
let lock = self.connection.lock();
@@ -14,7 +21,7 @@ impl Db {
pub fn delete_kvp(&self, key: &str) -> Result<()> {
let lock = self.connection.lock();
- let mut stmt = lock.prepare_cached("SELECT value FROM kv_store WHERE key = (?)")?;
+ let mut stmt = lock.prepare_cached("DELETE FROM kv_store WHERE key = (?)")?;
stmt.execute([key])?;
@@ -32,3 +39,31 @@ impl Db {
Ok(())
}
}
+
+#[cfg(test)]
+mod tests {
+ use anyhow::Result;
+
+ use super::*;
+
+ #[test]
+ fn test_kvp() -> Result<()> {
+ let db = Db::open_in_memory()?;
+
+ assert_eq!(db.read_kvp("key-1")?, None);
+
+ db.write_kvp("key-1", "one")?;
+ assert_eq!(db.read_kvp("key-1")?, Some("one".to_string()));
+
+ db.write_kvp("key-1", "one-2")?;
+ assert_eq!(db.read_kvp("key-1")?, Some("one-2".to_string()));
+
+ db.write_kvp("key-2", "two")?;
+ assert_eq!(db.read_kvp("key-2")?, Some("two".to_string()));
+
+ db.delete_kvp("key-1")?;
+ assert_eq!(db.read_kvp("key-1")?, None);
+
+ Ok(())
+ }
+}
@@ -1,22 +0,0 @@
-use std::path::PathBuf;
-
-use anyhow::Result;
-
-use super::Db;
-
-impl Db {}
-
-#[derive(PartialEq, Eq, Hash)]
-pub enum SerializedItemKind {
- Editor,
- Terminal,
- ProjectSearch,
- Diagnostics,
-}
-
-pub enum SerializedItem {
- Editor(PathBuf, String),
- Terminal,
- ProjectSearch(String),
- Diagnostics,
-}