(
&self,
collection: &str,
start: &NodeId,
depth: u8,
edge_filter: Option<&EdgeFilter>,
)
| 18 | |
| 19 | impl NodeDbRemote { |
| 20 | pub(super) async fn graph_traverse_impl( |
| 21 | &self, |
| 22 | collection: &str, |
| 23 | start: &NodeId, |
| 24 | depth: u8, |
| 25 | edge_filter: Option<&EdgeFilter>, |
| 26 | ) -> NodeDbResult<SubGraph> { |
| 27 | // Server-side DSL: `GRAPH TRAVERSE FROM '<start>' DEPTH <n> |
| 28 | // [LABEL '<l>']`. The Origin graph overlay is tenant-scoped |
| 29 | // (the dispatcher routes on `identity.tenant_id`), so the |
| 30 | // `collection` argument is accepted for trait symmetry with |
| 31 | // `graph_insert_edge` and Lite parity but is not threaded into |
| 32 | // the wire DSL — every edge in the tenant participates in the |
| 33 | // traversal regardless of which collection it was inserted |
| 34 | // into. |
| 35 | let _ = collection; |
| 36 | let label_clause = edge_filter |
| 37 | .and_then(|f| f.labels.first()) |
| 38 | .map(|l| format!(" LABEL {}", quote_string_literal(l))) |
| 39 | .unwrap_or_default(); |
| 40 | let start_lit = quote_string_literal(start.as_str()); |
| 41 | let sql = format!("GRAPH TRAVERSE FROM {start_lit} DEPTH {depth}{label_clause}"); |
| 42 | |
| 43 | let (columns, rows) = self.simple_query_raw(&sql).await?; |
| 44 | |
| 45 | if columns.len() == 1 && columns[0] == "result" { |
| 46 | if let Some(row) = rows.first() |
| 47 | && let Some(Value::String(json_text)) = row.first() |
| 48 | { |
| 49 | return parse_graph_traverse_json(json_text); |
| 50 | } |
| 51 | return Ok(SubGraph::empty()); |
| 52 | } |
| 53 | |
| 54 | // Structured: node_id, depth, edge_src, edge_dst, edge_label columns. |
| 55 | let mut nodes = Vec::new(); |
| 56 | let mut edges = Vec::new(); |
| 57 | let mut seen_nodes = std::collections::HashSet::new(); |
| 58 | |
| 59 | for row in &rows { |
| 60 | let node_id_str = row.first().and_then(|v| v.as_str()).unwrap_or(""); |
| 61 | let d = row.get(1).and_then(|v| v.as_i64()).unwrap_or(0) as u8; |
| 62 | |
| 63 | if seen_nodes.insert(node_id_str.to_string()) { |
| 64 | nodes.push(SubGraphNode { |
| 65 | id: NodeId::from_validated(node_id_str.to_owned()), |
| 66 | depth: d, |
| 67 | properties: HashMap::new(), |
| 68 | }); |
| 69 | } |
| 70 | |
| 71 | if let (Some(src), Some(dst), Some(label)) = ( |
| 72 | row.get(2).and_then(|v| v.as_str()), |
| 73 | row.get(3).and_then(|v| v.as_str()), |
| 74 | row.get(4).and_then(|v| v.as_str()), |
| 75 | ) { |
| 76 | edges.push(SubGraphEdge { |
| 77 | id: EdgeId::try_first( |
no test coverage detected