use super::Predicate;
use hawdb_core::{RelationshipDirection, Value};
use std::collections::{BTreeMap, BTreeSet};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct GraphMatchProgram {
pub imports: Vec<GraphBindingImport>,
pub introduced: Vec<String>,
pub projected_nodes: BTreeMap<String, BTreeSet<String>>,
pub projected_relationships: BTreeMap<String, BTreeSet<String>>,
pub steps: Vec<GraphMatchStep>,
pub predicate: Option<Predicate>,
pub predicate_steps: Vec<usize>,
pub edge_predicates: Vec<usize>,
pub optional: bool,
pub hash_join: Option<GraphMatchHashJoin>,
pub id_set_conjuncts: Vec<usize>,
pub seeded_union: Vec<GraphMatchProgram>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct GraphMatchHashJoin {
pub key: String,
pub build_conjuncts: Vec<usize>,
pub residual_order: Vec<usize>,
pub residual_placed: usize,
pub probe_conjuncts: usize,
}
pub fn membership_conjunct(predicate: &Predicate) -> Option<(&str, &str)> {
let Predicate::ExpressionEq {
expression:
crate::ProjectionExpression::Binary {
left,
op: crate::ScalarBinaryOp::In,
right,
},
value: crate::ProjectionExpression::Literal(Value::Bool(true)),
} = predicate
else {
return None;
};
match (left.as_ref(), right.as_ref()) {
(
crate::ProjectionExpression::Variable { variable },
crate::ProjectionExpression::Column(column),
) => Some((variable, column)),
_ => None,
}
}
pub fn predicate_cannot_fail(predicate: &Predicate) -> bool {
match predicate {
Predicate::And(children) | Predicate::Or(children) => {
children.iter().all(predicate_cannot_fail)
}
Predicate::Not(child) => predicate_cannot_fail(child),
Predicate::ConstantBool(_)
| Predicate::IdEq { .. }
| Predicate::IdNotEq { .. }
| Predicate::IdIn { .. }
| Predicate::PropertyEq { .. }
| Predicate::PropertyNotEq { .. }
| Predicate::PropertyCompare { .. }
| Predicate::PropertyIn { .. }
| Predicate::PropertyIsNull { .. }
| Predicate::PropertyIsNotNull { .. } => true,
_ => false,
}
}
impl GraphMatchProgram {
pub fn predicate_conjuncts(&self) -> &[Predicate] {
match &self.predicate {
None => &[],
Some(Predicate::And(conjuncts)) => conjuncts,
Some(predicate) => std::slice::from_ref(predicate),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct GraphBindingImport {
pub variable: String,
pub column: String,
pub kind: GraphEntityKind,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum GraphEntityKind {
Node,
Relationship,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct GraphMatchNode {
pub variable: String,
pub label: String,
pub properties: BTreeMap<String, Value>,
}
#[derive(Clone, PartialEq, Eq)]
pub enum GraphMatchStep {
Node(GraphMatchNode),
Expand {
source: String,
relationship: Option<String>,
rel_type: String,
rel_types: Vec<String>,
properties: BTreeMap<String, Value>,
direction: RelationshipDirection,
min_hops: usize,
max_hops: usize,
target: GraphMatchNode,
},
ShortestPath(Box<GraphMatchShortestPath>),
}
impl GraphMatchStep {
pub fn rel_type_disjunction(&self) -> Option<&[String]> {
match self {
Self::Expand { rel_types, .. } if !rel_types.is_empty() => Some(rel_types),
_ => None,
}
}
}
impl GraphMatchProgram {
pub fn has_rel_type_disjunction(&self) -> bool {
self.steps
.iter()
.any(|step| step.rel_type_disjunction().is_some())
}
}
pub fn expand_rel_types<'a>(rel_type: &'a str, rel_types: &'a [String]) -> Vec<&'a str> {
if rel_types.is_empty() {
if rel_type.is_empty() {
Vec::new()
} else {
vec![rel_type]
}
} else {
rel_types.iter().map(String::as_str).collect()
}
}
impl std::fmt::Debug for GraphMatchStep {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Node(node) => f.debug_tuple("Node").field(node).finish(),
Self::Expand {
source,
relationship,
rel_type,
rel_types,
properties,
direction,
min_hops,
max_hops,
target,
} => {
let mut debug = f.debug_struct("Expand");
debug
.field("source", source)
.field("relationship", relationship)
.field("rel_type", rel_type);
if !rel_types.is_empty() {
debug.field("rel_types", &format_args!("{}", rel_types.join("|")));
}
debug
.field("properties", properties)
.field("direction", direction)
.field("min_hops", min_hops)
.field("max_hops", max_hops)
.field("target", target)
.finish()
}
Self::ShortestPath(path) => f.debug_tuple("ShortestPath").field(path).finish(),
}
}
}
pub const MAX_SHORTEST_PATH_HOPS: usize = 256;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ShortestPathSelection {
Any,
All,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct GraphMatchShortestPath {
pub path: String,
pub source: String,
pub target: String,
pub rel_type: String,
pub direction: RelationshipDirection,
pub min_hops: usize,
pub max_hops: usize,
pub bounded: bool,
pub selection: ShortestPathSelection,
}
hawdb_core::impl_retained_heap_for_struct!(GraphMatchProgram {
imports,
introduced,
projected_nodes,
projected_relationships,
steps,
predicate,
predicate_steps,
edge_predicates,
optional,
hash_join,
id_set_conjuncts,
seeded_union
});
hawdb_core::impl_retained_heap_for_struct!(GraphMatchHashJoin {
key,
build_conjuncts,
residual_order,
residual_placed,
probe_conjuncts
});
hawdb_core::impl_retained_heap_for_struct!(GraphMatchNode {
variable,
label,
properties
});
hawdb_core::impl_retained_heap_for_struct!(GraphBindingImport {
variable,
column,
kind
});
hawdb_core::impl_retained_heap_for_copy!(GraphEntityKind);
impl hawdb_core::RetainedHeapBytes for GraphMatchStep {
fn retained_heap_bytes(&self) -> Option<usize> {
match self {
Self::Node(value0) => hawdb_core::checked_retained_heap_sum([
hawdb_core::RetainedHeapBytes::retained_heap_bytes(value0),
]),
Self::ShortestPath(path) => hawdb_core::RetainedHeapBytes::retained_heap_bytes(path),
Self::Expand {
source,
relationship,
rel_type,
rel_types,
properties,
direction,
min_hops,
max_hops,
target,
} => hawdb_core::checked_retained_heap_sum([
hawdb_core::RetainedHeapBytes::retained_heap_bytes(source),
hawdb_core::RetainedHeapBytes::retained_heap_bytes(relationship),
hawdb_core::RetainedHeapBytes::retained_heap_bytes(rel_type),
hawdb_core::RetainedHeapBytes::retained_heap_bytes(rel_types),
hawdb_core::RetainedHeapBytes::retained_heap_bytes(properties),
hawdb_core::RetainedHeapBytes::retained_heap_bytes(direction),
hawdb_core::RetainedHeapBytes::retained_heap_bytes(min_hops),
hawdb_core::RetainedHeapBytes::retained_heap_bytes(max_hops),
hawdb_core::RetainedHeapBytes::retained_heap_bytes(target),
]),
}
}
}
hawdb_core::impl_retained_heap_for_struct!(GraphMatchShortestPath {
path,
source,
target,
rel_type,
direction,
min_hops,
max_hops,
bounded,
selection
});
hawdb_core::impl_retained_heap_for_copy!(ShortestPathSelection);