1use anyhow::Result;
7use std::collections::{HashMap, HashSet};
8
9use crate::changes_simplifier::simplify_changes;
10use crate::error::LinkError;
11use crate::link::Link;
12use crate::link_reference_validator::LinkReferenceValidator;
13use crate::lino_link::LinoLink;
14use crate::named_type_links::NamedTypeLinks;
15use crate::parser::Parser;
16use crate::query_types::{Pattern, ResolvedLink};
17
18mod matching;
20
21pub struct QueryProcessor {
24 trace: bool,
25 auto_create_missing_references: bool,
26}
27
28impl QueryProcessor {
29 pub fn new(trace: bool) -> Self {
31 Self {
32 trace,
33 auto_create_missing_references: false,
34 }
35 }
36
37 pub fn with_auto_create_missing_references(
38 mut self,
39 auto_create_missing_references: bool,
40 ) -> Self {
41 self.auto_create_missing_references = auto_create_missing_references;
42 self
43 }
44
45 pub fn process_query(
47 &self,
48 storage: &mut impl NamedTypeLinks,
49 query: &str,
50 ) -> Result<Vec<(Option<Link>, Option<Link>)>> {
51 self.trace_msg(&format!("[ProcessQuery] Query: \"{}\"", query));
52
53 let query = query.trim();
54 if query.is_empty() {
55 self.trace_msg("[ProcessQuery] Query is empty, returning.");
56 return Ok(vec![]);
57 }
58
59 let parser = Parser::new();
60 let parsed_links = parser.parse(query)?;
61
62 self.trace_msg(&format!(
63 "[ProcessQuery] Parser returned {} top-level link(s).",
64 parsed_links.len()
65 ));
66
67 if parsed_links.is_empty() {
68 self.trace_msg("[ProcessQuery] No top-level parsed links found, returning.");
69 return Ok(vec![]);
70 }
71
72 let (restriction_link, substitution_link) = match &parsed_links[0].values {
75 Some(values) if values.len() >= 2 => (&values[0], &values[1]),
76 _ if parsed_links.len() >= 2 => (&parsed_links[0], &parsed_links[1]),
77 _ => {
78 self.trace_msg("[ProcessQuery] Query has fewer than 2 links, returning.");
79 return Ok(vec![]);
80 }
81 };
82
83 self.trace_msg(&format!(
84 "[ProcessQuery] Restriction link => Id={:?} Values.Count={}",
85 restriction_link.id,
86 restriction_link.values_count()
87 ));
88 self.trace_msg(&format!(
89 "[ProcessQuery] Substitution link => Id={:?} Values.Count={}",
90 substitution_link.id,
91 substitution_link.values_count()
92 ));
93
94 let mut changes_list = Vec::new();
95
96 if restriction_link.is_empty() && substitution_link.is_empty() {
98 self.trace_msg(
99 "[ProcessQuery] Restriction & substitution both empty => no operation, returning.",
100 );
101 return Ok(vec![]);
102 }
103
104 if restriction_link.is_empty() && !substitution_link.is_empty() {
106 self.trace_msg(
107 "[ProcessQuery] No restriction, but substitution is non-empty => creation scenario.",
108 );
109 if let Some(values) = &substitution_link.values {
110 changes_list.extend(
111 self.validate_links_exist_or_will_be_created(storage, &[], values)?
112 .into_iter()
113 .map(|link| (None, Some(link))),
114 );
115
116 for link_to_create in values {
117 let created_id = self.ensure_link_created(storage, link_to_create)?;
118 self.trace_msg(&format!(
119 "[ProcessQuery] Created link ID #{} from substitution pattern.",
120 created_id
121 ));
122 if let Some(link) = storage.get_link(created_id) {
123 changes_list.push((None, Some(link)));
124 }
125 }
126 }
127 storage.save()?;
128 return Ok(changes_list);
129 }
130
131 if !restriction_link.is_empty() && substitution_link.is_empty() {
133 self.trace_msg(
134 "[ProcessQuery] Restriction non-empty, substitution empty => deletion scenario.",
135 );
136 let restriction_values = restriction_link.values.as_deref().unwrap_or(&[]);
137 changes_list.extend(
138 self.validate_links_exist_or_will_be_created(storage, restriction_values, &[])?
139 .into_iter()
140 .map(|link| (None, Some(link))),
141 );
142
143 let restriction_patterns = self.patterns_from_lino(restriction_link);
144 let mut links_to_delete = Vec::new();
145 for pattern in &restriction_patterns {
146 links_to_delete.extend(self.matched_links(storage, pattern, &HashMap::new())?);
147 }
148 links_to_delete.sort_by_key(|link| link.index);
149 links_to_delete.dedup_by_key(|link| link.index);
150
151 for link in links_to_delete {
152 if storage.exists(link.index) {
153 let before = storage.delete(link.index)?;
154 changes_list.push((Some(before), None));
155 self.trace_msg(&format!("[ProcessQuery] Deleted link ID #{}.", link.index));
156 }
157 }
158 storage.save()?;
159 return Ok(changes_list);
160 }
161
162 self.trace_msg(
164 "[ProcessQuery] Both restriction and substitution non-empty => update/mixed scenario.",
165 );
166
167 let restriction_patterns = self.patterns_from_lino(restriction_link);
168 let substitution_patterns = self.patterns_from_lino(substitution_link);
169 let restriction_values = restriction_link.values.as_deref().unwrap_or(&[]);
170 let substitution_values = substitution_link.values.as_deref().unwrap_or(&[]);
171 changes_list.extend(
172 self.validate_links_exist_or_will_be_created(
173 storage,
174 restriction_values,
175 substitution_values,
176 )?
177 .into_iter()
178 .map(|link| (None, Some(link))),
179 );
180 let solutions = self.find_all_solutions(storage, &restriction_patterns)?;
181
182 if solutions.is_empty() {
183 self.trace_msg("[ProcessQuery] No solutions found => returning.");
184 if !changes_list.is_empty() {
185 storage.save()?;
186 }
187 return Ok(changes_list);
188 }
189
190 let mut all_solutions_no_operation = true;
191 for solution in &solutions {
192 if !self.solution_is_no_operation(
193 storage,
194 solution,
195 &restriction_patterns,
196 &substitution_patterns,
197 )? {
198 all_solutions_no_operation = false;
199 break;
200 }
201 }
202
203 if all_solutions_no_operation {
204 for solution in &solutions {
205 for pattern in &restriction_patterns {
206 for link in self.matched_links(storage, pattern, solution)? {
207 if !changes_list.contains(&(Some(link), Some(link))) {
208 changes_list.push((Some(link), Some(link)));
209 }
210 }
211 }
212 }
213 return Ok(changes_list);
214 }
215
216 for solution in &solutions {
217 let restriction_links =
218 self.resolve_patterns(storage, &restriction_patterns, solution, false)?;
219 let substitution_links =
220 self.resolve_patterns(storage, &substitution_patterns, solution, true)?;
221 let operations = self.determine_operations(&restriction_links, &substitution_links);
222 for (before, after) in operations {
223 self.apply_operation(storage, before, after, &mut changes_list)?;
224 }
225 }
226
227 storage.save()?;
228
229 let simplified = self.simplify_changes_list(&changes_list);
231
232 Ok(simplified)
233 }
234
235 fn validate_links_exist_or_will_be_created(
236 &self,
237 storage: &mut impl NamedTypeLinks,
238 restriction_patterns: &[LinoLink],
239 substitution_patterns: &[LinoLink],
240 ) -> Result<Vec<Link>> {
241 LinkReferenceValidator::new(self.trace, self.auto_create_missing_references)
242 .validate_links_exist_or_will_be_created(
243 storage,
244 restriction_patterns,
245 substitution_patterns,
246 )
247 }
248
249 fn patterns_from_lino(&self, lino_link: &LinoLink) -> Vec<Pattern> {
250 let mut patterns = lino_link
251 .values
252 .as_ref()
253 .map(|values| {
254 values
255 .iter()
256 .map(Self::create_pattern_from_lino)
257 .collect::<Vec<_>>()
258 })
259 .unwrap_or_default();
260
261 if lino_link.id.is_some() {
262 patterns.insert(0, Self::create_pattern_from_lino(lino_link));
263 }
264
265 patterns
266 }
267
268 fn create_pattern_from_lino(lino_link: &LinoLink) -> Pattern {
269 let index = lino_link.id.clone().unwrap_or_default();
270 match &lino_link.values {
271 Some(values) if values.len() == 2 => Pattern::new(
272 index,
273 Some(Self::create_pattern_from_lino(&values[0])),
274 Some(Self::create_pattern_from_lino(&values[1])),
275 ),
276 _ => Pattern::new(index, None, None),
277 }
278 }
279
280 fn find_all_solutions(
281 &self,
282 storage: &mut impl NamedTypeLinks,
283 patterns: &[Pattern],
284 ) -> Result<Vec<HashMap<String, u32>>> {
285 let mut partial_solutions = vec![HashMap::new()];
286
287 for pattern in patterns {
288 let mut new_solutions = Vec::new();
289 for solution in &partial_solutions {
290 for match_solution in self.match_pattern(storage, pattern, solution)? {
291 if Self::solutions_are_compatible(solution, &match_solution) {
292 let mut combined = solution.clone();
293 combined.extend(match_solution);
294 new_solutions.push(combined);
295 }
296 }
297 }
298 partial_solutions = new_solutions;
299 if partial_solutions.is_empty() {
300 break;
301 }
302 }
303
304 Ok(partial_solutions)
305 }
306
307 fn solutions_are_compatible(
308 existing: &HashMap<String, u32>,
309 new_assignments: &HashMap<String, u32>,
310 ) -> bool {
311 new_assignments
312 .iter()
313 .all(|(key, value)| existing.get(key).is_none_or(|existing| existing == value))
314 }
315
316 fn resolve_patterns_readonly(
317 &self,
318 storage: &mut impl NamedTypeLinks,
319 patterns: &[Pattern],
320 solution: &HashMap<String, u32>,
321 is_substitution: bool,
322 ) -> Result<Vec<ResolvedLink>> {
323 let mut resolved = Vec::new();
324 for pattern in patterns {
325 if let Some(link) =
326 self.resolve_pattern_readonly(storage, pattern, solution, is_substitution)?
327 {
328 resolved.push(link);
329 }
330 }
331 Ok(resolved)
332 }
333
334 fn resolve_pattern_readonly(
335 &self,
336 storage: &mut impl NamedTypeLinks,
337 pattern: &Pattern,
338 solution: &HashMap<String, u32>,
339 is_substitution: bool,
340 ) -> Result<Option<ResolvedLink>> {
341 if pattern.is_leaf() {
342 let index = self.resolve_identifier_readonly(
343 storage,
344 &pattern.index,
345 solution,
346 if is_substitution { 0 } else { u32::MAX },
347 )?;
348 return Ok(Some(ResolvedLink::new(index, u32::MAX, u32::MAX, None)));
349 }
350
351 let source_pattern = pattern
352 .source
353 .as_deref()
354 .ok_or_else(|| LinkError::InvalidFormat("Invalid source pattern".to_string()))?;
355 let target_pattern = pattern
356 .target
357 .as_deref()
358 .ok_or_else(|| LinkError::InvalidFormat("Invalid target pattern".to_string()))?;
359
360 let source = self
361 .resolve_pattern_readonly(storage, source_pattern, solution, is_substitution)?
362 .ok_or_else(|| LinkError::InvalidFormat("Invalid source pattern".to_string()))?
363 .index;
364 let target = self
365 .resolve_pattern_readonly(storage, target_pattern, solution, is_substitution)?
366 .ok_or_else(|| LinkError::InvalidFormat("Invalid target pattern".to_string()))?
367 .index;
368 let default_index = if is_substitution { 0 } else { u32::MAX };
369 let index =
370 self.resolve_identifier_readonly(storage, &pattern.index, solution, default_index)?;
371
372 Ok(Some(ResolvedLink::new(index, source, target, None)))
373 }
374
375 fn resolve_identifier_readonly(
376 &self,
377 storage: &mut impl NamedTypeLinks,
378 identifier: &str,
379 solution: &HashMap<String, u32>,
380 default_value: u32,
381 ) -> Result<u32> {
382 if identifier.is_empty() {
383 return Ok(default_value);
384 }
385 if identifier == "*" {
386 return Ok(u32::MAX);
387 }
388 if let Some(value) = solution.get(identifier) {
389 return Ok(*value);
390 }
391 if Self::is_variable(identifier) {
392 return Ok(default_value);
393 }
394 if let Ok(parsed) = identifier.parse::<u32>() {
395 return Ok(parsed);
396 }
397 Ok(storage.get_by_name(identifier)?.unwrap_or(default_value))
398 }
399
400 fn resolve_patterns(
401 &self,
402 storage: &mut impl NamedTypeLinks,
403 patterns: &[Pattern],
404 solution: &HashMap<String, u32>,
405 is_substitution: bool,
406 ) -> Result<Vec<ResolvedLink>> {
407 let mut working_solution = solution.clone();
408 let mut visited_indexes = HashSet::new();
409 let mut resolved = Vec::new();
410 for pattern in patterns {
411 resolved.push(self.resolve_pattern(
412 storage,
413 pattern,
414 &mut working_solution,
415 is_substitution,
416 &mut visited_indexes,
417 )?);
418 }
419 Ok(resolved)
420 }
421
422 fn resolve_pattern(
423 &self,
424 storage: &mut impl NamedTypeLinks,
425 pattern: &Pattern,
426 solution: &mut HashMap<String, u32>,
427 is_substitution: bool,
428 visited_indexes: &mut HashSet<u32>,
429 ) -> Result<ResolvedLink> {
430 if pattern.is_leaf() {
431 let index = self.resolve_identifier(
432 storage,
433 &pattern.index,
434 solution,
435 if is_substitution { 0 } else { u32::MAX },
436 is_substitution,
437 )?;
438 return Ok(ResolvedLink::new(index, u32::MAX, u32::MAX, None));
439 }
440
441 let mut source = self
442 .resolve_pattern(
443 storage,
444 pattern.source.as_deref().unwrap(),
445 solution,
446 is_substitution,
447 visited_indexes,
448 )?
449 .index;
450 let mut target = self
451 .resolve_pattern(
452 storage,
453 pattern.target.as_deref().unwrap(),
454 solution,
455 is_substitution,
456 visited_indexes,
457 )?
458 .index;
459 let default_index = if is_substitution { 0 } else { u32::MAX };
460 let mut index =
461 self.resolve_identifier(storage, &pattern.index, solution, default_index, false)?;
462 let mut name = None;
463
464 if is_substitution
465 && !pattern.index.is_empty()
466 && !Self::is_numeric_or_wildcard(&pattern.index)
467 && !Self::is_variable(&pattern.index)
468 {
469 name = Some(pattern.index.clone());
470 if index == 0 {
471 if let Some(existing_id) = storage.search(source, target) {
472 index = existing_id;
473 }
474 }
475 }
476
477 if is_substitution {
478 Self::preserve_existing_substitution_parts(
479 storage,
480 pattern,
481 solution,
482 index,
483 &mut source,
484 &mut target,
485 visited_indexes,
486 )?;
487 }
488
489 Ok(ResolvedLink::new(index, source, target, name))
490 }
491
492 fn resolve_identifier(
493 &self,
494 storage: &mut impl NamedTypeLinks,
495 identifier: &str,
496 solution: &HashMap<String, u32>,
497 default_value: u32,
498 create_named_leaf: bool,
499 ) -> Result<u32> {
500 if identifier.is_empty() {
501 return Ok(default_value);
502 }
503 if identifier == "*" {
504 return Ok(u32::MAX);
505 }
506 if let Some(value) = solution.get(identifier) {
507 return Ok(*value);
508 }
509 if Self::is_variable(identifier) {
510 return Ok(default_value);
511 }
512 if let Ok(parsed) = identifier.parse::<u32>() {
513 return Ok(parsed);
514 }
515 if let Some(named_id) = storage.get_by_name(identifier)? {
516 return Ok(named_id);
517 }
518 if create_named_leaf {
519 return storage.get_or_create_named(identifier);
520 }
521 Ok(default_value)
522 }
523
524 fn determine_operations(
525 &self,
526 restrictions: &[ResolvedLink],
527 substitutions: &[ResolvedLink],
528 ) -> Vec<(Option<ResolvedLink>, Option<ResolvedLink>)> {
529 let mut operations = Vec::new();
530 let mut restriction_by_index = HashMap::new();
531 let mut substitution_by_index = HashMap::new();
532 let mut wildcard_restrictions = Vec::new();
533 let mut wildcard_substitutions = Vec::new();
534
535 for restriction in restrictions {
536 if Self::is_normal_index(restriction.index) {
537 restriction_by_index.insert(restriction.index, restriction.clone());
538 } else {
539 wildcard_restrictions.push(restriction.clone());
540 }
541 }
542
543 for substitution in substitutions {
544 if Self::is_normal_index(substitution.index) {
545 substitution_by_index.insert(substitution.index, substitution.clone());
546 } else {
547 wildcard_substitutions.push(substitution.clone());
548 }
549 }
550
551 let mut all_indices = restriction_by_index
552 .keys()
553 .chain(substitution_by_index.keys())
554 .copied()
555 .collect::<Vec<_>>();
556 all_indices.sort_unstable();
557 all_indices.dedup();
558
559 for index in all_indices {
560 match (
561 restriction_by_index.get(&index),
562 substitution_by_index.get(&index),
563 ) {
564 (Some(before), Some(after)) => {
565 operations.push((Some(before.clone()), Some(after.clone())));
566 }
567 (Some(before), None) => operations.push((Some(before.clone()), None)),
568 (None, Some(after)) => operations.push((None, Some(after.clone()))),
569 (None, None) => {}
570 }
571 }
572
573 operations.extend(
574 wildcard_restrictions
575 .into_iter()
576 .map(|restriction| (Some(restriction), None)),
577 );
578 operations.extend(
579 wildcard_substitutions
580 .into_iter()
581 .map(|substitution| (None, Some(substitution))),
582 );
583
584 operations
585 }
586
587 fn apply_operation(
588 &self,
589 storage: &mut impl NamedTypeLinks,
590 before: Option<ResolvedLink>,
591 after: Option<ResolvedLink>,
592 changes: &mut Vec<(Option<Link>, Option<Link>)>,
593 ) -> Result<()> {
594 match (before, after) {
595 (Some(before), None) => {
596 let mut links = self.links_matching_definition(storage, &before)?;
597 links.sort_by_key(|link| link.index);
598 links.dedup_by_key(|link| link.index);
599 for link in links {
600 if storage.exists(link.index) {
601 let deleted = storage.delete(link.index)?;
602 changes.push((Some(deleted), None));
603 }
604 }
605 }
606 (None, Some(after)) => {
607 let created = self.create_or_update_resolved_link(storage, &after)?;
608 changes.push((None, Some(created)));
609 }
610 (Some(before), Some(after)) => {
611 if before.index == after.index && storage.exists(before.index) {
612 let before_link = storage.get_link(before.index).unwrap();
613 if before_link.source != after.source || before_link.target != after.target {
614 storage.update(before.index, after.source, after.target)?;
615 }
616 if let Some(name) = &after.name {
617 storage.set_name(before.index, name)?;
618 }
619 let after_link = storage.get_link(before.index).unwrap();
620 changes.push((Some(before_link), Some(after_link)));
621 } else {
622 self.apply_operation(storage, Some(before), None, changes)?;
623 self.apply_operation(storage, None, Some(after), changes)?;
624 }
625 }
626 (None, None) => {}
627 }
628
629 Ok(())
630 }
631
632 fn create_or_update_resolved_link(
633 &self,
634 storage: &mut impl NamedTypeLinks,
635 definition: &ResolvedLink,
636 ) -> Result<Link> {
637 let id = if Self::is_normal_index(definition.index) {
638 storage.try_ensure_created(definition.index)?;
639 storage.update(definition.index, definition.source, definition.target)?;
640 definition.index
641 } else if let Some(existing_id) = storage.search(definition.source, definition.target) {
642 existing_id
643 } else {
644 storage.create(definition.source, definition.target)
645 };
646
647 if let Some(name) = &definition.name {
648 storage.set_name(id, name)?;
649 }
650
651 Ok(storage.get_link(id).unwrap())
652 }
653
654 fn links_matching_definition(
655 &self,
656 storage: &mut impl NamedTypeLinks,
657 definition: &ResolvedLink,
658 ) -> Result<Vec<Link>> {
659 Ok(storage
660 .all_links()
661 .into_iter()
662 .filter(|link| {
663 (definition.index == 0
664 || Self::is_any(definition.index)
665 || link.index == definition.index)
666 && (Self::is_any(definition.source) || link.source == definition.source)
667 && (Self::is_any(definition.target) || link.target == definition.target)
668 })
669 .collect())
670 }
671
672 fn assign_variable(id: &str, value: u32, assignments: &mut HashMap<String, u32>) {
673 if Self::is_variable(id) && value != 0 {
674 assignments.insert(id.to_string(), value);
675 }
676 }
677
678 fn is_variable(identifier: &str) -> bool {
679 !identifier.is_empty() && identifier.starts_with('$')
680 }
681
682 fn is_any(value: u32) -> bool {
683 value == u32::MAX
684 }
685
686 fn is_normal_index(value: u32) -> bool {
687 value != 0 && !Self::is_any(value)
688 }
689
690 fn is_numeric_or_wildcard(identifier: &str) -> bool {
691 identifier == "*" || identifier.parse::<u32>().is_ok()
692 }
693
694 fn ensure_link_created(
696 &self,
697 storage: &mut impl NamedTypeLinks,
698 lino_link: &LinoLink,
699 ) -> Result<u32> {
700 if !lino_link.has_values() {
702 if let Some(ref id) = lino_link.id {
703 if id == "*" || Self::is_variable(id) {
704 return Ok(u32::MAX);
705 }
706
707 if let Ok(num) = id.parse::<u32>() {
709 return Ok(num);
710 }
711
712 return storage.get_or_create_named(id);
714 }
715 return Ok(0);
716 }
717
718 if lino_link.values_count() == 2 {
720 let values = lino_link.values.as_ref().unwrap();
721
722 let source_id = self.ensure_link_created(storage, &values[0])?;
724 let target_id = self.ensure_link_created(storage, &values[1])?;
725
726 let link_id = if let Some(ref id) = lino_link.id {
728 if let Ok(num) = id.parse::<u32>() {
729 storage.try_ensure_created(num)?;
731 storage.update(num, source_id, target_id)?;
732 num
733 } else if id == "*" || Self::is_variable(id) {
734 storage.get_or_create(source_id, target_id)
735 } else {
736 let existing = storage.get_by_name(id)?;
738 if let Some(id_num) = existing {
739 storage.update(id_num, source_id, target_id)?;
740 id_num
741 } else {
742 let new_id = storage.create(source_id, target_id);
743 storage.set_name(new_id, id)?;
744 new_id
745 }
746 }
747 } else {
748 storage.get_or_create(source_id, target_id)
750 };
751
752 return Ok(link_id);
753 }
754
755 Err(LinkError::InvalidFormat("Invalid link structure".to_string()).into())
756 }
757
758 fn simplify_changes_list(
760 &self,
761 changes: &[(Option<Link>, Option<Link>)],
762 ) -> Vec<(Option<Link>, Option<Link>)> {
763 let mut to_simplify: Vec<(Link, Link)> = Vec::new();
765 let mut non_simplifiable: Vec<(Option<Link>, Option<Link>)> = Vec::new();
766
767 for (before, after) in changes {
768 match (before, after) {
769 (Some(b), Some(a)) => {
770 to_simplify.push((*b, *a));
771 }
772 _ => {
773 non_simplifiable.push((*before, *after));
774 }
775 }
776 }
777
778 let simplified = simplify_changes(to_simplify);
779
780 let mut result: Vec<(Option<Link>, Option<Link>)> = non_simplifiable;
781 for (b, a) in simplified {
782 result.push((Some(b), Some(a)));
783 }
784
785 result
786 }
787
788 fn trace_msg(&self, msg: &str) {
790 if self.trace {
791 eprintln!("{}", msg);
792 }
793 }
794}