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