1#![allow(dead_code)]
7
8use crate::model::{NamedNode, Term, Variable};
9use crate::query::algebra::{TermPattern, TriplePattern};
10use crate::OxirsError;
11use std::collections::HashSet;
12use std::fmt;
13
14#[derive(Debug, Clone, PartialEq, Eq, Hash)]
16pub enum PropertyPath {
17 Predicate(NamedNode),
19
20 Inverse(Box<PropertyPath>),
22
23 Sequence(Box<PropertyPath>, Box<PropertyPath>),
25
26 Alternative(Box<PropertyPath>, Box<PropertyPath>),
28
29 ZeroOrMore(Box<PropertyPath>),
31
32 OneOrMore(Box<PropertyPath>),
34
35 ZeroOrOne(Box<PropertyPath>),
37
38 NegatedPropertySet(Vec<NamedNode>),
40
41 FixedLength(Box<PropertyPath>, usize),
43
44 RangeLength(Box<PropertyPath>, usize, Option<usize>),
46
47 Distinct(Box<PropertyPath>),
49}
50
51impl PropertyPath {
52 pub fn predicate(iri: NamedNode) -> Self {
54 PropertyPath::Predicate(iri)
55 }
56
57 pub fn inverse(path: PropertyPath) -> Self {
59 PropertyPath::Inverse(Box::new(path))
60 }
61
62 pub fn sequence(left: PropertyPath, right: PropertyPath) -> Self {
64 PropertyPath::Sequence(Box::new(left), Box::new(right))
65 }
66
67 pub fn alternative(left: PropertyPath, right: PropertyPath) -> Self {
69 PropertyPath::Alternative(Box::new(left), Box::new(right))
70 }
71
72 pub fn zero_or_more(path: PropertyPath) -> Self {
74 PropertyPath::ZeroOrMore(Box::new(path))
75 }
76
77 pub fn one_or_more(path: PropertyPath) -> Self {
79 PropertyPath::OneOrMore(Box::new(path))
80 }
81
82 pub fn zero_or_one(path: PropertyPath) -> Self {
84 PropertyPath::ZeroOrOne(Box::new(path))
85 }
86
87 pub fn negated_set(predicates: Vec<NamedNode>) -> Self {
89 PropertyPath::NegatedPropertySet(predicates)
90 }
91
92 pub fn fixed_length(path: PropertyPath, n: usize) -> Self {
94 PropertyPath::FixedLength(Box::new(path), n)
95 }
96
97 pub fn range_length(path: PropertyPath, min: usize, max: Option<usize>) -> Self {
99 PropertyPath::RangeLength(Box::new(path), min, max)
100 }
101
102 pub fn distinct(path: PropertyPath) -> Self {
104 PropertyPath::Distinct(Box::new(path))
105 }
106
107 pub fn is_simple(&self) -> bool {
109 matches!(self, PropertyPath::Predicate(_))
110 }
111
112 pub fn min_length(&self) -> usize {
114 match self {
115 PropertyPath::Predicate(_) => 1,
116 PropertyPath::Inverse(p) => p.min_length(),
117 PropertyPath::Sequence(l, r) => l.min_length() + r.min_length(),
118 PropertyPath::Alternative(l, r) => l.min_length().min(r.min_length()),
119 PropertyPath::ZeroOrMore(_) => 0,
120 PropertyPath::OneOrMore(p) => p.min_length(),
121 PropertyPath::ZeroOrOne(_) => 0,
122 PropertyPath::NegatedPropertySet(_) => 1,
123 PropertyPath::FixedLength(_, n) => *n,
124 PropertyPath::RangeLength(_, min, _) => *min,
125 PropertyPath::Distinct(p) => p.min_length(),
126 }
127 }
128
129 pub fn max_length(&self) -> Option<usize> {
131 match self {
132 PropertyPath::Predicate(_) => Some(1),
133 PropertyPath::Inverse(p) => p.max_length(),
134 PropertyPath::Sequence(l, r) => match (l.max_length(), r.max_length()) {
135 (Some(a), Some(b)) => Some(a + b),
136 _ => None,
137 },
138 PropertyPath::Alternative(l, r) => match (l.max_length(), r.max_length()) {
139 (Some(a), Some(b)) => Some(a.max(b)),
140 _ => None,
141 },
142 PropertyPath::ZeroOrMore(_) => None,
143 PropertyPath::OneOrMore(_) => None,
144 PropertyPath::ZeroOrOne(p) => p.max_length().map(|_| 1),
145 PropertyPath::NegatedPropertySet(_) => Some(1),
146 PropertyPath::FixedLength(_, n) => Some(*n),
147 PropertyPath::RangeLength(_, _, max) => *max,
148 PropertyPath::Distinct(p) => p.max_length(),
149 }
150 }
151
152 pub fn predicates(&self) -> HashSet<&NamedNode> {
154 let mut predicates = HashSet::new();
155 self.collect_predicates(&mut predicates);
156 predicates
157 }
158
159 fn collect_predicates<'a>(&'a self, predicates: &mut HashSet<&'a NamedNode>) {
160 match self {
161 PropertyPath::Predicate(p) => {
162 predicates.insert(p);
163 }
164 PropertyPath::Inverse(p) => p.collect_predicates(predicates),
165 PropertyPath::Sequence(l, r) => {
166 l.collect_predicates(predicates);
167 r.collect_predicates(predicates);
168 }
169 PropertyPath::Alternative(l, r) => {
170 l.collect_predicates(predicates);
171 r.collect_predicates(predicates);
172 }
173 PropertyPath::ZeroOrMore(p)
174 | PropertyPath::OneOrMore(p)
175 | PropertyPath::ZeroOrOne(p)
176 | PropertyPath::Distinct(p) => p.collect_predicates(predicates),
177 PropertyPath::FixedLength(p, _) | PropertyPath::RangeLength(p, _, _) => {
178 p.collect_predicates(predicates)
179 }
180 PropertyPath::NegatedPropertySet(ps) => {
181 for p in ps {
182 predicates.insert(p);
183 }
184 }
185 }
186 }
187}
188
189impl fmt::Display for PropertyPath {
190 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
191 match self {
192 PropertyPath::Predicate(p) => write!(f, "{p}"),
193 PropertyPath::Inverse(p) => write!(f, "^{p}"),
194 PropertyPath::Sequence(l, r) => write!(f, "{l}/{r}"),
195 PropertyPath::Alternative(l, r) => write!(f, "{l}|{r}"),
196 PropertyPath::ZeroOrMore(p) => write!(f, "{p}*"),
197 PropertyPath::OneOrMore(p) => write!(f, "{p}+"),
198 PropertyPath::ZeroOrOne(p) => write!(f, "{p}?"),
199 PropertyPath::NegatedPropertySet(ps) => {
200 write!(f, "!(")?;
201 for (i, p) in ps.iter().enumerate() {
202 if i > 0 {
203 write!(f, "|")?;
204 }
205 write!(f, "{p}")?;
206 }
207 write!(f, ")")
208 }
209 PropertyPath::FixedLength(p, n) => write!(f, "{p}{{{n}}}"),
210 PropertyPath::RangeLength(p, min, max) => match max {
211 Some(m) => write!(f, "{p}{{{min},{m}}}"),
212 None => write!(f, "{p}{{{min},}}"),
213 },
214 PropertyPath::Distinct(p) => write!(f, "DISTINCT({p})"),
215 }
216 }
217}
218
219#[derive(Debug, Clone, PartialEq, Eq, Hash)]
221pub struct PropertyPathPattern {
222 pub subject: TermPattern,
224 pub path: PropertyPath,
226 pub object: TermPattern,
228}
229
230impl PropertyPathPattern {
231 pub fn new(subject: TermPattern, path: PropertyPath, object: TermPattern) -> Self {
233 PropertyPathPattern {
234 subject,
235 path,
236 object,
237 }
238 }
239
240 pub fn to_triple_pattern(&self) -> Option<TriplePattern> {
242 use crate::model::pattern::{ObjectPattern, PredicatePattern, SubjectPattern};
243
244 match &self.path {
245 PropertyPath::Predicate(p) => {
246 let subject = match &self.subject {
247 TermPattern::Variable(v) => Some(SubjectPattern::Variable(v.clone())),
248 TermPattern::NamedNode(n) => Some(SubjectPattern::NamedNode(n.clone())),
249 TermPattern::BlankNode(b) => Some(SubjectPattern::BlankNode(b.clone())),
250 _ => None,
251 };
252
253 let predicate = Some(PredicatePattern::NamedNode(p.clone()));
254
255 let object = match &self.object {
256 TermPattern::Variable(v) => Some(ObjectPattern::Variable(v.clone())),
257 TermPattern::NamedNode(n) => Some(ObjectPattern::NamedNode(n.clone())),
258 TermPattern::BlankNode(b) => Some(ObjectPattern::BlankNode(b.clone())),
259 TermPattern::Literal(l) => Some(ObjectPattern::Literal(l.clone())),
260 TermPattern::QuotedTriple(_) => {
261 panic!("RDF-star quoted triples not yet supported in property paths")
262 }
263 };
264
265 Some(TriplePattern {
266 subject,
267 predicate,
268 object,
269 })
270 }
271 _ => None,
272 }
273 }
274
275 pub fn has_variables(&self) -> bool {
277 self.subject.is_variable() || self.object.is_variable()
278 }
279
280 pub fn variables(&self) -> Vec<Variable> {
282 let mut vars = Vec::new();
283 if let TermPattern::Variable(v) = &self.subject {
284 vars.push(v.clone());
285 }
286 if let TermPattern::Variable(v) = &self.object {
287 vars.push(v.clone());
288 }
289 vars
290 }
291}
292
293pub struct PropertyPathEvaluator {
295 max_depth: usize,
297 cycle_detection: bool,
299 distinct_paths: bool,
301}
302
303impl Default for PropertyPathEvaluator {
304 fn default() -> Self {
305 Self::new()
306 }
307}
308
309impl PropertyPathEvaluator {
310 pub fn new() -> Self {
312 PropertyPathEvaluator {
313 max_depth: 100,
314 cycle_detection: true,
315 distinct_paths: false,
316 }
317 }
318
319 pub fn with_max_depth(mut self, depth: usize) -> Self {
321 self.max_depth = depth;
322 self
323 }
324
325 pub fn with_cycle_detection(mut self, enable: bool) -> Self {
327 self.cycle_detection = enable;
328 self
329 }
330
331 pub fn with_distinct_paths(mut self, enable: bool) -> Self {
333 self.distinct_paths = enable;
334 self
335 }
336
337 pub fn evaluate(
340 &self,
341 _pattern: &PropertyPathPattern,
342 ) -> Result<Vec<(Term, Term)>, OxirsError> {
343 Ok(Vec::new())
345 }
346}
347
348pub struct PropertyPathOptimizer {
350 rewrite_enabled: bool,
352 decompose_enabled: bool,
354}
355
356impl Default for PropertyPathOptimizer {
357 fn default() -> Self {
358 Self::new()
359 }
360}
361
362impl PropertyPathOptimizer {
363 pub fn new() -> Self {
365 PropertyPathOptimizer {
366 rewrite_enabled: true,
367 decompose_enabled: true,
368 }
369 }
370
371 pub fn optimize(&self, path: PropertyPath) -> PropertyPath {
373 if !self.rewrite_enabled {
374 return path;
375 }
376
377 self.optimize_recursive(path)
379 }
380
381 #[allow(clippy::only_used_in_recursion)]
382 fn optimize_recursive(&self, path: PropertyPath) -> PropertyPath {
383 match path {
384 PropertyPath::Sequence(ref l, ref r) if l == r => {
386 PropertyPath::FixedLength(l.clone(), 2)
387 }
388
389 PropertyPath::Alternative(ref l, ref r) => match (l.as_ref(), r.as_ref()) {
391 (PropertyPath::ZeroOrOne(p1), PropertyPath::OneOrMore(p2)) if p1 == p2 => {
392 PropertyPath::ZeroOrMore(p1.clone())
393 }
394 (PropertyPath::OneOrMore(p1), PropertyPath::ZeroOrOne(p2)) if p1 == p2 => {
395 PropertyPath::ZeroOrMore(p1.clone())
396 }
397 _ => PropertyPath::Alternative(
398 Box::new(self.optimize_recursive(*l.clone())),
399 Box::new(self.optimize_recursive(*r.clone())),
400 ),
401 },
402
403 PropertyPath::Inverse(p) => {
405 PropertyPath::Inverse(Box::new(self.optimize_recursive(*p)))
406 }
407 PropertyPath::Sequence(l, r) => PropertyPath::Sequence(
408 Box::new(self.optimize_recursive(*l)),
409 Box::new(self.optimize_recursive(*r)),
410 ),
411 PropertyPath::ZeroOrMore(p) => {
412 PropertyPath::ZeroOrMore(Box::new(self.optimize_recursive(*p)))
413 }
414 PropertyPath::OneOrMore(p) => {
415 PropertyPath::OneOrMore(Box::new(self.optimize_recursive(*p)))
416 }
417 PropertyPath::ZeroOrOne(p) => {
418 PropertyPath::ZeroOrOne(Box::new(self.optimize_recursive(*p)))
419 }
420 PropertyPath::FixedLength(p, n) => {
421 PropertyPath::FixedLength(Box::new(self.optimize_recursive(*p)), n)
422 }
423 PropertyPath::RangeLength(p, min, max) => {
424 PropertyPath::RangeLength(Box::new(self.optimize_recursive(*p)), min, max)
425 }
426 PropertyPath::Distinct(p) => {
427 PropertyPath::Distinct(Box::new(self.optimize_recursive(*p)))
428 }
429
430 PropertyPath::Predicate(_) | PropertyPath::NegatedPropertySet(_) => path,
432 }
433 }
434}
435
436#[cfg(test)]
437mod tests {
438 use super::*;
439
440 #[test]
441 fn test_property_path_creation() {
442 let p1 = NamedNode::new("http://example.org/knows").expect("valid IRI");
443 let p2 = NamedNode::new("http://example.org/likes").expect("valid IRI");
444
445 let path = PropertyPath::predicate(p1.clone());
447 assert_eq!(path.min_length(), 1);
448 assert_eq!(path.max_length(), Some(1));
449
450 let seq = PropertyPath::sequence(
452 PropertyPath::predicate(p1.clone()),
453 PropertyPath::predicate(p2.clone()),
454 );
455 assert_eq!(seq.min_length(), 2);
456 assert_eq!(seq.max_length(), Some(2));
457
458 let star = PropertyPath::zero_or_more(PropertyPath::predicate(p1.clone()));
460 assert_eq!(star.min_length(), 0);
461 assert_eq!(star.max_length(), None);
462
463 let fixed = PropertyPath::fixed_length(PropertyPath::predicate(p1.clone()), 3);
465 assert_eq!(fixed.min_length(), 3);
466 assert_eq!(fixed.max_length(), Some(3));
467 }
468
469 #[test]
470 fn test_property_path_display() {
471 let p1 = NamedNode::new("http://example.org/p").expect("valid IRI");
472 let p2 = NamedNode::new("http://example.org/q").expect("valid IRI");
473
474 let path = PropertyPath::sequence(
475 PropertyPath::predicate(p1.clone()),
476 PropertyPath::zero_or_more(PropertyPath::predicate(p2.clone())),
477 );
478
479 let expected = format!("{p1}/{p2}*");
480 assert_eq!(format!("{path}"), expected);
481 }
482
483 #[test]
484 fn test_path_optimization() {
485 let optimizer = PropertyPathOptimizer::new();
486 let p = PropertyPath::predicate(NamedNode::new("http://example.org/p").expect("valid IRI"));
487
488 let seq = PropertyPath::sequence(p.clone(), p.clone());
490 let optimized = optimizer.optimize(seq);
491 assert!(matches!(optimized, PropertyPath::FixedLength(_, 2)));
492
493 let alt = PropertyPath::alternative(
495 PropertyPath::zero_or_one(p.clone()),
496 PropertyPath::one_or_more(p.clone()),
497 );
498 let optimized = optimizer.optimize(alt);
499 assert!(matches!(optimized, PropertyPath::ZeroOrMore(_)));
500 }
501}