use super::super::ast::*;
use super::helpers::*;
use super::*;
use crate::datatypes::values::Value;
use crate::graph::core::pattern_matching::{
EdgeDirection, MatchBinding, NodePattern, Pattern, PatternElement, PatternExecutor,
PatternMatch, PropertyMatcher,
};
use crate::graph::schema::InternedKey;
use crate::graph::storage::GraphRead;
use petgraph::graph::{EdgeIndex, NodeIndex};
use petgraph::Direction;
use rustc_hash::{FxHashMap, FxHashSet};
use std::collections::{HashMap, HashSet};
use std::sync::Arc;
pub(super) fn match_edge_indices(m: &PatternMatch, out: &mut Vec<EdgeIndex>) {
let push = |edge: EdgeIndex, out: &mut Vec<EdgeIndex>| {
if !out.contains(&edge) {
out.push(edge);
}
};
if let Some((_, path)) = m.exact_path.as_deref() {
for hop in path {
push(hop.edge, out);
}
}
for (_, binding) in &m.bindings {
match binding {
MatchBinding::Edge { edge_index, .. } => push(*edge_index, out),
MatchBinding::VariableLengthPath { path, .. } => {
for hop in path {
push(hop.edge, out);
}
}
_ => {}
}
}
}
pub(super) fn row_edge_indices(row: &ResultRow) -> Vec<EdgeIndex> {
let mut out = Vec::new();
for (_, eb) in &row.edge_bindings {
if !out.contains(&eb.edge_index) {
out.push(eb.edge_index);
}
}
for (_, pb) in &row.path_bindings {
for hop in &pb.path {
if !out.contains(&hop.edge) {
out.push(hop.edge);
}
}
}
out
}
pub(super) fn null_pad_pattern_vars(row: &mut ResultRow, clause: &MatchClause) {
for pattern in &clause.patterns {
for elem in &pattern.elements {
match elem {
PatternElement::Node(np) => {
if let Some(ref var) = np.variable {
if !row.node_bindings.contains_key(var) && !row.projected.contains_key(var)
{
row.projected.insert(var.clone(), Value::Null);
}
}
}
PatternElement::Edge(ep) => {
if let Some(ref var) = ep.variable {
if !row.edge_bindings.contains_key(var) && !row.projected.contains_key(var)
{
row.projected.insert(var.clone(), Value::Null);
}
}
}
}
}
}
}
pub(super) fn clause_needs_rel_uniqueness(clause: &MatchClause) -> bool {
patterns_need_rel_uniqueness(&clause.patterns)
}
pub(super) fn patterns_need_rel_uniqueness(patterns: &[Pattern]) -> bool {
patterns.len() > 1 && patterns.iter().filter(|p| p.elements.len() > 1).count() >= 2
}
pub(super) fn grouped_patterns_need_rel_uniqueness(patterns: &[Pattern], groups: &[usize]) -> bool {
let mut run_group: Option<usize> = None;
let mut run_edge_count = 0usize;
for (pi, pattern) in patterns.iter().enumerate() {
if pattern.elements.len() <= 1 {
continue;
}
let group = groups.get(pi).copied().unwrap_or(0);
if run_group == Some(group) {
run_edge_count += 1;
if run_edge_count >= 2 {
return true;
}
} else {
run_group = Some(group);
run_edge_count = 1;
}
}
false
}
pub(super) fn row_bound_edge(
row: &ResultRow,
var: &str,
) -> Option<(NodeIndex, NodeIndex, EdgeIndex)> {
if let Some(eb) = row.edge_bindings.get(var) {
return Some((eb.source, eb.target, eb.edge_index));
}
if let Some(Value::Relationship(rel)) = row.projected.get(var) {
return Some((
NodeIndex::new(rel.start_id as usize),
NodeIndex::new(rel.end_id as usize),
EdgeIndex::new(rel.id as usize),
));
}
None
}
pub(super) fn row_projected_node(row: &ResultRow, var: &str) -> Option<NodeIndex> {
match row.projected.get(var) {
Some(Value::Node(nv)) => Some(NodeIndex::new(nv.id as usize)),
Some(Value::NodeRef(i)) => Some(NodeIndex::new(*i as usize)),
_ => None,
}
}
fn seed_projected_node_vars(pattern: &Pattern, row: &ResultRow) -> Option<Bindings<NodeIndex>> {
let mut extended: Option<Bindings<NodeIndex>> = None;
for elem in &pattern.elements {
let PatternElement::Node(np) = elem else {
continue;
};
let Some(var) = np.variable.as_ref() else {
continue;
};
if row.node_bindings.contains_key(var) {
continue; }
let Some(idx) = row_projected_node(row, var) else {
continue;
};
let ext = extended.get_or_insert_with(|| row.node_bindings.clone());
if !ext.contains_key(var) {
ext.insert(var.clone(), idx);
}
}
extended
}
pub(super) fn seed_prebound_pattern_vars(
pattern: &Pattern,
row: &ResultRow,
) -> Option<Bindings<NodeIndex>> {
let mut extended = seed_projected_node_vars(pattern, row);
for (i, elem) in pattern.elements.iter().enumerate() {
let PatternElement::Edge(ep) = elem else {
continue;
};
if ep.var_length.is_some() {
continue;
}
let Some(var) = ep.variable.as_deref() else {
continue;
};
let Some((src, tgt, _)) = row_bound_edge(row, var) else {
continue;
};
let (left, right) = match ep.direction {
EdgeDirection::Outgoing => (src, tgt),
EdgeDirection::Incoming => (tgt, src),
EdgeDirection::Both => continue,
};
let mut seed = |node_elem: Option<&PatternElement>, idx: NodeIndex| {
let Some(PatternElement::Node(np)) = node_elem else {
return;
};
let Some(node_var) = np.variable.as_ref() else {
return;
};
if row.node_bindings.contains_key(node_var) {
return; }
let ext = extended.get_or_insert_with(|| row.node_bindings.clone());
if !ext.contains_key(node_var) {
ext.insert(node_var.clone(), idx);
}
};
seed(i.checked_sub(1).and_then(|j| pattern.elements.get(j)), left);
seed(pattern.elements.get(i + 1), right);
}
extended
}
fn count_call_is_star(args: &[Expression]) -> bool {
args.is_empty() || matches!(args[0], Expression::Star)
}
fn substitute_count_with_value(expr: &Expression, star_value: i64, var_value: i64) -> Expression {
let subst = |e: &Expression| Box::new(substitute_count_with_value(e, star_value, var_value));
match expr {
Expression::FunctionCall { name, args, .. } if name == "count" => {
let value = if count_call_is_star(args) {
star_value
} else {
var_value
};
Expression::Literal(Value::Int64(value))
}
Expression::Add(l, r) => Expression::Add(subst(l), subst(r)),
Expression::Subtract(l, r) => Expression::Subtract(subst(l), subst(r)),
Expression::Multiply(l, r) => Expression::Multiply(subst(l), subst(r)),
Expression::Divide(l, r) => Expression::Divide(subst(l), subst(r)),
Expression::Modulo(l, r) => Expression::Modulo(subst(l), subst(r)),
Expression::Negate(inner) => Expression::Negate(subst(inner)),
other => other.clone(),
}
}
impl<'a> CypherExecutor<'a> {
pub(super) fn pattern_match_to_row(&self, m: PatternMatch) -> ResultRow {
let PatternMatch {
bindings,
exact_path,
} = m;
let binding_count = bindings.len();
let mut row = ResultRow::with_capacity(binding_count, binding_count / 2, 0);
if let Some(exact_path) = exact_path {
let (source, path) = *exact_path;
row.path_bindings.insert(
"__fixed_path".to_string(),
PathBinding {
source,
hops: path.len(),
path,
},
);
}
for (var, binding) in bindings {
match binding {
MatchBinding::Node { index, .. } | MatchBinding::NodeRef(index) => {
row.node_bindings.insert(var, index);
}
MatchBinding::Edge {
source,
target,
edge_index,
..
} => {
row.edge_bindings.insert(
var,
EdgeBinding {
source,
target,
edge_index,
},
);
}
MatchBinding::VariableLengthPath {
source, hops, path, ..
} => {
row.path_bindings
.insert(var, PathBinding { source, hops, path });
}
}
}
row
}
pub(super) fn stream_expand_optional(
&self,
clause: &MatchClause,
row: &ResultRow,
) -> Result<Vec<ResultRow>, String> {
self.expand_optional_match_row(clause, row, 0)
}
fn expand_optional_match_row(
&self,
clause: &MatchClause,
row: &ResultRow,
budget_rows_base: usize,
) -> Result<Vec<ResultRow>, String> {
let enforce_rel_uniqueness = clause_needs_rel_uniqueness(clause);
let mut row_set: Vec<ResultRow> = vec![row.clone()];
let mut edge_sets: Vec<Vec<EdgeIndex>> = if enforce_rel_uniqueness {
vec![Vec::new()]
} else {
Vec::new()
};
for pattern in &clause.patterns {
if row_set.is_empty() {
break;
}
let mut expanded: Vec<ResultRow> = Vec::new();
let mut expanded_sets: Vec<Vec<EdgeIndex>> = Vec::new();
for (ci, cur) in row_set.iter().enumerate() {
let resolved;
let pat = if Self::pattern_has_vars(pattern) {
resolved = self.resolve_pattern_vars(pattern, cur);
&resolved
} else {
pattern
};
let seeded = seed_prebound_pattern_vars(pat, cur);
let pre_bindings = seeded.as_ref().unwrap_or(&cur.node_bindings);
let executor = PatternExecutor::with_bindings_and_params(
self.graph,
self.budget_probe_limit(None),
pre_bindings,
self.params,
)
.set_deadline(self.deadline)
.set_cancel(self.cancel);
let matches = executor.execute(pat)?;
self.budget
.check_work(matches.len(), "OPTIONAL MATCH expansion")?;
for m in &matches {
if !self.bindings_compatible(cur, m) {
continue;
}
if enforce_rel_uniqueness {
let mut m_edges = Vec::new();
match_edge_indices(m, &mut m_edges);
if m_edges.iter().any(|e| edge_sets[ci].contains(e)) {
continue;
}
let mut next_set = edge_sets[ci].clone();
next_set.extend(m_edges);
expanded_sets.push(next_set);
}
self.budget.reserve_rows(
budget_rows_base + expanded.len(),
1,
"OPTIONAL MATCH",
)?;
let mut new_row = cur.clone();
self.merge_match_into_row(&mut new_row, m);
expanded.push(new_row);
}
}
row_set = expanded;
if enforce_rel_uniqueness {
edge_sets = expanded_sets;
}
}
Ok(row_set)
}
pub(super) fn merge_match_into_row(&self, row: &mut ResultRow, m: &PatternMatch) {
if let Some((source, path)) = m.exact_path.as_deref() {
row.path_bindings.insert(
"__fixed_path".to_string(),
PathBinding {
source: *source,
hops: path.len(),
path: path.clone(),
},
);
}
for (var, binding) in &m.bindings {
match binding {
MatchBinding::Node { index, .. } | MatchBinding::NodeRef(index) => {
row.node_bindings.insert(var.clone(), *index);
}
MatchBinding::Edge {
source,
target,
edge_index,
..
} => {
row.edge_bindings.insert(
var.clone(),
EdgeBinding {
source: *source,
target: *target,
edge_index: *edge_index,
},
);
}
MatchBinding::VariableLengthPath {
source, hops, path, ..
} => {
row.path_bindings.insert(
var.clone(),
PathBinding {
source: *source,
hops: *hops,
path: path.clone(),
},
);
}
}
}
}
pub(super) fn synthesize_path_from_pattern(
&self,
pattern: &crate::graph::core::pattern_matching::Pattern,
row: &ResultRow,
) -> Option<PathBinding> {
let mut node_vars: Vec<&str> = Vec::new();
let mut edge_vars: Vec<Option<&str>> = Vec::new();
let mut edge_element_indices: Vec<usize> = Vec::new();
for (element_index, elem) in pattern.elements.iter().enumerate() {
match elem {
PatternElement::Node(np) => {
if let Some(ref v) = np.variable {
node_vars.push(v);
}
}
PatternElement::Edge(ep) => {
edge_vars.push(ep.variable.as_deref());
edge_element_indices.push(element_index);
}
}
}
if node_vars.len() < 2 || edge_vars.is_empty() {
return None;
}
let source_idx = row.node_bindings.get(node_vars[0])?;
let mut path = Vec::with_capacity(edge_vars.len());
for (i, edge_var) in edge_vars.iter().enumerate() {
let node_idx = row.node_bindings.get(node_vars[i + 1])?;
let binding_name = edge_var
.map(str::to_string)
.unwrap_or_else(|| format!("__anon_edge_{}", edge_element_indices[i] + 1));
let edge = row.edge_bindings.get(&binding_name)?;
path.push(crate::graph::core::pattern_matching::PathHop {
node: *node_idx,
edge: edge.edge_index,
connection_type: self
.graph
.graph
.edge_weight(edge.edge_index)?
.connection_type,
});
}
Some(PathBinding {
source: *source_idx,
hops: edge_vars.len(),
path,
})
}
pub(super) fn execute_optional_match(
&self,
clause: &MatchClause,
existing: ResultSet,
) -> Result<ResultSet, String> {
if existing.rows.is_empty() {
let columns = existing.columns.clone();
let result = self.execute_match(clause, existing, None)?;
if !result.rows.is_empty() {
return Ok(result);
}
let mut null_row = ResultRow::new();
for pattern in &clause.patterns {
for elem in &pattern.elements {
match elem {
PatternElement::Node(np) => {
if let Some(ref var) = np.variable {
null_row.projected.insert(var.clone(), Value::Null);
}
}
PatternElement::Edge(ep) => {
if let Some(ref var) = ep.variable {
null_row.projected.insert(var.clone(), Value::Null);
}
}
}
}
}
return Ok(ResultSet {
rows: vec![null_row],
columns,
lazy_return_items: None,
});
}
let mut new_rows = Vec::with_capacity(existing.rows.len());
for row in &existing.rows {
let expanded = self.expand_optional_match_row(clause, row, new_rows.len())?;
if expanded.is_empty() {
self.budget
.reserve_rows(new_rows.len(), 1, "OPTIONAL MATCH")?;
let mut keep = row.clone();
null_pad_pattern_vars(&mut keep, clause);
new_rows.push(keep);
} else {
new_rows.extend(expanded);
}
}
Ok(ResultSet {
rows: new_rows,
columns: existing.columns,
lazy_return_items: None,
})
}
pub(super) fn try_fast_exists_check(
&self,
patterns: &[Pattern],
where_clause: &Option<Box<Predicate>>,
row: &ResultRow,
) -> Option<Result<bool, String>> {
if patterns.len() != 1 {
return None;
}
let pattern = &patterns[0];
if pattern.elements.len() != 3 {
return None;
}
let node_a = match &pattern.elements[0] {
PatternElement::Node(np) => np,
_ => return None,
};
let edge = match &pattern.elements[1] {
PatternElement::Edge(ep) => ep,
_ => return None,
};
let node_b = match &pattern.elements[2] {
PatternElement::Node(np) => np,
_ => return None,
};
if edge.var_length.is_some() || edge.properties.is_some() {
return None;
}
if let Some(var) = edge.variable.as_deref() {
if row_bound_edge(row, var).is_some() {
return None;
}
}
for np in [node_a, node_b] {
if let Some(var) = np.variable.as_deref() {
if !row.node_bindings.contains_key(var) && row.projected.contains_key(var) {
return None;
}
}
}
let a_bound = node_a
.variable
.as_ref()
.and_then(|v| row.node_bindings.get(v).copied());
let b_bound = node_b
.variable
.as_ref()
.and_then(|v| row.node_bindings.get(v).copied());
let (bound_idx, other_node, other_var, direction) = match (a_bound, b_bound) {
(Some(idx), None) => {
let dir = match edge.direction {
EdgeDirection::Outgoing => Direction::Outgoing,
EdgeDirection::Incoming => Direction::Incoming,
EdgeDirection::Both => return None,
};
(idx, node_b, &node_b.variable, dir)
}
(None, Some(idx)) => {
let dir = match edge.direction {
EdgeDirection::Outgoing => Direction::Incoming,
EdgeDirection::Incoming => Direction::Outgoing,
EdgeDirection::Both => return None,
};
(idx, node_a, &node_a.variable, dir)
}
_ => return None, };
let interned_conn = edge.connection_type.as_deref().map(InternedKey::from_str);
let (has_where, mut eval_row) = if where_clause.is_some() {
let mut r = row.clone(); if let Some(ref var) = other_var {
r.node_bindings.insert(var.clone(), NodeIndex::new(0)); }
(true, r)
} else {
(false, ResultRow::new()) };
for edge_ref in
self.graph
.graph
.edges_directed_filtered(bound_idx, direction, interned_conn)
{
if let Some(ik) = interned_conn {
if edge_ref.weight().connection_type != ik {
continue;
}
}
let other_idx = if direction == Direction::Outgoing {
edge_ref.target()
} else {
edge_ref.source()
};
if let Some(ref req_type) = other_node.node_type {
if let Some(nt) = self.graph.graph.node_type_of(other_idx) {
if self.graph.interner.resolve(nt) != req_type {
continue;
}
} else {
continue;
}
}
if let Some(ref props) = other_node.properties {
if let Some(nd) = self.graph.graph.node_weight(other_idx) {
let mut all_match = true;
let tgt_type_str = nd.node_type_str(&self.graph.interner);
for (key, matcher) in props {
let resolved = self.graph.resolve_alias(tgt_type_str, key);
let val: Option<std::borrow::Cow<'_, Value>> = if resolved == "id" {
Some(nd.id())
} else if resolved == "title" {
Some(nd.title())
} else if let Some(v) = nd.get_property(resolved) {
Some(v)
} else {
match crate::graph::schema::soft_alias_fallback(resolved) {
Some(crate::graph::schema::SoftAliasFallback::Title) => {
Some(nd.title())
}
Some(crate::graph::schema::SoftAliasFallback::TypeString) => {
Some(std::borrow::Cow::Owned(Value::String(
tgt_type_str.to_string(),
)))
}
None => None,
}
};
let ok = match matcher {
PropertyMatcher::Equals(expected) => val.as_deref().is_some_and(|v| {
crate::graph::core::filtering::values_equal(v, expected)
}),
PropertyMatcher::In(values) => val.as_deref().is_some_and(|v| {
values
.iter()
.any(|exp| crate::graph::core::filtering::values_equal(v, exp))
}),
_ => return None,
};
if !ok {
all_match = false;
break;
}
}
if !all_match {
continue;
}
} else {
continue;
}
}
if has_where {
if let Some(ref var) = other_var {
eval_row.node_bindings.insert(var.clone(), other_idx);
}
match self.evaluate_predicate(
where_clause
.as_ref()
.expect("invariant: has_where guards Some(where_clause)"),
&eval_row,
) {
Ok(true) => {}
Ok(false) => continue,
Err(e) => return Some(Err(e)),
}
}
return Some(Ok(true)); }
Some(Ok(false)) }
pub(super) fn try_count_simple_pattern(
&self,
pattern: &crate::graph::core::pattern_matching::Pattern,
bindings: &Bindings<NodeIndex>,
) -> Result<Option<i64>, String> {
self.count_simple_pattern_from_bound(pattern, bindings, false)
}
pub(super) fn try_count_distinct_peers(
&self,
pattern: &crate::graph::core::pattern_matching::Pattern,
bindings: &Bindings<NodeIndex>,
) -> Result<Option<i64>, String> {
self.count_simple_pattern_from_bound(pattern, bindings, true)
}
fn count_simple_pattern_from_bound(
&self,
pattern: &crate::graph::core::pattern_matching::Pattern,
bindings: &Bindings<NodeIndex>,
distinct_peers: bool,
) -> Result<Option<i64>, String> {
if pattern.elements.len() != 3 {
return Ok(None);
}
let node_a = match &pattern.elements[0] {
PatternElement::Node(np) => np,
_ => return Ok(None),
};
let edge = match &pattern.elements[1] {
PatternElement::Edge(ep) => ep,
_ => return Ok(None),
};
let node_b = match &pattern.elements[2] {
PatternElement::Node(np) => np,
_ => return Ok(None),
};
if edge.var_length.is_some() || edge.properties.is_some() {
return Ok(None);
}
let a_bound = node_a
.variable
.as_ref()
.and_then(|v| bindings.get(v).copied());
let b_bound = node_b
.variable
.as_ref()
.and_then(|v| bindings.get(v).copied());
type CountFastPath<'p> = (
NodeIndex,
&'p Option<String>,
&'p Option<HashMap<String, PropertyMatcher>>,
&'p [Direction],
);
let (bound_idx, other_type, other_props, traverse_dirs): CountFastPath =
match (a_bound, b_bound) {
(None, Some(b_idx)) => {
let dirs: &[Direction] = match edge.direction {
EdgeDirection::Outgoing => &[Direction::Incoming], EdgeDirection::Incoming => &[Direction::Outgoing], EdgeDirection::Both => &[Direction::Outgoing, Direction::Incoming],
};
(b_idx, &node_a.node_type, &node_a.properties, dirs)
}
(Some(a_idx), None) => {
let dirs: &[Direction] = match edge.direction {
EdgeDirection::Outgoing => &[Direction::Outgoing],
EdgeDirection::Incoming => &[Direction::Incoming],
EdgeDirection::Both => &[Direction::Outgoing, Direction::Incoming],
};
(a_idx, &node_b.node_type, &node_b.properties, dirs)
}
_ => return Ok(None), };
let conn_type = edge.connection_type.as_deref();
let interned_conn = conn_type.map(InternedKey::from_str);
let interned_other_type = other_type.as_ref().map(|t| InternedKey::from_str(t));
if !distinct_peers && other_props.is_none() && edge.edge_filter.is_none() {
let mut count: usize = 0;
for &dir in traverse_dirs {
count = count.saturating_add(self.graph.graph.count_edges_filtered(
bound_idx,
dir,
interned_conn,
interned_other_type,
self.deadline,
)?);
}
return Ok(Some(count as i64));
}
let pe = PatternExecutor::new_lightweight_with_params(self.graph, None, self.params);
let mut count: i64 = 0;
let mut peers: HashSet<NodeIndex> = HashSet::new();
let mut iter: usize = 0;
let edge_filter = edge.edge_filter.as_ref();
for &dir in traverse_dirs {
let peer_is_start = if let Some(f) = edge_filter {
use crate::graph::core::pattern_matching::pattern::AnchorSide;
match (f.anchor, dir) {
(AnchorSide::Source, Direction::Outgoing) => false,
(AnchorSide::Source, Direction::Incoming) => true,
(AnchorSide::Target, Direction::Outgoing) => true,
(AnchorSide::Target, Direction::Incoming) => false,
}
} else {
false
};
for edge_ref in self
.graph
.graph
.edges_directed_filtered(bound_idx, dir, interned_conn)
{
iter += 1;
if iter.is_multiple_of(1 << 20) {
self.check_deadline()?;
}
if let Some(required_conn) = interned_conn {
if edge_ref.weight().connection_type != required_conn {
continue;
}
}
let other_idx = if dir == Direction::Outgoing {
edge_ref.target()
} else {
edge_ref.source()
};
if distinct_peers && peers.contains(&other_idx) {
continue;
}
if let Some(required_type) = interned_other_type {
if let Some(nt) = self.graph.graph.node_type_of(other_idx) {
if nt != required_type {
continue;
}
} else {
continue;
}
}
if let Some(filter) = edge_filter {
let edge_data = edge_ref.weight();
let conn_ty = edge_data.connection_type;
let edge_source = edge_ref.source();
let edge_target = edge_ref.target();
if !filter.predicate.eval(
conn_ty,
peer_is_start,
edge_source,
edge_target,
&|prop: &str| edge_data.get_property(prop).cloned(),
) {
continue;
}
}
if let Some(ref props) = other_props {
if !pe.node_matches_properties_pub(other_idx, props) {
continue;
}
}
if distinct_peers {
peers.insert(other_idx);
} else {
count += 1;
}
}
}
Ok(Some(if distinct_peers {
peers.len() as i64
} else {
count
}))
}
pub(super) fn count_two_hop_pattern(
&self,
pattern: &crate::graph::core::pattern_matching::Pattern,
first_idx: NodeIndex,
) -> Result<i64, String> {
self.count_two_hop_from_anchor(pattern, first_idx, false)
}
pub(super) fn count_two_hop_pattern_reverse(
&self,
pattern: &crate::graph::core::pattern_matching::Pattern,
last_idx: NodeIndex,
) -> Result<i64, String> {
self.count_two_hop_from_anchor(pattern, last_idx, true)
}
fn count_two_hop_from_anchor(
&self,
pattern: &crate::graph::core::pattern_matching::Pattern,
anchor_idx: NodeIndex,
reverse: bool,
) -> Result<i64, String> {
use petgraph::Direction;
let (hop1_elem, hop2_elem, end_elem) = if reverse {
(
&pattern.elements[3],
&pattern.elements[1],
&pattern.elements[0],
)
} else {
(
&pattern.elements[1],
&pattern.elements[3],
&pattern.elements[4],
)
};
let hop1_edge = match hop1_elem {
PatternElement::Edge(ep) => ep,
_ => return Ok(0),
};
let mid_node = match &pattern.elements[2] {
PatternElement::Node(np) => np,
_ => return Ok(0),
};
let hop2_edge = match hop2_elem {
PatternElement::Edge(ep) => ep,
_ => return Ok(0),
};
let end_node = match end_elem {
PatternElement::Node(np) => np,
_ => return Ok(0),
};
let map_dir = |d: EdgeDirection| -> Option<Direction> {
match d {
EdgeDirection::Outgoing if reverse => Some(Direction::Incoming),
EdgeDirection::Outgoing => Some(Direction::Outgoing),
EdgeDirection::Incoming if reverse => Some(Direction::Outgoing),
EdgeDirection::Incoming => Some(Direction::Incoming),
EdgeDirection::Both => None,
}
};
let Some(dir1) = map_dir(hop1_edge.direction) else {
return Ok(0);
};
let Some(dir2) = map_dir(hop2_edge.direction) else {
return Ok(0);
};
let interned_conn1 = hop1_edge
.connection_type
.as_deref()
.map(InternedKey::from_str);
let interned_conn2 = hop2_edge
.connection_type
.as_deref()
.map(InternedKey::from_str);
let node_type_matches = |idx: NodeIndex, want: &Option<String>| -> bool {
match want {
None => true,
Some(want_ty) => self
.graph
.graph
.node_type_of(idx)
.is_some_and(|nt| self.graph.interner.resolve(nt) == *want_ty),
}
};
let mut total: i64 = 0;
let mut work = 0usize;
for e1_ref in self
.graph
.graph
.edges_directed_filtered(anchor_idx, dir1, interned_conn1)
{
self.check_interrupt_periodic(work)?;
work = work.saturating_add(1);
if let Some(ik) = interned_conn1 {
if e1_ref.weight().connection_type != ik {
continue;
}
}
let mid_idx = if dir1 == Direction::Outgoing {
e1_ref.target()
} else {
e1_ref.source()
};
if !node_type_matches(mid_idx, &mid_node.node_type) {
continue;
}
for e2_ref in self
.graph
.graph
.edges_directed_filtered(mid_idx, dir2, interned_conn2)
{
self.check_interrupt_periodic(work)?;
work = work.saturating_add(1);
if let Some(ik) = interned_conn2 {
if e2_ref.weight().connection_type != ik {
continue;
}
}
if e2_ref.id() == e1_ref.id() {
continue;
}
let end_idx = if dir2 == Direction::Outgoing {
e2_ref.target()
} else {
e2_ref.source()
};
if !node_type_matches(end_idx, &end_node.node_type) {
continue;
}
total += 1;
}
}
Ok(total)
}
}
include!("match_clause/fused_match.rs");