feat(sidebar): add nested virtual table groups with drag and batch move
This commit is contained in:
1 parent
0db9756a57
commit
1f43d184aa
31 files changed
+1714
-20
No files matched your search
@@ -775,6 +775,10 @@ const SCHEMA_STATEMENTS: &[&str] = &[
|
||||
id INTEGER PRIMARY KEY CHECK (id = 1),
|
||||
layout_json TEXT NOT NULL
|
||||
)",
|
||||
"CREATE TABLE IF NOT EXISTS table_vgroups (
|
||||
scope_key TEXT PRIMARY KEY,
|
||||
layout_json TEXT NOT NULL
|
||||
)",
|
||||
"CREATE TABLE IF NOT EXISTS app_settings (
|
||||
id INTEGER PRIMARY KEY CHECK (id = 1),
|
||||
settings_json TEXT NOT NULL
|
||||
@@ -4475,6 +4479,55 @@ impl Storage {
|
||||
.await?;
|
||||
json.map(|value| serde_json::from_str(&value).map_err(|e| e.to_string())).transpose()
|
||||
}
|
||||
|
||||
pub async fn save_table_vgroups(&self, scope_key: &str, layout: &serde_json::Value) -> Result<(), String> {
|
||||
let scope_key = scope_key.to_string();
|
||||
let json = serde_json::to_string(layout).map_err(|e| e.to_string())?;
|
||||
self.with_conn(move |conn| {
|
||||
conn.execute(
|
||||
"INSERT OR REPLACE INTO table_vgroups (scope_key, layout_json) VALUES (?1, ?2)",
|
||||
params![scope_key, json],
|
||||
)
|
||||
.map(|_| ())
|
||||
.map_err(|e| e.to_string())
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn load_table_vgroups(&self) -> Result<serde_json::Value, String> {
|
||||
self.with_conn(|conn| {
|
||||
let mut stmt =
|
||||
conn.prepare("SELECT scope_key, layout_json FROM table_vgroups").map_err(|e| e.to_string())?;
|
||||
let rows = stmt
|
||||
.query_map([], |row| Ok((row.get::<_, String>(0)?, row.get::<_, String>(1)?)))
|
||||
.map_err(|e| e.to_string())?;
|
||||
let mut layouts = serde_json::Map::new();
|
||||
for row in rows {
|
||||
let (scope_key, json) = row.map_err(|e| e.to_string())?;
|
||||
match serde_json::from_str::<serde_json::Value>(&json) {
|
||||
Ok(layout) => {
|
||||
layouts.insert(scope_key, layout);
|
||||
}
|
||||
Err(e) => warn!("Failed to deserialize table vgroups for scope {scope_key}: {e}"),
|
||||
}
|
||||
}
|
||||
Ok(serde_json::Value::Object(layouts))
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
/// 删除连接时清理其名下全部表分组布局(scope_key 前缀 = `{connectionId}\u{0}`)。
|
||||
pub async fn delete_table_vgroups_for_connection(&self, connection_id: &str) -> Result<(), String> {
|
||||
// 前缀用 instr 做大小写敏感的字节匹配:LIKE 默认大小写不敏感且把 `%`/`_` 当通配符,
|
||||
// 会连带删除 id 仅大小写不同或含通配符的连接行。
|
||||
let prefix = format!("{connection_id}\u{0}");
|
||||
self.with_conn(move |conn| {
|
||||
conn.execute("DELETE FROM table_vgroups WHERE instr(scope_key, ?1) = 1", params![prefix])
|
||||
.map(|_| ())
|
||||
.map_err(|e| e.to_string())
|
||||
})
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
// Schema cache
|
||||
@@ -5434,6 +5487,38 @@ mod tests {
|
||||
dir
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn delete_table_vgroups_for_connection_scopes_by_prefix() {
|
||||
let path = temp_db_path("vgroup-delete");
|
||||
let storage = Storage::open(&path).await.unwrap();
|
||||
|
||||
let layout = serde_json::json!({ "version": 1, "groups": [], "order": [] });
|
||||
// 真实 scope_key 形如 `{connectionId}\u{0}{linkedServer}\u{0}{catalog}\u{0}{database}\u{0}{schema}`。
|
||||
storage.save_table_vgroups("conn-1\u{0}\u{0}\u{0}db1\u{0}", &layout).await.unwrap();
|
||||
storage.save_table_vgroups("conn-1\u{0}\u{0}\u{0}db2\u{0}", &layout).await.unwrap();
|
||||
storage.save_table_vgroups("conn-2\u{0}\u{0}\u{0}db1\u{0}", &layout).await.unwrap();
|
||||
// 前缀恰好是另一连接 id 的连接不能被误删。
|
||||
storage.save_table_vgroups("conn-12\u{0}\u{0}\u{0}db1\u{0}", &layout).await.unwrap();
|
||||
// 仅大小写不同、或 id 含 LIKE 通配符的连接同样不能被误删。
|
||||
storage.save_table_vgroups("CONN-1\u{0}\u{0}\u{0}db1\u{0}", &layout).await.unwrap();
|
||||
storage.save_table_vgroups("conn_1\u{0}\u{0}\u{0}db1\u{0}", &layout).await.unwrap();
|
||||
|
||||
storage.delete_table_vgroups_for_connection("conn-1").await.unwrap();
|
||||
|
||||
let remaining = storage.load_table_vgroups().await.unwrap();
|
||||
let mut keys: Vec<&String> = remaining.as_object().unwrap().keys().collect();
|
||||
keys.sort();
|
||||
assert_eq!(
|
||||
keys,
|
||||
vec![
|
||||
"CONN-1\u{0}\u{0}\u{0}db1\u{0}",
|
||||
"conn-12\u{0}\u{0}\u{0}db1\u{0}",
|
||||
"conn-2\u{0}\u{0}\u{0}db1\u{0}",
|
||||
"conn_1\u{0}\u{0}\u{0}db1\u{0}",
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
fn file_mode(path: &std::path::Path) -> Option<u32> {
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
|
||||
Reference in new issue
Block a user