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(_) => return None,
264 };
265
266 Some(TriplePattern {
267 subject,
268 predicate,
269 object,
270 })
271 }
272 _ => None,
273 }
274 }
275
276 pub fn has_variables(&self) -> bool {
278 self.subject.is_variable() || self.object.is_variable()
279 }
280
281 pub fn variables(&self) -> Vec<Variable> {
283 let mut vars = Vec::new();
284 if let TermPattern::Variable(v) = &self.subject {
285 vars.push(v.clone());
286 }
287 if let TermPattern::Variable(v) = &self.object {
288 vars.push(v.clone());
289 }
290 vars
291 }
292}
293
294pub struct PropertyPathEvaluator {
296 max_depth: usize,
298 cycle_detection: bool,
300 distinct_paths: bool,
302}
303
304impl Default for PropertyPathEvaluator {
305 fn default() -> Self {
306 Self::new()
307 }
308}
309
310impl PropertyPathEvaluator {
311 pub fn new() -> Self {
313 PropertyPathEvaluator {
314 max_depth: 100,
315 cycle_detection: true,
316 distinct_paths: false,
317 }
318 }
319
320 pub fn with_max_depth(mut self, depth: usize) -> Self {
322 self.max_depth = depth;
323 self
324 }
325
326 pub fn with_cycle_detection(mut self, enable: bool) -> Self {
328 self.cycle_detection = enable;
329 self
330 }
331
332 pub fn with_distinct_paths(mut self, enable: bool) -> Self {
334 self.distinct_paths = enable;
335 self
336 }
337
338 pub fn evaluate(
341 &self,
342 _pattern: &PropertyPathPattern,
343 ) -> Result<Vec<(Term, Term)>, OxirsError> {
344 Ok(Vec::new())
346 }
347}
348
349pub struct PropertyPathOptimizer {
351 rewrite_enabled: bool,
353 decompose_enabled: bool,
355}
356
357impl Default for PropertyPathOptimizer {
358 fn default() -> Self {
359 Self::new()
360 }
361}
362
363impl PropertyPathOptimizer {
364 pub fn new() -> Self {
366 PropertyPathOptimizer {
367 rewrite_enabled: true,
368 decompose_enabled: true,
369 }
370 }
371
372 pub fn optimize(&self, path: PropertyPath) -> PropertyPath {
374 if !self.rewrite_enabled {
375 return path;
376 }
377
378 self.optimize_recursive(path)
380 }
381
382 #[allow(clippy::only_used_in_recursion)]
383 fn optimize_recursive(&self, path: PropertyPath) -> PropertyPath {
384 match path {
385 PropertyPath::Sequence(ref l, ref r) if l == r => {
387 PropertyPath::FixedLength(l.clone(), 2)
388 }
389
390 PropertyPath::Alternative(ref l, ref r) => match (l.as_ref(), r.as_ref()) {
392 (PropertyPath::ZeroOrOne(p1), PropertyPath::OneOrMore(p2)) if p1 == p2 => {
393 PropertyPath::ZeroOrMore(p1.clone())
394 }
395 (PropertyPath::OneOrMore(p1), PropertyPath::ZeroOrOne(p2)) if p1 == p2 => {
396 PropertyPath::ZeroOrMore(p1.clone())
397 }
398 _ => PropertyPath::Alternative(
399 Box::new(self.optimize_recursive(*l.clone())),
400 Box::new(self.optimize_recursive(*r.clone())),
401 ),
402 },
403
404 PropertyPath::Inverse(p) => {
406 PropertyPath::Inverse(Box::new(self.optimize_recursive(*p)))
407 }
408 PropertyPath::Sequence(l, r) => PropertyPath::Sequence(
409 Box::new(self.optimize_recursive(*l)),
410 Box::new(self.optimize_recursive(*r)),
411 ),
412 PropertyPath::ZeroOrMore(p) => {
413 PropertyPath::ZeroOrMore(Box::new(self.optimize_recursive(*p)))
414 }
415 PropertyPath::OneOrMore(p) => {
416 PropertyPath::OneOrMore(Box::new(self.optimize_recursive(*p)))
417 }
418 PropertyPath::ZeroOrOne(p) => {
419 PropertyPath::ZeroOrOne(Box::new(self.optimize_recursive(*p)))
420 }
421 PropertyPath::FixedLength(p, n) => {
422 PropertyPath::FixedLength(Box::new(self.optimize_recursive(*p)), n)
423 }
424 PropertyPath::RangeLength(p, min, max) => {
425 PropertyPath::RangeLength(Box::new(self.optimize_recursive(*p)), min, max)
426 }
427 PropertyPath::Distinct(p) => {
428 PropertyPath::Distinct(Box::new(self.optimize_recursive(*p)))
429 }
430
431 PropertyPath::Predicate(_) | PropertyPath::NegatedPropertySet(_) => path,
433 }
434 }
435}
436
437#[cfg(test)]
438mod tests {
439 use super::*;
440
441 #[test]
442 fn test_property_path_creation() {
443 let p1 = NamedNode::new("http://example.org/knows").expect("valid IRI");
444 let p2 = NamedNode::new("http://example.org/likes").expect("valid IRI");
445
446 let path = PropertyPath::predicate(p1.clone());
448 assert_eq!(path.min_length(), 1);
449 assert_eq!(path.max_length(), Some(1));
450
451 let seq = PropertyPath::sequence(
453 PropertyPath::predicate(p1.clone()),
454 PropertyPath::predicate(p2.clone()),
455 );
456 assert_eq!(seq.min_length(), 2);
457 assert_eq!(seq.max_length(), Some(2));
458
459 let star = PropertyPath::zero_or_more(PropertyPath::predicate(p1.clone()));
461 assert_eq!(star.min_length(), 0);
462 assert_eq!(star.max_length(), None);
463
464 let fixed = PropertyPath::fixed_length(PropertyPath::predicate(p1.clone()), 3);
466 assert_eq!(fixed.min_length(), 3);
467 assert_eq!(fixed.max_length(), Some(3));
468 }
469
470 #[test]
471 fn test_property_path_display() {
472 let p1 = NamedNode::new("http://example.org/p").expect("valid IRI");
473 let p2 = NamedNode::new("http://example.org/q").expect("valid IRI");
474
475 let path = PropertyPath::sequence(
476 PropertyPath::predicate(p1.clone()),
477 PropertyPath::zero_or_more(PropertyPath::predicate(p2.clone())),
478 );
479
480 let expected = format!("{p1}/{p2}*");
481 assert_eq!(format!("{path}"), expected);
482 }
483
484 #[test]
485 fn test_path_optimization() {
486 let optimizer = PropertyPathOptimizer::new();
487 let p = PropertyPath::predicate(NamedNode::new("http://example.org/p").expect("valid IRI"));
488
489 let seq = PropertyPath::sequence(p.clone(), p.clone());
491 let optimized = optimizer.optimize(seq);
492 assert!(matches!(optimized, PropertyPath::FixedLength(_, 2)));
493
494 let alt = PropertyPath::alternative(
496 PropertyPath::zero_or_one(p.clone()),
497 PropertyPath::one_or_more(p.clone()),
498 );
499 let optimized = optimizer.optimize(alt);
500 assert!(matches!(optimized, PropertyPath::ZeroOrMore(_)));
501 }
502}