Execute a single triple `(src)-[edge]->(dst)` against a binding row.
(
triple: &PatternTriple,
csr: &CsrIndex,
input_row: &BindingRow,
state: &mut ExecutionState,
frontier_bitmap: Option<&nodedb_types::SurrogateBitmap>,
)
| 174 | |
| 175 | /// Execute a single triple `(src)-[edge]->(dst)` against a binding row. |
| 176 | fn execute_triple( |
| 177 | triple: &PatternTriple, |
| 178 | csr: &CsrIndex, |
| 179 | input_row: &BindingRow, |
| 180 | state: &mut ExecutionState, |
| 181 | frontier_bitmap: Option<&nodedb_types::SurrogateBitmap>, |
| 182 | ) -> Result<Vec<BindingRow>, crate::Error> { |
| 183 | let direction = triple.edge.direction.to_csr_direction(); |
| 184 | let label_filter = triple.edge.edge_type.as_deref(); |
| 185 | let src_nodes = resolve_binding(&triple.src, csr, input_row, frontier_bitmap); |
| 186 | |
| 187 | if src_nodes.is_empty() { |
| 188 | return Ok(Vec::new()); |
| 189 | } |
| 190 | |
| 191 | let mut results = Vec::new(); |
| 192 | |
| 193 | if triple.edge.is_variable_length() { |
| 194 | // Path strings are only needed when the edge variable is bound |
| 195 | // (e.g. `(a)-[e*1..3]->(b) RETURN e`). For anonymous variable |
| 196 | // expansions skip all `format!`/`String` work in the hot loop. |
| 197 | let want_path = triple.edge.name.is_some(); |
| 198 | for &src_id in &src_nodes { |
| 199 | let expansion = expansion::expand_variable_length( |
| 200 | csr, |
| 201 | src_id, |
| 202 | label_filter, |
| 203 | direction, |
| 204 | triple.edge.min_hops, |
| 205 | triple.edge.max_hops, |
| 206 | want_path, |
| 207 | ); |
| 208 | if expansion.truncated { |
| 209 | state.truncated = true; |
| 210 | } |
| 211 | for (dst_id, path) in expansion.results { |
| 212 | if !binding_compatible(&triple.dst, csr, input_row, dst_id) { |
| 213 | continue; |
| 214 | } |
| 215 | let mut row = input_row.clone(); |
| 216 | bind_node(&mut row, &triple.src, csr, src_id); |
| 217 | bind_node(&mut row, &triple.dst, csr, dst_id); |
| 218 | if let Some(ref edge_name) = triple.edge.name { |
| 219 | row.insert(edge_name.clone(), path); |
| 220 | } |
| 221 | results.push(row); |
| 222 | } |
| 223 | } |
| 224 | } else { |
| 225 | for &src_id in &src_nodes { |
| 226 | let neighbors = expansion::collect_neighbors(csr, src_id, label_filter, direction); |
| 227 | for (lid, dst_id) in neighbors { |
| 228 | if !binding_compatible(&triple.dst, csr, input_row, dst_id) { |
| 229 | continue; |
| 230 | } |
| 231 | let mut row = input_row.clone(); |
| 232 | bind_node(&mut row, &triple.src, csr, src_id); |
| 233 | bind_node(&mut row, &triple.dst, csr, dst_id); |
no test coverage detected