1use serde::{Deserialize, Serialize};
16use std::collections::HashMap;
17use std::fmt;
18
19#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
25pub enum NodeExpression {
26 FocusNode,
28
29 Constant(NodeTerm),
31
32 Path(PropertyPath),
34
35 FilterShape {
37 nodes: Box<NodeExpression>,
39 shape: String,
41 },
42
43 Intersection(Vec<NodeExpression>),
45
46 Union(Vec<NodeExpression>),
48
49 Minus {
51 base: Box<NodeExpression>,
53 excluded: Box<NodeExpression>,
55 },
56
57 IfThenElse {
60 condition: Box<NodeExpression>,
62 then_expr: Box<NodeExpression>,
64 else_expr: Box<NodeExpression>,
66 },
67
68 FunctionCall {
70 function: String,
72 args: Vec<NodeExpression>,
74 },
75
76 Count(Box<NodeExpression>),
78
79 Distinct(Box<NodeExpression>),
81
82 OrderBy {
84 expr: Box<NodeExpression>,
86 sort_path: PropertyPath,
88 ascending: bool,
90 },
91
92 Limit {
94 expr: Box<NodeExpression>,
96 limit: usize,
98 },
99
100 Offset {
102 expr: Box<NodeExpression>,
104 offset: usize,
106 },
107
108 GroupConcat {
110 expr: Box<NodeExpression>,
112 separator: String,
114 },
115}
116
117#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
119pub enum PropertyPath {
120 Predicate(String),
122 Inverse(Box<PropertyPath>),
124 Sequence(Vec<PropertyPath>),
126 Alternative(Vec<PropertyPath>),
128 ZeroOrMore(Box<PropertyPath>),
130 OneOrMore(Box<PropertyPath>),
132 ZeroOrOne(Box<PropertyPath>),
134}
135
136impl fmt::Display for PropertyPath {
137 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
138 match self {
139 Self::Predicate(iri) => write!(f, "<{iri}>"),
140 Self::Inverse(p) => write!(f, "^{p}"),
141 Self::Sequence(steps) => {
142 let parts: Vec<_> = steps.iter().map(|s| s.to_string()).collect();
143 write!(f, "{}", parts.join(" / "))
144 }
145 Self::Alternative(alts) => {
146 let parts: Vec<_> = alts.iter().map(|s| s.to_string()).collect();
147 write!(f, "{}", parts.join(" | "))
148 }
149 Self::ZeroOrMore(p) => write!(f, "{p}*"),
150 Self::OneOrMore(p) => write!(f, "{p}+"),
151 Self::ZeroOrOne(p) => write!(f, "{p}?"),
152 }
153 }
154}
155
156#[derive(Debug, Clone, PartialEq, Eq, Hash, Ord, PartialOrd, Serialize, Deserialize)]
162pub enum NodeTerm {
163 Iri(String),
165 Literal {
167 value: String,
168 datatype: Option<String>,
169 lang: Option<String>,
170 },
171 BlankNode(String),
173}
174
175impl fmt::Display for NodeTerm {
176 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
177 match self {
178 Self::Iri(iri) => write!(f, "<{iri}>"),
179 Self::Literal {
180 value,
181 datatype,
182 lang,
183 } => {
184 write!(f, "\"{value}\"")?;
185 if let Some(dt) = datatype {
186 write!(f, "^^<{dt}>")?;
187 }
188 if let Some(l) = lang {
189 write!(f, "@{l}")?;
190 }
191 Ok(())
192 }
193 Self::BlankNode(id) => write!(f, "_:{id}"),
194 }
195 }
196}
197
198impl NodeTerm {
199 pub fn iri(s: impl Into<String>) -> Self {
201 Self::Iri(s.into())
202 }
203
204 pub fn literal(s: impl Into<String>) -> Self {
206 Self::Literal {
207 value: s.into(),
208 datatype: None,
209 lang: None,
210 }
211 }
212
213 pub fn typed_literal(s: impl Into<String>, dt: impl Into<String>) -> Self {
215 Self::Literal {
216 value: s.into(),
217 datatype: Some(dt.into()),
218 lang: None,
219 }
220 }
221
222 pub fn lang_literal(s: impl Into<String>, lang: impl Into<String>) -> Self {
224 Self::Literal {
225 value: s.into(),
226 datatype: None,
227 lang: Some(lang.into()),
228 }
229 }
230
231 pub fn is_iri(&self) -> bool {
233 matches!(self, Self::Iri(_))
234 }
235
236 pub fn is_literal(&self) -> bool {
238 matches!(self, Self::Literal { .. })
239 }
240
241 pub fn as_str(&self) -> &str {
243 match self {
244 Self::Iri(s) | Self::BlankNode(s) => s,
245 Self::Literal { value, .. } => value,
246 }
247 }
248
249 pub fn as_integer(&self) -> Option<i64> {
251 if let Self::Literal { value, .. } = self {
252 value.parse().ok()
253 } else {
254 None
255 }
256 }
257
258 pub fn as_float(&self) -> Option<f64> {
260 if let Self::Literal { value, .. } = self {
261 value.parse().ok()
262 } else {
263 None
264 }
265 }
266}
267
268#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
274pub struct Triple {
275 pub subject: NodeTerm,
277 pub predicate: NodeTerm,
279 pub object: NodeTerm,
281}
282
283pub trait NodeExprGraph {
285 fn objects(&self, subject: &NodeTerm, predicate: &str) -> Vec<NodeTerm>;
287
288 fn subjects(&self, predicate: &str, object: &NodeTerm) -> Vec<NodeTerm>;
290
291 fn conforms_to_shape(&self, node: &NodeTerm, shape_iri: &str) -> bool;
293
294 fn all_triples(&self) -> Vec<Triple>;
296}
297
298#[derive(Debug, Clone, Default)]
300pub struct InMemoryGraph {
301 triples: Vec<Triple>,
302 shape_conformance: HashMap<(String, String), bool>,
304}
305
306impl InMemoryGraph {
307 pub fn new() -> Self {
309 Self::default()
310 }
311
312 pub fn add_triple(&mut self, subject: NodeTerm, predicate: NodeTerm, object: NodeTerm) {
314 self.triples.push(Triple {
315 subject,
316 predicate,
317 object,
318 });
319 }
320
321 pub fn set_conforms(&mut self, node: &str, shape: &str, conforms: bool) {
323 self.shape_conformance
324 .insert((node.to_string(), shape.to_string()), conforms);
325 }
326
327 pub fn len(&self) -> usize {
329 self.triples.len()
330 }
331
332 pub fn is_empty(&self) -> bool {
334 self.triples.is_empty()
335 }
336}
337
338impl NodeExprGraph for InMemoryGraph {
339 fn objects(&self, subject: &NodeTerm, predicate: &str) -> Vec<NodeTerm> {
340 self.triples
341 .iter()
342 .filter(|t| {
343 &t.subject == subject && matches!(&t.predicate, NodeTerm::Iri(p) if p == predicate)
344 })
345 .map(|t| t.object.clone())
346 .collect()
347 }
348
349 fn subjects(&self, predicate: &str, object: &NodeTerm) -> Vec<NodeTerm> {
350 self.triples
351 .iter()
352 .filter(|t| {
353 &t.object == object && matches!(&t.predicate, NodeTerm::Iri(p) if p == predicate)
354 })
355 .map(|t| t.subject.clone())
356 .collect()
357 }
358
359 fn conforms_to_shape(&self, node: &NodeTerm, shape_iri: &str) -> bool {
360 let node_str = node.as_str().to_string();
361 self.shape_conformance
362 .get(&(node_str, shape_iri.to_string()))
363 .copied()
364 .unwrap_or(false)
365 }
366
367 fn all_triples(&self) -> Vec<Triple> {
368 self.triples.clone()
369 }
370}
371
372#[derive(Debug, Clone, Serialize, Deserialize)]
378pub struct EvalConfig {
379 pub max_path_depth: usize,
381 pub max_results: usize,
383 pub deduplicate: bool,
385}
386
387impl Default for EvalConfig {
388 fn default() -> Self {
389 Self {
390 max_path_depth: 100,
391 max_results: 10000,
392 deduplicate: false,
393 }
394 }
395}
396
397#[derive(Debug, Clone, Default, Serialize, Deserialize)]
399pub struct EvalStats {
400 pub graph_lookups: u64,
402 pub nodes_evaluated: u64,
404 pub path_traversals: u64,
406 pub shape_checks: u64,
408 pub function_calls: u64,
410}
411
412#[derive(Debug, Clone, Serialize, Deserialize)]
414pub enum NodeExprError {
415 PathDepthExceeded { depth: usize, max: usize },
417 ResultLimitExceeded { count: usize, max: usize },
419 UnknownFunction(String),
421 InvalidArgCount {
423 function: String,
424 expected: usize,
425 got: usize,
426 },
427 TypeError(String),
429 EmptySequence(String),
431}
432
433impl fmt::Display for NodeExprError {
434 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
435 match self {
436 Self::PathDepthExceeded { depth, max } => {
437 write!(f, "Path depth {depth} exceeded maximum {max}")
438 }
439 Self::ResultLimitExceeded { count, max } => {
440 write!(f, "Result count {count} exceeded limit {max}")
441 }
442 Self::UnknownFunction(name) => write!(f, "Unknown function: {name}"),
443 Self::InvalidArgCount {
444 function,
445 expected,
446 got,
447 } => {
448 write!(f, "Function {function} expects {expected} args, got {got}")
449 }
450 Self::TypeError(msg) => write!(f, "Type error: {msg}"),
451 Self::EmptySequence(ctx) => write!(f, "Empty sequence in {ctx}"),
452 }
453 }
454}
455
456impl std::error::Error for NodeExprError {}
457
458pub struct ExprBuilder;
464
465impl ExprBuilder {
466 pub fn focus() -> NodeExpression {
468 NodeExpression::FocusNode
469 }
470
471 pub fn constant(term: NodeTerm) -> NodeExpression {
473 NodeExpression::Constant(term)
474 }
475
476 pub fn path(predicate: impl Into<String>) -> NodeExpression {
478 NodeExpression::Path(PropertyPath::Predicate(predicate.into()))
479 }
480
481 pub fn sequence_path(predicates: &[&str]) -> NodeExpression {
483 NodeExpression::Path(PropertyPath::Sequence(
484 predicates
485 .iter()
486 .map(|p| PropertyPath::Predicate(p.to_string()))
487 .collect(),
488 ))
489 }
490
491 pub fn intersection(exprs: Vec<NodeExpression>) -> NodeExpression {
493 NodeExpression::Intersection(exprs)
494 }
495
496 pub fn union(exprs: Vec<NodeExpression>) -> NodeExpression {
498 NodeExpression::Union(exprs)
499 }
500
501 pub fn minus(base: NodeExpression, excluded: NodeExpression) -> NodeExpression {
503 NodeExpression::Minus {
504 base: Box::new(base),
505 excluded: Box::new(excluded),
506 }
507 }
508
509 pub fn if_then_else(
511 condition: NodeExpression,
512 then_expr: NodeExpression,
513 else_expr: NodeExpression,
514 ) -> NodeExpression {
515 NodeExpression::IfThenElse {
516 condition: Box::new(condition),
517 then_expr: Box::new(then_expr),
518 else_expr: Box::new(else_expr),
519 }
520 }
521
522 pub fn count(inner: NodeExpression) -> NodeExpression {
524 NodeExpression::Count(Box::new(inner))
525 }
526
527 pub fn distinct(inner: NodeExpression) -> NodeExpression {
529 NodeExpression::Distinct(Box::new(inner))
530 }
531
532 pub fn limit(inner: NodeExpression, limit: usize) -> NodeExpression {
534 NodeExpression::Limit {
535 expr: Box::new(inner),
536 limit,
537 }
538 }
539
540 pub fn offset(inner: NodeExpression, offset: usize) -> NodeExpression {
542 NodeExpression::Offset {
543 expr: Box::new(inner),
544 offset,
545 }
546 }
547}