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