velesdb_core/velesql/parser/
match_patterns.rs1use crate::velesql::error::ParseError;
9use crate::velesql::graph_pattern::{Direction, GraphPattern, NodePattern, RelationshipPattern};
10
11use super::match_clause::split_with_braces;
12
13pub fn parse_node_pattern(input: &str) -> Result<NodePattern, ParseError> {
19 let input = input.trim();
20 validate_node_delimiters(input)?;
21 let inner = input[1..input.len() - 1].trim();
22 if inner.is_empty() {
23 return Ok(NodePattern::new());
24 }
25 let mut node = NodePattern::new();
26 let (main_part, properties) = split_with_braces(inner, input, "node pattern")?;
27 node.properties = properties;
28 apply_alias_and_labels(main_part, &mut node);
29 Ok(node)
30}
31
32fn validate_node_delimiters(input: &str) -> Result<(), ParseError> {
34 if !input.starts_with('(') {
35 return Err(ParseError::syntax(
36 0,
37 input,
38 "Node pattern must start with '('",
39 ));
40 }
41 if !input.ends_with(')') {
42 return Err(ParseError::syntax(input.len(), input, "Expected ')'"));
43 }
44 Ok(())
45}
46
47fn apply_alias_and_labels(main_part: &str, node: &mut NodePattern) {
55 if main_part.is_empty() {
56 return;
57 }
58
59 let (part_without_coll, collection) = extract_collection_annotation(main_part);
60
61 let parts: Vec<&str> = part_without_coll.split(':').collect();
62 if !parts[0].trim().is_empty() {
63 node.alias = Some(parts[0].trim().to_string());
64 }
65 for label in &parts[1..] {
66 let trimmed = label.trim();
67 if !trimmed.is_empty() {
68 node.labels.push(trimmed.to_string());
69 }
70 }
71
72 node.collection = collection;
73}
74
75fn extract_collection_annotation(input: &str) -> (&str, Option<String>) {
80 if let Some(at_pos) = input.rfind('@') {
81 let before = &input[..at_pos];
82 let after = input[at_pos + 1..].trim();
83 if !after.is_empty() {
84 return (before, Some(after.to_string()));
85 }
86 }
87 (input, None)
88}
89
90pub fn parse_relationship_pattern(input: &str) -> Result<RelationshipPattern, ParseError> {
97 let input = input.trim();
98 let (direction, is, ie) = detect_direction_and_brackets(input)?;
99 let mut rel = RelationshipPattern::new(direction);
100
101 validate_bracket_matching(input)?;
102
103 if input.contains('[') && input.contains(']') {
104 parse_bracket_contents(input, is, ie, &mut rel)?;
105 }
106 Ok(rel)
107}
108
109fn detect_direction_and_brackets(input: &str) -> Result<(Direction, usize, usize), ParseError> {
111 if input.starts_with("<-") && input.ends_with('-') {
112 Ok((
113 Direction::Incoming,
114 input.find('[').unwrap_or(2),
115 input.rfind(']').unwrap_or(input.len() - 1),
116 ))
117 } else if input.starts_with('-') && input.ends_with("->") {
118 Ok((
119 Direction::Outgoing,
120 input.find('[').unwrap_or(1),
121 input.rfind(']').unwrap_or(input.len() - 2),
122 ))
123 } else if input.starts_with('-') && input.ends_with('-') {
124 Ok((
125 Direction::Both,
126 input.find('[').unwrap_or(1),
127 input.rfind(']').unwrap_or(input.len() - 1),
128 ))
129 } else {
130 Err(ParseError::syntax(
131 0,
132 input,
133 "Invalid relationship direction",
134 ))
135 }
136}
137
138fn validate_bracket_matching(input: &str) -> Result<(), ParseError> {
140 let has_open = input.contains('[');
141 let has_close = input.contains(']');
142 if has_open != has_close {
143 return Err(ParseError::syntax(
144 0,
145 input,
146 if has_open {
147 "Missing closing ']' in relationship pattern"
148 } else {
149 "Missing opening '[' in relationship pattern"
150 },
151 ));
152 }
153 Ok(())
154}
155
156fn parse_bracket_contents(
158 input: &str,
159 is: usize,
160 ie: usize,
161 rel: &mut RelationshipPattern,
162) -> Result<(), ParseError> {
163 if ie <= is {
164 return Err(ParseError::syntax(
165 is,
166 input,
167 "Mismatched brackets in relationship pattern",
168 ));
169 }
170 let inner = input[is + 1..ie].trim();
171 if inner.is_empty() {
172 return Ok(());
173 }
174 if let Some(sp) = inner.find('*') {
175 if let Some((s, e)) = parse_range(&inner[sp + 1..]) {
176 rel.range = Some((s, e));
177 }
178 parse_rel_details(inner[..sp].trim(), rel)?;
179 } else {
180 parse_rel_details(inner, rel)?;
181 }
182 Ok(())
183}
184
185fn parse_rel_details(input: &str, rel: &mut RelationshipPattern) -> Result<(), ParseError> {
186 if input.is_empty() {
187 return Ok(());
188 }
189 let (main_part, props) = split_with_braces(input, input, "relationship properties")?;
190 rel.properties = props;
191 if let Some(stripped) = main_part.strip_prefix(':') {
192 parse_rel_types(stripped, rel);
193 } else if let Some(cp) = main_part.find(':') {
194 rel.alias = Some(main_part[..cp].trim().to_string());
195 parse_rel_types(&main_part[cp + 1..], rel);
196 } else if !main_part.is_empty() {
197 rel.alias = Some(main_part.to_string());
198 }
199 Ok(())
200}
201
202fn parse_rel_types(input: &str, rel: &mut RelationshipPattern) {
203 for t in input.split('|') {
204 if !t.trim().is_empty() {
205 rel.types.push(t.trim().to_string());
206 }
207 }
208}
209
210fn parse_range(input: &str) -> Option<(u32, u32)> {
212 let input = input.trim();
213 if input.is_empty() {
214 return Some((1, u32::MAX));
215 }
216 if let Some(d) = input.find("..") {
217 Some((
218 input[..d].trim().parse().unwrap_or(1),
219 input[d + 2..].trim().parse().unwrap_or(u32::MAX),
220 ))
221 } else {
222 input.parse::<u32>().ok().map(|n| (n, n))
223 }
224}
225
226pub(super) fn parse_pattern_list(input: &str) -> Result<Vec<GraphPattern>, ParseError> {
228 let (name, ps) = if let Some(eq) = input.find('=') {
229 let b = input[..eq].trim();
230 if b.chars().all(|c| c.is_alphanumeric() || c == '_') {
231 (Some(b.to_string()), input[eq + 1..].trim())
232 } else {
233 (None, input)
234 }
235 } else {
236 (None, input)
237 };
238 let mut pattern = parse_path_pattern(ps)?;
239 pattern.name = name;
240 Ok(vec![pattern])
241}
242
243fn parse_path_pattern(input: &str) -> Result<GraphPattern, ParseError> {
244 let mut nodes = Vec::new();
245 let mut rels = Vec::new();
246 let mut pos = 0;
247 let input = input.trim();
248 while pos < input.len() {
249 let Some(s) = input[pos..].find('(') else {
250 break;
251 };
252 let abs = pos + s;
253 let end = find_matching_paren(input, abs)?;
254 nodes.push(parse_node_pattern(&input[abs..=end])?);
255 pos = end + 1;
256 pos += parse_trailing_relationship(&input[pos.min(input.len())..], &mut rels)?;
257 }
258 Ok(GraphPattern {
259 name: None,
260 nodes,
261 relationships: rels,
262 })
263}
264
265fn parse_trailing_relationship(
267 rem: &str,
268 rels: &mut Vec<RelationshipPattern>,
269) -> Result<usize, ParseError> {
270 if !(rem.starts_with('-') || rem.starts_with('<')) {
271 return Ok(0);
272 }
273 let Some(np) = rem.find('(') else {
274 return Ok(0);
275 };
276 rels.push(parse_relationship_pattern(&rem[..np])?);
277 Ok(np)
278}
279
280fn find_matching_paren(input: &str, start: usize) -> Result<usize, ParseError> {
281 let mut d = 0;
282 for (i, c) in input[start..].char_indices() {
283 match c {
284 '(' => d += 1,
285 ')' => {
286 d -= 1;
287 if d == 0 {
288 return Ok(start + i);
289 }
290 }
291 _ => {}
292 }
293 }
294 Err(ParseError::syntax(start, input, "Expected ')'"))
295}