1use gpui::App;
2use sqlez_macros::sql;
3use util::ResultExt as _;
4
5use crate::{
6 query,
7 sqlez::{domain::Domain, thread_safe_connection::ThreadSafeConnection},
8 write_and_log,
9};
10
11pub struct KeyValueStore(crate::sqlez::thread_safe_connection::ThreadSafeConnection);
12
13impl Domain for KeyValueStore {
14 const NAME: &str = stringify!(KeyValueStore);
15
16 const MIGRATIONS: &[&str] = &[sql!(
17 CREATE TABLE IF NOT EXISTS kv_store(
18 key TEXT PRIMARY KEY,
19 value TEXT NOT NULL
20 ) STRICT;
21 )];
22}
23
24crate::static_connection!(KEY_VALUE_STORE, KeyValueStore, []);
25
26pub trait Dismissable {
27 const KEY: &'static str;
28
29 fn dismissed() -> bool {
30 KEY_VALUE_STORE
31 .read_kvp(Self::KEY)
32 .log_err()
33 .is_some_and(|s| s.is_some())
34 }
35
36 fn set_dismissed(is_dismissed: bool, cx: &mut App) {
37 write_and_log(cx, move || async move {
38 if is_dismissed {
39 KEY_VALUE_STORE
40 .write_kvp(Self::KEY.into(), "1".into())
41 .await
42 } else {
43 KEY_VALUE_STORE.delete_kvp(Self::KEY.into()).await
44 }
45 })
46 }
47}
48
49impl KeyValueStore {
50 query! {
51 pub fn read_kvp(key: &str) -> Result<Option<String>> {
52 SELECT value FROM kv_store WHERE key = (?)
53 }
54 }
55
56 pub async fn write_kvp(&self, key: String, value: String) -> anyhow::Result<()> {
57 log::debug!("Writing key-value pair for key {key}");
58 self.write_kvp_inner(key, value).await
59 }
60
61 query! {
62 async fn write_kvp_inner(key: String, value: String) -> Result<()> {
63 INSERT OR REPLACE INTO kv_store(key, value) VALUES ((?), (?))
64 }
65 }
66
67 query! {
68 pub async fn delete_kvp(key: String) -> Result<()> {
69 DELETE FROM kv_store WHERE key = (?)
70 }
71 }
72}
73
74#[cfg(test)]
75mod tests {
76 use crate::kvp::KeyValueStore;
77
78 #[gpui::test]
79 async fn test_kvp() {
80 let db = KeyValueStore::open_test_db("test_kvp").await;
81
82 assert_eq!(db.read_kvp("key-1").unwrap(), None);
83
84 db.write_kvp("key-1".to_string(), "one".to_string())
85 .await
86 .unwrap();
87 assert_eq!(db.read_kvp("key-1").unwrap(), Some("one".to_string()));
88
89 db.write_kvp("key-1".to_string(), "one-2".to_string())
90 .await
91 .unwrap();
92 assert_eq!(db.read_kvp("key-1").unwrap(), Some("one-2".to_string()));
93
94 db.write_kvp("key-2".to_string(), "two".to_string())
95 .await
96 .unwrap();
97 assert_eq!(db.read_kvp("key-2").unwrap(), Some("two".to_string()));
98
99 db.delete_kvp("key-1".to_string()).await.unwrap();
100 assert_eq!(db.read_kvp("key-1").unwrap(), None);
101 }
102}
103
104pub struct GlobalKeyValueStore(ThreadSafeConnection);
105
106impl Domain for GlobalKeyValueStore {
107 const NAME: &str = stringify!(GlobalKeyValueStore);
108 const MIGRATIONS: &[&str] = &[sql!(
109 CREATE TABLE IF NOT EXISTS kv_store(
110 key TEXT PRIMARY KEY,
111 value TEXT NOT NULL
112 ) STRICT;
113 )];
114}
115
116crate::static_connection!(GLOBAL_KEY_VALUE_STORE, GlobalKeyValueStore, [], global);
117
118impl GlobalKeyValueStore {
119 query! {
120 pub fn read_kvp(key: &str) -> Result<Option<String>> {
121 SELECT value FROM kv_store WHERE key = (?)
122 }
123 }
124
125 pub async fn write_kvp(&self, key: String, value: String) -> anyhow::Result<()> {
126 log::debug!("Writing global key-value pair for key {key}");
127 self.write_kvp_inner(key, value).await
128 }
129
130 query! {
131 async fn write_kvp_inner(key: String, value: String) -> Result<()> {
132 INSERT OR REPLACE INTO kv_store(key, value) VALUES ((?), (?))
133 }
134 }
135
136 query! {
137 pub async fn delete_kvp(key: String) -> Result<()> {
138 DELETE FROM kv_store WHERE key = (?)
139 }
140 }
141}