oxirs_core/sparql/
patterns.rs1use crate::model::{Quad, Term};
4use crate::rdf_store::VariableBinding;
5use crate::Result;
6
7#[derive(Debug, Clone)]
9pub struct SimpleTriplePattern {
10 pub subject: Option<String>,
11 pub predicate: Option<String>,
12 pub object: Option<String>,
13}
14
15#[derive(Debug, Clone)]
17pub struct PatternGroup {
18 pub patterns: Vec<SimpleTriplePattern>,
19 pub optional: bool,
20}
21
22#[derive(Debug, Clone)]
24pub struct UnionGroup {
25 pub branches: Vec<Vec<PatternGroup>>,
26}
27
28pub fn has_union(sparql: &str) -> bool {
30 let sparql_upper = sparql.to_uppercase();
31 sparql_upper.contains(" UNION ")
32 || sparql_upper.contains("\nUNION\n")
33 || sparql_upper.contains("{UNION")
34}
35
36pub fn find_matching_brace(text: &str, start_pos: usize) -> Option<usize> {
38 let chars: Vec<char> = text.chars().collect();
39 if start_pos >= chars.len() || chars[start_pos] != '{' {
40 return None;
41 }
42
43 let mut brace_count = 1;
44 for (i, &ch) in chars.iter().enumerate().skip(start_pos + 1) {
45 if ch == '{' {
46 brace_count += 1;
47 } else if ch == '}' {
48 brace_count -= 1;
49 if brace_count == 0 {
50 return Some(i);
51 }
52 }
53 }
54
55 None
56}
57
58pub fn parse_simple_pattern(text: &str) -> Option<SimpleTriplePattern> {
60 let text = text.trim();
62
63 for line in text.split('.') {
65 let line = line.trim();
66 if line.is_empty() {
67 continue;
68 }
69
70 let line_upper = line.to_uppercase();
72 if line_upper.contains("FILTER")
73 || line_upper.contains("BIND")
74 || line_upper.contains("VALUES")
75 || line_upper.contains("UNION")
76 {
77 continue;
78 }
79
80 let parts: Vec<&str> = line.split_whitespace().collect();
81 if parts.len() >= 3 {
82 return Some(SimpleTriplePattern {
83 subject: Some(parts[0].to_string()),
84 predicate: Some(parts[1].to_string()),
85 object: Some(parts[2..].join(" ")),
86 });
87 }
88 }
89
90 None
91}
92
93pub fn extract_pattern_groups(sparql: &str) -> Result<Vec<PatternGroup>> {
99 let mut groups = Vec::new();
100
101 if let Some((where_open, where_close)) = super::query_locator::locate_where_group(sparql) {
102 let pattern_text = &sparql[where_open + 1..where_close];
103
104 let sparql_upper = pattern_text.to_uppercase();
106 if sparql_upper.contains("OPTIONAL") {
107 let mut pos = 0;
109 let mut required_patterns = Vec::new();
110
111 while pos < pattern_text.len() {
112 if let Some(opt_pos) = pattern_text[pos..].to_uppercase().find("OPTIONAL") {
114 let abs_pos = pos + opt_pos;
115
116 let before_optional = &pattern_text[pos..abs_pos];
118 if let Some(req_pattern) = parse_simple_pattern(before_optional) {
119 required_patterns.push(req_pattern);
120 }
121
122 let after_optional = &pattern_text[abs_pos + 8..];
124 if let Some(opt_brace) = after_optional.find('{') {
125 if let Some(opt_end) = find_matching_brace(after_optional, opt_brace) {
126 let optional_content = &after_optional[opt_brace + 1..opt_end];
127
128 if let Some(opt_pattern) = parse_simple_pattern(optional_content) {
130 groups.push(PatternGroup {
131 patterns: vec![opt_pattern],
132 optional: true,
133 });
134 }
135
136 pos = abs_pos + 8 + opt_end + 1;
137 } else {
138 break;
139 }
140 } else {
141 break;
142 }
143 } else {
144 if let Some(req_pattern) = parse_simple_pattern(&pattern_text[pos..]) {
146 required_patterns.push(req_pattern);
147 }
148 break;
149 }
150 }
151
152 if !required_patterns.is_empty() {
154 groups.push(PatternGroup {
155 patterns: required_patterns,
156 optional: false,
157 });
158 }
159 } else {
160 if let Some(pattern) = parse_simple_pattern(pattern_text) {
162 groups.push(PatternGroup {
163 patterns: vec![pattern],
164 optional: false,
165 });
166 }
167 }
168 }
169
170 Ok(groups)
171}
172
173pub fn apply_optional_patterns<F>(
175 bindings: Vec<VariableBinding>,
176 patterns: &[SimpleTriplePattern],
177 query_quads: F,
178) -> Result<Vec<VariableBinding>>
179where
180 F: Fn(&SimpleTriplePattern) -> Result<Vec<Quad>>,
181{
182 let mut new_results = Vec::new();
183
184 for binding in bindings {
185 let mut extended = false;
186
187 for pattern in patterns {
189 let matching_quads = query_quads(pattern)?;
190
191 for quad in matching_quads {
192 let mut new_binding = binding.clone();
193 let mut compatible = true;
194
195 if let Some(var) = &pattern.subject {
197 if let Some(var_name) = var.strip_prefix('?') {
198 if let Some(existing) = binding.get(var_name) {
199 let new_term = Term::from(quad.subject().clone());
200 if format!("{:?}", existing) != format!("{:?}", new_term) {
201 compatible = false;
202 }
203 } else {
204 new_binding
205 .bind(var_name.to_string(), Term::from(quad.subject().clone()));
206 }
207 }
208 }
209
210 if compatible {
212 if let Some(var) = &pattern.predicate {
213 if let Some(var_name) = var.strip_prefix('?') {
214 if let Some(existing) = binding.get(var_name) {
215 let new_term = Term::from(quad.predicate().clone());
216 if format!("{:?}", existing) != format!("{:?}", new_term) {
217 compatible = false;
218 }
219 } else {
220 new_binding.bind(
221 var_name.to_string(),
222 Term::from(quad.predicate().clone()),
223 );
224 }
225 }
226 }
227 }
228
229 if compatible {
231 if let Some(var) = &pattern.object {
232 if let Some(var_name) = var.strip_prefix('?') {
233 if let Some(existing) = binding.get(var_name) {
234 let new_term = Term::from(quad.object().clone());
235 if format!("{:?}", existing) != format!("{:?}", new_term) {
236 compatible = false;
237 }
238 } else {
239 new_binding
240 .bind(var_name.to_string(), Term::from(quad.object().clone()));
241 }
242 }
243 }
244 }
245
246 if compatible {
247 new_results.push(new_binding);
248 extended = true;
249 }
250 }
251 }
252
253 if !extended {
255 new_results.push(binding);
256 }
257 }
258
259 Ok(new_results)
260}
261
262pub fn extract_union_groups(sparql: &str) -> Result<Option<UnionGroup>> {
264 if !has_union(sparql) {
265 return Ok(None);
266 }
267
268 if let Some((where_open, where_close)) = super::query_locator::locate_where_group(sparql) {
269 let content = &sparql[where_open + 1..where_close];
270
271 let mut branches = Vec::new();
273 let mut current_branch = String::new();
274
275 let mut pos = 0;
276 while pos < content.len() {
277 if let Some(union_pos) = content[pos..].to_uppercase().find(" UNION ") {
278 let abs_pos = pos + union_pos;
279 current_branch.push_str(&content[pos..abs_pos]);
280
281 if let Some(branch) = parse_union_branch(¤t_branch)? {
283 branches.push(branch);
284 }
285
286 current_branch.clear();
287 pos = abs_pos + 7; } else {
289 current_branch.push_str(&content[pos..]);
291 break;
292 }
293 }
294
295 if !current_branch.trim().is_empty() {
297 if let Some(branch) = parse_union_branch(¤t_branch)? {
298 branches.push(branch);
299 }
300 }
301
302 if !branches.is_empty() {
303 return Ok(Some(UnionGroup { branches }));
304 }
305 }
306
307 Ok(None)
308}
309
310pub fn parse_union_branch(branch_text: &str) -> Result<Option<Vec<PatternGroup>>> {
312 let branch_text = branch_text.trim();
313
314 let pattern_text = if branch_text.starts_with('{') && branch_text.ends_with('}') {
316 &branch_text[1..branch_text.len() - 1]
317 } else {
318 branch_text
319 };
320
321 let mut groups = Vec::new();
322
323 if pattern_text.to_uppercase().contains("OPTIONAL") {
325 let mut pos = 0;
327 let mut required_patterns = Vec::new();
328
329 while pos < pattern_text.len() {
330 if let Some(opt_pos) = pattern_text[pos..].to_uppercase().find("OPTIONAL") {
331 let abs_pos = pos + opt_pos;
332
333 let before_optional = &pattern_text[pos..abs_pos];
335 if let Some(req_pattern) = parse_simple_pattern(before_optional) {
336 required_patterns.push(req_pattern);
337 }
338
339 let after_optional = &pattern_text[abs_pos + 8..];
341 if let Some(opt_brace) = after_optional.find('{') {
342 if let Some(opt_end) = find_matching_brace(after_optional, opt_brace) {
343 let optional_content = &after_optional[opt_brace + 1..opt_end];
344
345 if let Some(opt_pattern) = parse_simple_pattern(optional_content) {
346 groups.push(PatternGroup {
347 patterns: vec![opt_pattern],
348 optional: true,
349 });
350 }
351
352 pos = abs_pos + 8 + opt_end + 1;
353 } else {
354 break;
355 }
356 } else {
357 break;
358 }
359 } else {
360 if let Some(req_pattern) = parse_simple_pattern(&pattern_text[pos..]) {
362 required_patterns.push(req_pattern);
363 }
364 break;
365 }
366 }
367
368 if !required_patterns.is_empty() {
369 groups.push(PatternGroup {
370 patterns: required_patterns,
371 optional: false,
372 });
373 }
374 } else {
375 if let Some(pattern) = parse_simple_pattern(pattern_text) {
377 groups.push(PatternGroup {
378 patterns: vec![pattern],
379 optional: false,
380 });
381 }
382 }
383
384 if groups.is_empty() {
385 Ok(None)
386 } else {
387 Ok(Some(groups))
388 }
389}
390
391pub fn execute_union_query_placeholder() {
394 }