link_cli/
query_processor_substitution.rs1use anyhow::Result;
5use std::collections::{HashMap, HashSet};
6
7use crate::error::LinkError;
8use crate::link::Link;
9use crate::named_type_links::NamedTypeLinks;
10use crate::query_processor::QueryProcessor;
11use crate::query_types::Pattern;
12
13impl QueryProcessor {
14 pub fn preserve_existing_substitution_parts(
15 storage: &mut impl NamedTypeLinks,
16 pattern: &Pattern,
17 solution: &mut HashMap<String, u32>,
18 index: u32,
19 source: &mut u32,
20 target: &mut u32,
21 visited_indexes: &mut HashSet<u32>,
22 ) -> Result<()> {
23 if !is_normal_index(index) || !storage.exists(index) {
24 return Ok(());
25 }
26
27 if !visited_indexes.insert(index) {
28 return Ok(());
29 }
30
31 let existing_link = storage.get_link(index).ok_or(LinkError::not_found(index))?;
32
33 if should_preserve_existing_part(pattern.source.as_deref(), solution)
34 && can_preserve_existing_part(existing_link, existing_link.source, visited_indexes)
35 {
36 *source = existing_link.source;
37 assign_variable_from_pattern(pattern.source.as_deref(), *source, solution);
38 } else if let Some(bound_source) =
39 resolved_variable_part(pattern.source.as_deref(), solution)
40 {
41 *source = bound_source;
42 }
43
44 if should_preserve_existing_part(pattern.target.as_deref(), solution)
45 && can_preserve_existing_part(existing_link, existing_link.target, visited_indexes)
46 {
47 *target = existing_link.target;
48 assign_variable_from_pattern(pattern.target.as_deref(), *target, solution);
49 } else if let Some(bound_target) =
50 resolved_variable_part(pattern.target.as_deref(), solution)
51 {
52 *target = bound_target;
53 }
54
55 visited_indexes.remove(&index);
56 Ok(())
57 }
58}
59
60fn should_preserve_existing_part(
61 pattern: Option<&Pattern>,
62 solution: &HashMap<String, u32>,
63) -> bool {
64 pattern.is_some_and(|pattern| {
65 pattern.is_leaf() && is_variable(&pattern.index) && !solution.contains_key(&pattern.index)
66 })
67}
68
69fn resolved_variable_part(
70 pattern: Option<&Pattern>,
71 solution: &HashMap<String, u32>,
72) -> Option<u32> {
73 pattern.and_then(|pattern| {
74 if pattern.is_leaf() && is_variable(&pattern.index) {
75 solution.get(&pattern.index).copied()
76 } else {
77 None
78 }
79 })
80}
81
82fn assign_variable_from_pattern(
83 pattern: Option<&Pattern>,
84 value: u32,
85 solution: &mut HashMap<String, u32>,
86) {
87 if let Some(pattern) = pattern {
88 assign_variable(&pattern.index, value, solution);
89 }
90}
91
92fn assign_variable(id: &str, value: u32, assignments: &mut HashMap<String, u32>) {
93 if is_variable(id) && value != 0 {
94 assignments.insert(id.to_string(), value);
95 }
96}
97
98fn can_preserve_existing_part(
99 existing_link: Link,
100 part: u32,
101 visited_indexes: &HashSet<u32>,
102) -> bool {
103 existing_link.is_full_point()
104 || existing_link.is_partial_point()
105 || !visited_indexes.contains(&part)
106}
107
108fn is_variable(identifier: &str) -> bool {
109 !identifier.is_empty() && identifier.starts_with('$')
110}
111
112fn is_normal_index(value: u32) -> bool {
113 value != 0 && value != u32::MAX
114}