use super::expr::{Expr, Identifier};
use std::fmt;
#[derive(Debug, Clone, PartialEq)]
pub struct GraphPattern {
pub paths: Vec<PathPattern>,
}
impl GraphPattern {
#[must_use]
pub fn single(path: PathPattern) -> Self {
Self { paths: vec![path] }
}
#[must_use]
pub const fn new(paths: Vec<PathPattern>) -> Self {
Self { paths }
}
}
impl fmt::Display for GraphPattern {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for (i, path) in self.paths.iter().enumerate() {
if i > 0 {
write!(f, ", ")?;
}
write!(f, "{path}")?;
}
Ok(())
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct PathPattern {
pub start: NodePattern,
pub steps: Vec<(EdgePattern, NodePattern)>,
}
impl PathPattern {
#[must_use]
pub const fn node(node: NodePattern) -> Self {
Self { start: node, steps: vec![] }
}
#[must_use]
pub fn chain(start: NodePattern, edge: EdgePattern, end: NodePattern) -> Self {
Self { start, steps: vec![(edge, end)] }
}
#[must_use]
pub fn then(mut self, edge: EdgePattern, node: NodePattern) -> Self {
self.steps.push((edge, node));
self
}
}
impl fmt::Display for PathPattern {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.start)?;
for (edge, node) in &self.steps {
write!(f, "{edge}{node}")?;
}
Ok(())
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct NodePattern {
pub variable: Option<Identifier>,
pub labels: Vec<Identifier>,
pub properties: Vec<PropertyCondition>,
}
impl NodePattern {
#[must_use]
pub const fn anonymous() -> Self {
Self { variable: None, labels: vec![], properties: vec![] }
}
#[must_use]
pub fn var(name: impl Into<Identifier>) -> Self {
Self { variable: Some(name.into()), labels: vec![], properties: vec![] }
}
#[must_use]
pub fn with_label(name: impl Into<Identifier>, label: impl Into<Identifier>) -> Self {
Self { variable: Some(name.into()), labels: vec![label.into()], properties: vec![] }
}
#[must_use]
pub fn label(mut self, label: impl Into<Identifier>) -> Self {
self.labels.push(label.into());
self
}
#[must_use]
pub fn property(mut self, name: impl Into<Identifier>, value: Expr) -> Self {
self.properties.push(PropertyCondition { name: name.into(), value });
self
}
}
impl fmt::Display for NodePattern {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "(")?;
if let Some(var) = &self.variable {
write!(f, "{var}")?;
}
for label in &self.labels {
write!(f, ":{label}")?;
}
if !self.properties.is_empty() {
write!(f, " {{")?;
for (i, prop) in self.properties.iter().enumerate() {
if i > 0 {
write!(f, ", ")?;
}
write!(f, "{prop}")?;
}
write!(f, "}}")?;
}
write!(f, ")")
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct EdgePattern {
pub direction: EdgeDirection,
pub variable: Option<Identifier>,
pub edge_types: Vec<Identifier>,
pub properties: Vec<PropertyCondition>,
pub length: EdgeLength,
}
impl EdgePattern {
#[must_use]
pub const fn directed() -> Self {
Self {
direction: EdgeDirection::Right,
variable: None,
edge_types: vec![],
properties: vec![],
length: EdgeLength::Single,
}
}
#[must_use]
pub const fn undirected() -> Self {
Self {
direction: EdgeDirection::Undirected,
variable: None,
edge_types: vec![],
properties: vec![],
length: EdgeLength::Single,
}
}
#[must_use]
pub const fn left() -> Self {
Self {
direction: EdgeDirection::Left,
variable: None,
edge_types: vec![],
properties: vec![],
length: EdgeLength::Single,
}
}
#[must_use]
pub fn var(mut self, name: impl Into<Identifier>) -> Self {
self.variable = Some(name.into());
self
}
#[must_use]
pub fn edge_type(mut self, edge_type: impl Into<Identifier>) -> Self {
self.edge_types.push(edge_type.into());
self
}
#[must_use]
pub const fn length(mut self, length: EdgeLength) -> Self {
self.length = length;
self
}
#[must_use]
pub fn property(mut self, name: impl Into<Identifier>, value: Expr) -> Self {
self.properties.push(PropertyCondition { name: name.into(), value });
self
}
}
impl fmt::Display for EdgePattern {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.direction {
EdgeDirection::Left => write!(f, "<-[")?,
EdgeDirection::Right | EdgeDirection::Undirected => write!(f, "-[")?,
}
if let Some(var) = &self.variable {
write!(f, "{var}")?;
}
for (i, edge_type) in self.edge_types.iter().enumerate() {
if i == 0 {
write!(f, ":")?;
} else {
write!(f, "|")?;
}
write!(f, "{edge_type}")?;
}
match &self.length {
EdgeLength::Single => {}
EdgeLength::Range { min, max } => {
write!(f, "*")?;
if let Some(min) = min {
write!(f, "{min}")?;
}
write!(f, "..")?;
if let Some(max) = max {
write!(f, "{max}")?;
}
}
EdgeLength::Exact(n) => write!(f, "*{n}")?,
EdgeLength::Any => write!(f, "*")?,
}
if !self.properties.is_empty() {
write!(f, " {{")?;
for (i, prop) in self.properties.iter().enumerate() {
if i > 0 {
write!(f, ", ")?;
}
write!(f, "{prop}")?;
}
write!(f, "}}")?;
}
match self.direction {
EdgeDirection::Right => write!(f, "]->")?,
EdgeDirection::Left | EdgeDirection::Undirected => write!(f, "]-")?,
}
Ok(())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum EdgeDirection {
Left,
Right,
Undirected,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum EdgeLength {
Single,
Range {
min: Option<u32>,
max: Option<u32>,
},
Exact(u32),
Any,
}
impl EdgeLength {
#[must_use]
pub const fn range(min: Option<u32>, max: Option<u32>) -> Self {
Self::Range { min, max }
}
#[must_use]
pub const fn at_least(min: u32) -> Self {
Self::Range { min: Some(min), max: None }
}
#[must_use]
pub const fn at_most(max: u32) -> Self {
Self::Range { min: None, max: Some(max) }
}
#[must_use]
pub const fn between(min: u32, max: u32) -> Self {
Self::Range { min: Some(min), max: Some(max) }
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct PropertyCondition {
pub name: Identifier,
pub value: Expr,
}
impl fmt::Display for PropertyCondition {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}: ...", self.name)
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct ShortestPathPattern {
pub path: PathPattern,
pub find_all: bool,
pub weight: Option<WeightSpec>,
}
impl ShortestPathPattern {
#[must_use]
pub fn new(path: PathPattern) -> Self {
Self { path, find_all: false, weight: None }
}
#[must_use]
pub fn all(path: PathPattern) -> Self {
Self { path, find_all: true, weight: None }
}
#[must_use]
pub fn weighted_by(mut self, weight: WeightSpec) -> Self {
self.weight = Some(weight);
self
}
#[must_use]
pub fn weighted_by_property(self, property: impl Into<String>) -> Self {
self.weighted_by(WeightSpec::property(property))
}
#[must_use]
pub const fn is_weighted(&self) -> bool {
self.weight.is_some()
}
}
impl fmt::Display for ShortestPathPattern {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.find_all {
write!(f, "ALL SHORTEST PATHS ")?;
} else {
write!(f, "SHORTEST PATH ")?;
}
write!(f, "{}", self.path)?;
if let Some(ref weight) = self.weight {
write!(f, " {weight}")?;
}
Ok(())
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum WeightSpec {
Property {
name: String,
default: Option<f64>,
},
Constant(f64),
Expression(Expr),
}
impl WeightSpec {
#[must_use]
pub fn property(name: impl Into<String>) -> Self {
Self::Property { name: name.into(), default: None }
}
#[must_use]
pub fn property_with_default(name: impl Into<String>, default: f64) -> Self {
Self::Property { name: name.into(), default: Some(default) }
}
#[must_use]
pub const fn constant(value: f64) -> Self {
Self::Constant(value)
}
#[must_use]
pub const fn expression(expr: Expr) -> Self {
Self::Expression(expr)
}
}
impl fmt::Display for WeightSpec {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "WEIGHTED BY ")?;
match self {
Self::Property { name, default } => {
write!(f, "{name}")?;
if let Some(d) = default {
write!(f, " DEFAULT {d}")?;
}
}
Self::Constant(v) => write!(f, "{v}")?,
Self::Expression(_) => write!(f, "<expr>")?,
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn node_pattern_display() {
let node = NodePattern::anonymous();
assert_eq!(node.to_string(), "()");
let node = NodePattern::var("p");
assert_eq!(node.to_string(), "(p)");
let node = NodePattern::with_label("p", "Person");
assert_eq!(node.to_string(), "(p:Person)");
let node = NodePattern::with_label("p", "Person").label("Employee");
assert_eq!(node.to_string(), "(p:Person:Employee)");
}
#[test]
fn edge_pattern_display() {
let edge = EdgePattern::directed();
assert_eq!(edge.to_string(), "-[]->");
let edge = EdgePattern::left();
assert_eq!(edge.to_string(), "<-[]-");
let edge = EdgePattern::undirected();
assert_eq!(edge.to_string(), "-[]-");
let edge = EdgePattern::directed().edge_type("FOLLOWS");
assert_eq!(edge.to_string(), "-[:FOLLOWS]->");
let edge =
EdgePattern::directed().var("r").edge_type("FOLLOWS").length(EdgeLength::between(1, 3));
assert_eq!(edge.to_string(), "-[r:FOLLOWS*1..3]->");
}
#[test]
fn path_pattern_display() {
let path = PathPattern::chain(
NodePattern::with_label("a", "Person"),
EdgePattern::directed().edge_type("FOLLOWS"),
NodePattern::with_label("b", "Person"),
);
assert_eq!(path.to_string(), "(a:Person)-[:FOLLOWS]->(b:Person)");
}
#[test]
fn path_pattern_chaining() {
let path = PathPattern::chain(
NodePattern::var("a"),
EdgePattern::directed().edge_type("KNOWS"),
NodePattern::var("b"),
)
.then(EdgePattern::directed().edge_type("LIKES"), NodePattern::var("c"));
assert_eq!(path.steps.len(), 2);
assert_eq!(path.to_string(), "(a)-[:KNOWS]->(b)-[:LIKES]->(c)");
}
#[test]
fn edge_length_variants() {
assert_eq!(EdgeLength::Single, EdgeLength::Single);
assert_eq!(EdgeLength::Any, EdgeLength::Any);
assert_eq!(EdgeLength::Exact(3), EdgeLength::Exact(3));
assert_eq!(EdgeLength::at_least(2), EdgeLength::Range { min: Some(2), max: None });
}
#[test]
fn shortest_path_pattern_display() {
let path = PathPattern::chain(
NodePattern::var("a"),
EdgePattern::directed().length(EdgeLength::Any),
NodePattern::var("b"),
);
let sp = ShortestPathPattern::new(path.clone());
assert_eq!(sp.to_string(), "SHORTEST PATH (a)-[*]->(b)");
assert!(!sp.is_weighted());
let sp = ShortestPathPattern::all(path.clone());
assert_eq!(sp.to_string(), "ALL SHORTEST PATHS (a)-[*]->(b)");
let sp = ShortestPathPattern::new(path).weighted_by_property("cost");
assert!(sp.is_weighted());
assert!(sp.to_string().contains("WEIGHTED BY cost"));
}
#[test]
fn weight_spec_variants() {
let ws = WeightSpec::property("cost");
assert_eq!(ws.to_string(), "WEIGHTED BY cost");
let ws = WeightSpec::property_with_default("cost", 1.0);
assert_eq!(ws.to_string(), "WEIGHTED BY cost DEFAULT 1");
let ws = WeightSpec::constant(2.5);
assert_eq!(ws.to_string(), "WEIGHTED BY 2.5");
}
}