Skip to main content

uqa_graph/
pattern.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7//! Declarative graph patterns (Definition 5.2.1, Paper 2):
8//! `P = (V_P, E_P, C_V, C_E)`.
9//!
10//! Constraints are encoded as `VertexPredicate` / `EdgePredicate`
11//! enums rather than open closures so patterns are serializable and
12//! introspectable by the planner. A `Custom` arm exists for callers
13//! that need an escape hatch.
14
15use std::sync::Arc;
16
17use uqa_core::{Edge, Value, Vertex};
18
19/// Predicate over a vertex.
20#[derive(Clone)]
21pub enum VertexPredicate {
22    /// `vertex.label == label`.
23    LabelEq(String),
24    /// `vertex.properties[key] == value`.
25    PropertyEq { key: String, value: Value },
26    /// Property is present (any value).
27    PropertyExists(String),
28    /// Conjunction of nested predicates (n-ary AND).
29    All(Vec<VertexPredicate>),
30    /// User-supplied predicate. Carried by `Arc` so the pattern stays
31    /// `Clone`; the closure is shared by reference across clones.
32    Custom(Arc<dyn Fn(&Vertex) -> bool + Send + Sync>),
33}
34
35impl VertexPredicate {
36    pub fn matches(&self, vertex: &Vertex) -> bool {
37        match self {
38            VertexPredicate::LabelEq(l) => vertex.label == *l,
39            VertexPredicate::PropertyEq { key, value } => vertex.properties.get(key) == Some(value),
40            VertexPredicate::PropertyExists(key) => vertex.properties.contains_key(key),
41            VertexPredicate::All(preds) => preds.iter().all(|p| p.matches(vertex)),
42            VertexPredicate::Custom(f) => f(vertex),
43        }
44    }
45}
46
47impl std::fmt::Debug for VertexPredicate {
48    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
49        match self {
50            VertexPredicate::LabelEq(l) => write!(f, "LabelEq({l:?})"),
51            VertexPredicate::PropertyEq { key, value } => {
52                write!(f, "PropertyEq({key:?} = {value:?})")
53            }
54            VertexPredicate::PropertyExists(k) => write!(f, "PropertyExists({k:?})"),
55            VertexPredicate::All(ps) => f.debug_tuple("All").field(ps).finish(),
56            VertexPredicate::Custom(_) => write!(f, "Custom(<fn>)"),
57        }
58    }
59}
60
61/// Predicate over an edge.
62#[derive(Clone)]
63pub enum EdgePredicate {
64    /// `edge.properties[key] == value`.
65    PropertyEq {
66        key: String,
67        value: Value,
68    },
69    PropertyExists(String),
70    All(Vec<EdgePredicate>),
71    Custom(Arc<dyn Fn(&Edge) -> bool + Send + Sync>),
72}
73
74impl EdgePredicate {
75    pub fn matches(&self, edge: &Edge) -> bool {
76        match self {
77            EdgePredicate::PropertyEq { key, value } => edge.properties.get(key) == Some(value),
78            EdgePredicate::PropertyExists(key) => edge.properties.contains_key(key),
79            EdgePredicate::All(preds) => preds.iter().all(|p| p.matches(edge)),
80            EdgePredicate::Custom(f) => f(edge),
81        }
82    }
83}
84
85impl std::fmt::Debug for EdgePredicate {
86    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
87        match self {
88            EdgePredicate::PropertyEq { key, value } => {
89                write!(f, "PropertyEq({key:?} = {value:?})")
90            }
91            EdgePredicate::PropertyExists(k) => write!(f, "PropertyExists({k:?})"),
92            EdgePredicate::All(ps) => f.debug_tuple("All").field(ps).finish(),
93            EdgePredicate::Custom(_) => write!(f, "Custom(<fn>)"),
94        }
95    }
96}
97
98/// `(V_P)` — a vertex variable in a pattern, with optional constraints.
99#[derive(Debug, Clone)]
100pub struct VertexPattern {
101    pub variable: String,
102    pub constraints: Vec<VertexPredicate>,
103}
104
105impl VertexPattern {
106    pub fn new(variable: impl Into<String>) -> Self {
107        Self {
108            variable: variable.into(),
109            constraints: Vec::new(),
110        }
111    }
112
113    pub fn with(mut self, predicate: VertexPredicate) -> Self {
114        self.constraints.push(predicate);
115        self
116    }
117
118    pub fn satisfies(&self, vertex: &Vertex) -> bool {
119        self.constraints.iter().all(|c| c.matches(vertex))
120    }
121}
122
123/// `(E_P)` — an edge between two vertex variables, with an optional
124/// label and per-property constraints. `negated == true` flips it into
125/// a "must not exist" pattern, matched after positive edges resolve.
126#[derive(Debug, Clone)]
127pub struct EdgePattern {
128    pub source_var: String,
129    pub target_var: String,
130    pub label: Option<String>,
131    pub constraints: Vec<EdgePredicate>,
132    pub negated: bool,
133}
134
135impl EdgePattern {
136    pub fn new(source: impl Into<String>, target: impl Into<String>) -> Self {
137        Self {
138            source_var: source.into(),
139            target_var: target.into(),
140            label: None,
141            constraints: Vec::new(),
142            negated: false,
143        }
144    }
145
146    pub fn with_label(mut self, label: impl Into<String>) -> Self {
147        self.label = Some(label.into());
148        self
149    }
150
151    pub fn with(mut self, predicate: EdgePredicate) -> Self {
152        self.constraints.push(predicate);
153        self
154    }
155
156    pub fn negated(mut self) -> Self {
157        self.negated = true;
158        self
159    }
160
161    pub fn satisfies(&self, edge: &Edge) -> bool {
162        if let Some(label) = &self.label {
163            if edge.label != *label {
164                return false;
165            }
166        }
167        self.constraints.iter().all(|c| c.matches(edge))
168    }
169}
170
171/// Subgraph pattern: `P = (V_P, E_P, C_V, C_E)`.
172#[derive(Debug, Clone, Default)]
173pub struct GraphPattern {
174    pub vertex_patterns: Vec<VertexPattern>,
175    pub edge_patterns: Vec<EdgePattern>,
176}
177
178impl GraphPattern {
179    pub fn new() -> Self {
180        Self::default()
181    }
182
183    pub fn add_vertex(mut self, vp: VertexPattern) -> Self {
184        self.vertex_patterns.push(vp);
185        self
186    }
187
188    pub fn add_edge(mut self, ep: EdgePattern) -> Self {
189        self.edge_patterns.push(ep);
190        self
191    }
192}