Skip to content

Commit b3fde9a

Browse files
committed
Add schema graph view
1 parent 80534f2 commit b3fde9a

3 files changed

Lines changed: 254 additions & 45 deletions

File tree

src-tauri/src/lib.rs

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@ struct GraphNode {
4646
offset: Option<usize>,
4747
#[serde(rename = "hiddenCount", skip_serializing_if = "Option::is_none")]
4848
hidden_count: Option<usize>,
49+
#[serde(rename = "colorKey", skip_serializing_if = "Option::is_none")]
50+
color_key: Option<String>,
4951
}
5052

5153
#[derive(Debug, Clone, Serialize, Deserialize)]
@@ -410,6 +412,7 @@ fn graph_node_from_value(val: &Value) -> Option<GraphNode> {
410412
expand_node_id: None,
411413
offset: None,
412414
hidden_count: None,
415+
color_key: None,
413416
})
414417
}
415418

@@ -516,6 +519,7 @@ fn make_expander_node(parent_id: &str, hidden_count: usize, offset: usize) -> Gr
516519
expand_node_id: Some(parent_id.to_string()),
517520
offset: Some(offset),
518521
hidden_count: Some(hidden_count),
522+
color_key: None,
519523
}
520524
}
521525

@@ -1071,6 +1075,7 @@ fn compute_cluster_levels(
10711075
expand_node_id: None,
10721076
offset: None,
10731077
hidden_count: None,
1078+
color_key: None,
10741079
})
10751080
.collect();
10761081
let cluster_node_index: HashMap<String, usize> = cluster_nodes
@@ -1298,6 +1303,7 @@ fn collect_edge_graph(conn: &Connection, limit: usize) -> Result<GraphData, Stri
12981303
expand_node_id: None,
12991304
offset: None,
13001305
hidden_count: None,
1306+
color_key: None,
13011307
},
13021308
);
13031309
}
@@ -1319,6 +1325,130 @@ fn collect_edge_graph(conn: &Connection, limit: usize) -> Result<GraphData, Stri
13191325
))
13201326
}
13211327

1328+
fn escaped_identifier(value: &str) -> String {
1329+
value.replace('\\', "\\\\").replace('\'', "\\'")
1330+
}
1331+
1332+
fn table_properties(conn: &Connection, table_name: &str) -> Vec<(String, String, bool)> {
1333+
let Ok(mut result) = conn.query(&format!(
1334+
"CALL TABLE_INFO('{}') RETURN *;",
1335+
escaped_identifier(table_name)
1336+
)) else {
1337+
return Vec::new();
1338+
};
1339+
1340+
let mut properties = Vec::new();
1341+
for row in &mut result {
1342+
let Some(name) = row.first().map(value_to_string) else {
1343+
continue;
1344+
};
1345+
let property_type = row
1346+
.get(1)
1347+
.map(value_to_string)
1348+
.unwrap_or_else(|| "UNKNOWN".to_string());
1349+
let is_primary_key = row.iter().any(|value| matches!(value, Value::Bool(true)));
1350+
properties.push((name, property_type, is_primary_key));
1351+
}
1352+
properties
1353+
}
1354+
1355+
fn schema_node_properties(
1356+
conn: &Connection,
1357+
table_name: &str,
1358+
include_primary_key: bool,
1359+
) -> HashMap<String, String> {
1360+
table_properties(conn, table_name)
1361+
.into_iter()
1362+
.map(|(name, property_type, is_primary_key)| {
1363+
let value = if include_primary_key && is_primary_key {
1364+
format!("{property_type} primary key")
1365+
} else {
1366+
property_type
1367+
};
1368+
(name, value)
1369+
})
1370+
.collect()
1371+
}
1372+
1373+
fn collect_schema_graph(conn: &Connection) -> Result<GraphData, String> {
1374+
let mut tables = conn
1375+
.query("CALL show_tables() RETURN *;")
1376+
.map_err(|e| format!("Schema table query failed: {e}"))?;
1377+
1378+
let mut node_tables = Vec::new();
1379+
let mut rel_tables = Vec::new();
1380+
for row in &mut tables {
1381+
if row.len() < 2 {
1382+
continue;
1383+
}
1384+
let table_name = value_to_string(&row[0]);
1385+
let table_type = value_to_string(&row[1]);
1386+
if table_type.eq_ignore_ascii_case("NODE") {
1387+
node_tables.push(table_name);
1388+
} else if table_type.eq_ignore_ascii_case("REL") {
1389+
rel_tables.push(table_name);
1390+
}
1391+
}
1392+
1393+
node_tables.sort();
1394+
rel_tables.sort();
1395+
1396+
let nodes: Vec<GraphNode> = node_tables
1397+
.iter()
1398+
.map(|table_name| GraphNode {
1399+
id: table_name.clone(),
1400+
name: table_name.clone(),
1401+
label: "Node Table".to_string(),
1402+
properties: schema_node_properties(conn, table_name, true),
1403+
table_id: None,
1404+
rowid: None,
1405+
community: None,
1406+
expansion_kind: None,
1407+
expand_node_id: None,
1408+
offset: None,
1409+
hidden_count: None,
1410+
color_key: Some(format!("schema-node:{table_name}")),
1411+
})
1412+
.collect();
1413+
1414+
let node_table_set: HashSet<&str> = node_tables.iter().map(String::as_str).collect();
1415+
let mut links = Vec::new();
1416+
let mut seen_links = HashSet::new();
1417+
1418+
for rel_table in &rel_tables {
1419+
let Ok(mut result) = conn.query(&format!(
1420+
"CALL SHOW_CONNECTION('{}') RETURN *;",
1421+
escaped_identifier(rel_table)
1422+
)) else {
1423+
continue;
1424+
};
1425+
1426+
for row in &mut result {
1427+
if row.len() < 2 {
1428+
continue;
1429+
}
1430+
let source = value_to_string(&row[0]);
1431+
let target = value_to_string(&row[1]);
1432+
if !node_table_set.contains(source.as_str())
1433+
|| !node_table_set.contains(target.as_str())
1434+
{
1435+
continue;
1436+
}
1437+
merge_link(
1438+
&mut links,
1439+
&mut seen_links,
1440+
GraphLink {
1441+
source,
1442+
target,
1443+
label: rel_table.clone(),
1444+
},
1445+
);
1446+
}
1447+
}
1448+
1449+
Ok(graph_data_without_clusters(nodes, links, "collect_schema_graph"))
1450+
}
1451+
13221452
fn add_expanders(
13231453
graph: &GraphData,
13241454
visible_ids: &HashSet<String>,
@@ -1678,6 +1808,18 @@ fn get_graph(
16781808
.map(|full_graph| seed_graph_from_full(full_graph, llm_config.as_ref()))
16791809
}
16801810

1811+
#[tauri::command]
1812+
fn get_schema_graph(state: State<AppState>, id: usize) -> Result<GraphData, String> {
1813+
let databases = get_all_databases(&state);
1814+
let db_info = databases.get(id).ok_or("Database not found")?;
1815+
1816+
let db = Database::new(&db_info.path, SystemConfig::default())
1817+
.map_err(|e| format!("Failed to open database: {}", e))?;
1818+
let conn = Connection::new(&db).map_err(|e| format!("Failed to create connection: {}", e))?;
1819+
1820+
collect_schema_graph(&conn)
1821+
}
1822+
16811823
#[tauri::command]
16821824
fn search_nodes(
16831825
state: State<AppState>,
@@ -1885,6 +2027,7 @@ fn execute_query(
18852027
expand_node_id: None,
18862028
offset: None,
18872029
hidden_count: None,
2030+
color_key: None,
18882031
});
18892032
}
18902033
}
@@ -1952,6 +2095,7 @@ pub fn run() {
19522095
add_database,
19532096
get_directories,
19542097
get_graph,
2098+
get_schema_graph,
19552099
search_nodes,
19562100
get_node_neighborhood,
19572101
expand_node,

src/App.css

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -374,6 +374,11 @@ body {
374374
color: white;
375375
}
376376

377+
.cluster-controls button:disabled {
378+
cursor: not-allowed;
379+
opacity: 0.55;
380+
}
381+
377382
.cluster-controls button:hover:not(.active) {
378383
color: var(--text-primary);
379384
}

0 commit comments

Comments
 (0)