Skip to main content

oxirs_arq/
values_clause.rs

1/// SPARQL 1.1 VALUES clause (inline data) implementation.
2///
3/// The VALUES clause provides a mechanism for injecting a set of bindings
4/// directly into a query without needing to retrieve them from a dataset.
5use std::collections::HashMap;
6
7use thiserror::Error;
8
9// ── Error type ────────────────────────────────────────────────────────────────
10
11/// Errors that can occur when working with a VALUES clause.
12#[derive(Debug, Clone, PartialEq, Eq, Error)]
13pub enum ValuesError {
14    /// The number of values in a row did not match the number of declared variables.
15    #[error("column count mismatch: expected {expected}, got {got}")]
16    ColumnCountMismatch { expected: usize, got: usize },
17}
18
19// ── Core data structures ──────────────────────────────────────────────────────
20
21/// A single row of VALUES data.  `None` represents the `UNDEF` keyword.
22#[derive(Debug, Clone, PartialEq, Eq)]
23pub struct ValuesRow(pub Vec<Option<String>>);
24
25impl ValuesRow {
26    /// Create a new row from a vec of optional values.
27    pub fn new(values: Vec<Option<String>>) -> Self {
28        Self(values)
29    }
30
31    /// Return the value at position `idx`, or `None` if out of range.
32    pub fn get(&self, idx: usize) -> Option<&Option<String>> {
33        self.0.get(idx)
34    }
35
36    /// Number of values (columns) in the row.
37    pub fn len(&self) -> usize {
38        self.0.len()
39    }
40
41    /// Returns `true` when the row contains no values.
42    pub fn is_empty(&self) -> bool {
43        self.0.is_empty()
44    }
45
46    /// Returns `true` when the value at position `idx` is `UNDEF` (i.e. `None`).
47    pub fn is_undef(&self, idx: usize) -> bool {
48        matches!(self.0.get(idx), Some(None))
49    }
50}
51
52/// A single variable binding produced by expanding a VALUES row.
53#[derive(Debug, Clone, PartialEq, Eq)]
54pub struct BindingResult {
55    /// Variable name (without the `?` sigil).
56    pub var: String,
57    /// Bound value, or `None` for UNDEF.
58    pub value: Option<String>,
59}
60
61/// The result of expanding all rows in a VALUES clause into per-variable bindings.
62#[derive(Debug, Clone, PartialEq, Eq)]
63pub struct ValuesExpansion {
64    /// One entry per row; each inner `Vec` has one `BindingResult` per variable.
65    pub rows: Vec<Vec<BindingResult>>,
66}
67
68// ── ValuesClause ──────────────────────────────────────────────────────────────
69
70/// Represents a `VALUES` clause from a SPARQL 1.1 query.
71///
72/// ```sparql
73/// VALUES (?x ?y) {
74///   ("a" "b")
75///   ("c" UNDEF)
76/// }
77/// ```
78#[derive(Debug, Clone, PartialEq, Eq)]
79pub struct ValuesClause {
80    /// The declared variable names (without the `?` sigil).
81    pub variables: Vec<String>,
82    /// The rows of data.
83    pub rows: Vec<ValuesRow>,
84}
85
86impl ValuesClause {
87    /// Create an empty VALUES clause with the given variable list.
88    pub fn new(variables: Vec<String>) -> Self {
89        Self {
90            variables,
91            rows: Vec::new(),
92        }
93    }
94
95    /// Append a row to the clause, validating the column count.
96    pub fn add_row(&mut self, row: ValuesRow) -> Result<(), ValuesError> {
97        let expected = self.variables.len();
98        let got = row.len();
99        if got != expected {
100            return Err(ValuesError::ColumnCountMismatch { expected, got });
101        }
102        self.rows.push(row);
103        Ok(())
104    }
105
106    /// Expand all rows into a flat `ValuesExpansion` (each row → set of `BindingResult`s).
107    pub fn expand(&self) -> ValuesExpansion {
108        let rows = self
109            .rows
110            .iter()
111            .map(|row| {
112                self.variables
113                    .iter()
114                    .enumerate()
115                    .map(|(i, var)| BindingResult {
116                        var: var.clone(),
117                        value: row.0.get(i).and_then(|v| v.clone()),
118                    })
119                    .collect()
120            })
121            .collect();
122        ValuesExpansion { rows }
123    }
124
125    /// Perform a VALUES join: for each existing binding map, try to extend it
126    /// with each VALUES row.  Rows whose defined values conflict with the
127    /// existing bindings are dropped.  UNDEF values never conflict.
128    pub fn join_with(&self, bindings: &[HashMap<String, String>]) -> Vec<HashMap<String, String>> {
129        if bindings.is_empty() {
130            // With no incoming bindings the VALUES rows become the full solution.
131            return self.expand_as_maps();
132        }
133        let mut out = Vec::new();
134        for binding in bindings {
135            for row in &self.rows {
136                if let Some(merged) = self.try_merge(binding, row) {
137                    out.push(merged);
138                }
139            }
140        }
141        out
142    }
143
144    /// Number of rows.
145    pub fn row_count(&self) -> usize {
146        self.rows.len()
147    }
148
149    /// Number of declared variables.
150    pub fn variable_count(&self) -> usize {
151        self.variables.len()
152    }
153
154    /// Returns `true` when there are no rows.
155    pub fn is_empty(&self) -> bool {
156        self.rows.is_empty()
157    }
158
159    /// Return a new `ValuesClause` with rows removed where every value is `UNDEF`.
160    pub fn filter_undef(&self) -> Self {
161        let rows = self
162            .rows
163            .iter()
164            .filter(|row| row.0.iter().any(|v| v.is_some()))
165            .cloned()
166            .collect();
167        Self {
168            variables: self.variables.clone(),
169            rows,
170        }
171    }
172
173    /// Return a new `ValuesClause` keeping only the variables listed in `vars`.
174    /// The column order follows the order of `vars`.
175    pub fn project(&self, vars: &[&str]) -> Self {
176        // Build index map: var name → original column index
177        let indices: Vec<usize> = vars
178            .iter()
179            .filter_map(|v| self.variables.iter().position(|x| x == v))
180            .collect();
181        let new_variables: Vec<String> =
182            indices.iter().map(|&i| self.variables[i].clone()).collect();
183        let rows = self
184            .rows
185            .iter()
186            .map(|row| {
187                let values = indices
188                    .iter()
189                    .map(|&i| row.0.get(i).and_then(|v| v.clone()))
190                    .collect();
191                ValuesRow(values)
192            })
193            .collect();
194        Self {
195            variables: new_variables,
196            rows,
197        }
198    }
199
200    // ── Private helpers ────────────────────────────────────────────────────────
201
202    /// Expand all rows as `HashMap<variable, value>` (UNDEF omitted).
203    fn expand_as_maps(&self) -> Vec<HashMap<String, String>> {
204        self.rows
205            .iter()
206            .map(|row| {
207                self.variables
208                    .iter()
209                    .enumerate()
210                    .filter_map(|(i, var)| {
211                        row.0
212                            .get(i)
213                            .and_then(|v| v.as_ref())
214                            .map(|v| (var.clone(), v.clone()))
215                    })
216                    .collect()
217            })
218            .collect()
219    }
220
221    /// Try to merge an incoming `binding` with a VALUES `row`.
222    ///
223    /// Returns `None` if there is a conflict (same variable, different value).
224    /// UNDEF (`None`) in the row is treated as compatible with any binding.
225    fn try_merge(
226        &self,
227        binding: &HashMap<String, String>,
228        row: &ValuesRow,
229    ) -> Option<HashMap<String, String>> {
230        let mut merged = binding.clone();
231        for (i, var) in self.variables.iter().enumerate() {
232            if let Some(Some(val)) = row.0.get(i) {
233                if let Some(existing) = merged.get(var) {
234                    if existing != val {
235                        return None; // conflict
236                    }
237                } else {
238                    merged.insert(var.clone(), val.clone());
239                }
240            }
241        }
242        Some(merged)
243    }
244}
245
246// ── Tests ─────────────────────────────────────────────────────────────────────
247
248#[cfg(test)]
249mod tests {
250    use super::*;
251
252    fn make_row(values: &[Option<&str>]) -> ValuesRow {
253        ValuesRow::new(values.iter().map(|v| v.map(String::from)).collect())
254    }
255
256    // ── ValuesRow tests ────────────────────────────────────────────────────────
257
258    #[test]
259    fn test_row_new_and_len() {
260        let r = make_row(&[Some("a"), None, Some("c")]);
261        assert_eq!(r.len(), 3);
262    }
263
264    #[test]
265    fn test_row_is_empty_false() {
266        let r = make_row(&[Some("x")]);
267        assert!(!r.is_empty());
268    }
269
270    #[test]
271    fn test_row_is_empty_true() {
272        let r = ValuesRow::new(vec![]);
273        assert!(r.is_empty());
274    }
275
276    #[test]
277    fn test_row_get_some() {
278        let r = make_row(&[Some("hello"), None]);
279        assert_eq!(r.get(0), Some(&Some("hello".to_string())));
280    }
281
282    #[test]
283    fn test_row_get_none_value() {
284        let r = make_row(&[None]);
285        assert_eq!(r.get(0), Some(&None));
286    }
287
288    #[test]
289    fn test_row_get_out_of_range() {
290        let r = make_row(&[Some("x")]);
291        assert_eq!(r.get(5), None);
292    }
293
294    #[test]
295    fn test_row_is_undef_true() {
296        let r = make_row(&[Some("v"), None]);
297        assert!(r.is_undef(1));
298    }
299
300    #[test]
301    fn test_row_is_undef_false() {
302        let r = make_row(&[Some("v"), None]);
303        assert!(!r.is_undef(0));
304    }
305
306    #[test]
307    fn test_row_is_undef_out_of_range() {
308        let r = make_row(&[Some("v")]);
309        assert!(!r.is_undef(99));
310    }
311
312    // ── ValuesClause construction tests ───────────────────────────────────────
313
314    #[test]
315    fn test_new_empty_clause() {
316        let vc = ValuesClause::new(vec!["x".into()]);
317        assert_eq!(vc.variable_count(), 1);
318        assert!(vc.is_empty());
319    }
320
321    #[test]
322    fn test_add_row_success() {
323        let mut vc = ValuesClause::new(vec!["x".into(), "y".into()]);
324        assert!(vc.add_row(make_row(&[Some("1"), Some("2")])).is_ok());
325        assert_eq!(vc.row_count(), 1);
326    }
327
328    #[test]
329    fn test_add_row_column_mismatch_too_few() {
330        let mut vc = ValuesClause::new(vec!["x".into(), "y".into()]);
331        let err = vc.add_row(make_row(&[Some("1")])).unwrap_err();
332        assert!(matches!(
333            err,
334            ValuesError::ColumnCountMismatch {
335                expected: 2,
336                got: 1
337            }
338        ));
339    }
340
341    #[test]
342    fn test_add_row_column_mismatch_too_many() {
343        let mut vc = ValuesClause::new(vec!["x".into()]);
344        let err = vc.add_row(make_row(&[Some("1"), Some("2")])).unwrap_err();
345        assert!(matches!(
346            err,
347            ValuesError::ColumnCountMismatch {
348                expected: 1,
349                got: 2
350            }
351        ));
352    }
353
354    #[test]
355    fn test_add_multiple_rows() {
356        let mut vc = ValuesClause::new(vec!["x".into()]);
357        vc.add_row(make_row(&[Some("a")])).unwrap();
358        vc.add_row(make_row(&[Some("b")])).unwrap();
359        vc.add_row(make_row(&[None])).unwrap();
360        assert_eq!(vc.row_count(), 3);
361    }
362
363    #[test]
364    fn test_variable_count() {
365        let vc = ValuesClause::new(vec!["a".into(), "b".into(), "c".into()]);
366        assert_eq!(vc.variable_count(), 3);
367    }
368
369    #[test]
370    fn test_row_count_zero() {
371        let vc = ValuesClause::new(vec!["x".into()]);
372        assert_eq!(vc.row_count(), 0);
373    }
374
375    // ── expand tests ──────────────────────────────────────────────────────────
376
377    #[test]
378    fn test_expand_single_variable() {
379        let mut vc = ValuesClause::new(vec!["x".into()]);
380        vc.add_row(make_row(&[Some("hello")])).unwrap();
381        let exp = vc.expand();
382        assert_eq!(exp.rows.len(), 1);
383        assert_eq!(exp.rows[0][0].var, "x");
384        assert_eq!(exp.rows[0][0].value, Some("hello".to_string()));
385    }
386
387    #[test]
388    fn test_expand_multiple_variables() {
389        let mut vc = ValuesClause::new(vec!["x".into(), "y".into()]);
390        vc.add_row(make_row(&[Some("a"), Some("b")])).unwrap();
391        let exp = vc.expand();
392        assert_eq!(exp.rows[0][0].var, "x");
393        assert_eq!(exp.rows[0][1].var, "y");
394        assert_eq!(exp.rows[0][1].value, Some("b".to_string()));
395    }
396
397    #[test]
398    fn test_expand_undef_value() {
399        let mut vc = ValuesClause::new(vec!["x".into(), "y".into()]);
400        vc.add_row(make_row(&[Some("a"), None])).unwrap();
401        let exp = vc.expand();
402        assert_eq!(exp.rows[0][1].value, None);
403    }
404
405    #[test]
406    fn test_expand_multiple_rows() {
407        let mut vc = ValuesClause::new(vec!["x".into()]);
408        vc.add_row(make_row(&[Some("1")])).unwrap();
409        vc.add_row(make_row(&[Some("2")])).unwrap();
410        let exp = vc.expand();
411        assert_eq!(exp.rows.len(), 2);
412        assert_eq!(exp.rows[1][0].value, Some("2".to_string()));
413    }
414
415    #[test]
416    fn test_expand_empty_clause() {
417        let vc = ValuesClause::new(vec!["x".into()]);
418        let exp = vc.expand();
419        assert!(exp.rows.is_empty());
420    }
421
422    // ── join_with tests ───────────────────────────────────────────────────────
423
424    #[test]
425    fn test_join_with_empty_bindings_returns_values_rows() {
426        let mut vc = ValuesClause::new(vec!["x".into()]);
427        vc.add_row(make_row(&[Some("1")])).unwrap();
428        vc.add_row(make_row(&[Some("2")])).unwrap();
429        let result = vc.join_with(&[]);
430        assert_eq!(result.len(), 2);
431    }
432
433    #[test]
434    fn test_join_with_compatible_bindings() {
435        let mut vc = ValuesClause::new(vec!["y".into()]);
436        vc.add_row(make_row(&[Some("b")])).unwrap();
437
438        let bindings = vec![{
439            let mut m = HashMap::new();
440            m.insert("x".to_string(), "a".to_string());
441            m
442        }];
443
444        let result = vc.join_with(&bindings);
445        assert_eq!(result.len(), 1);
446        assert_eq!(result[0].get("x").map(String::as_str), Some("a"));
447        assert_eq!(result[0].get("y").map(String::as_str), Some("b"));
448    }
449
450    #[test]
451    fn test_join_with_conflict_drops_row() {
452        let mut vc = ValuesClause::new(vec!["x".into()]);
453        vc.add_row(make_row(&[Some("wrong")])).unwrap();
454
455        let bindings = vec![{
456            let mut m = HashMap::new();
457            m.insert("x".to_string(), "correct".to_string());
458            m
459        }];
460
461        let result = vc.join_with(&bindings);
462        assert!(result.is_empty());
463    }
464
465    #[test]
466    fn test_join_with_undef_is_compatible() {
467        let mut vc = ValuesClause::new(vec!["x".into()]);
468        vc.add_row(make_row(&[None])).unwrap(); // UNDEF
469
470        let bindings = vec![{
471            let mut m = HashMap::new();
472            m.insert("x".to_string(), "anything".to_string());
473            m
474        }];
475
476        let result = vc.join_with(&bindings);
477        assert_eq!(result.len(), 1);
478        assert_eq!(result[0].get("x").map(String::as_str), Some("anything"));
479    }
480
481    #[test]
482    fn test_join_with_same_value_no_conflict() {
483        let mut vc = ValuesClause::new(vec!["x".into()]);
484        vc.add_row(make_row(&[Some("same")])).unwrap();
485
486        let bindings = vec![{
487            let mut m = HashMap::new();
488            m.insert("x".to_string(), "same".to_string());
489            m
490        }];
491
492        let result = vc.join_with(&bindings);
493        assert_eq!(result.len(), 1);
494    }
495
496    #[test]
497    fn test_join_cross_product_multiple_rows_multiple_bindings() {
498        let mut vc = ValuesClause::new(vec!["y".into()]);
499        vc.add_row(make_row(&[Some("1")])).unwrap();
500        vc.add_row(make_row(&[Some("2")])).unwrap();
501
502        let bindings: Vec<HashMap<String, String>> = vec![
503            {
504                let mut m = HashMap::new();
505                m.insert("x".to_string(), "a".to_string());
506                m
507            },
508            {
509                let mut m = HashMap::new();
510                m.insert("x".to_string(), "b".to_string());
511                m
512            },
513        ];
514
515        let result = vc.join_with(&bindings);
516        assert_eq!(result.len(), 4); // 2 bindings × 2 rows
517    }
518
519    // ── filter_undef tests ────────────────────────────────────────────────────
520
521    #[test]
522    fn test_filter_undef_removes_all_undef_row() {
523        let mut vc = ValuesClause::new(vec!["x".into(), "y".into()]);
524        vc.add_row(make_row(&[Some("a"), Some("b")])).unwrap();
525        vc.add_row(make_row(&[None, None])).unwrap();
526        let filtered = vc.filter_undef();
527        assert_eq!(filtered.row_count(), 1);
528    }
529
530    #[test]
531    fn test_filter_undef_keeps_partial_undef() {
532        let mut vc = ValuesClause::new(vec!["x".into(), "y".into()]);
533        vc.add_row(make_row(&[Some("a"), None])).unwrap(); // partial UNDEF → kept
534        let filtered = vc.filter_undef();
535        assert_eq!(filtered.row_count(), 1);
536    }
537
538    #[test]
539    fn test_filter_undef_empty_clause() {
540        let vc = ValuesClause::new(vec!["x".into()]);
541        let filtered = vc.filter_undef();
542        assert!(filtered.is_empty());
543    }
544
545    // ── project tests ─────────────────────────────────────────────────────────
546
547    #[test]
548    fn test_project_single_variable() {
549        let mut vc = ValuesClause::new(vec!["x".into(), "y".into()]);
550        vc.add_row(make_row(&[Some("a"), Some("b")])).unwrap();
551        let projected = vc.project(&["x"]);
552        assert_eq!(projected.variable_count(), 1);
553        assert_eq!(projected.variables[0], "x");
554        assert_eq!(projected.rows[0].0[0], Some("a".to_string()));
555    }
556
557    #[test]
558    fn test_project_all_variables() {
559        let mut vc = ValuesClause::new(vec!["x".into(), "y".into()]);
560        vc.add_row(make_row(&[Some("a"), Some("b")])).unwrap();
561        let projected = vc.project(&["x", "y"]);
562        assert_eq!(projected.variable_count(), 2);
563    }
564
565    #[test]
566    fn test_project_reorder_variables() {
567        let mut vc = ValuesClause::new(vec!["x".into(), "y".into(), "z".into()]);
568        vc.add_row(make_row(&[Some("a"), Some("b"), Some("c")]))
569            .unwrap();
570        let projected = vc.project(&["z", "x"]);
571        assert_eq!(projected.variables[0], "z");
572        assert_eq!(projected.variables[1], "x");
573        assert_eq!(projected.rows[0].0[0], Some("c".to_string()));
574        assert_eq!(projected.rows[0].0[1], Some("a".to_string()));
575    }
576
577    #[test]
578    fn test_project_unknown_variable_omitted() {
579        let mut vc = ValuesClause::new(vec!["x".into()]);
580        vc.add_row(make_row(&[Some("a")])).unwrap();
581        let projected = vc.project(&["unknown"]);
582        assert_eq!(projected.variable_count(), 0);
583    }
584
585    #[test]
586    fn test_project_preserves_undef() {
587        let mut vc = ValuesClause::new(vec!["x".into(), "y".into()]);
588        vc.add_row(make_row(&[Some("a"), None])).unwrap();
589        let projected = vc.project(&["y"]);
590        assert_eq!(projected.rows[0].0[0], None);
591    }
592
593    // ── edge-case / integration tests ─────────────────────────────────────────
594
595    #[test]
596    fn test_values_error_display() {
597        let e = ValuesError::ColumnCountMismatch {
598            expected: 3,
599            got: 1,
600        };
601        let msg = e.to_string();
602        assert!(msg.contains("3"));
603        assert!(msg.contains("1"));
604    }
605
606    #[test]
607    fn test_single_variable_full_cycle() {
608        let mut vc = ValuesClause::new(vec!["name".into()]);
609        vc.add_row(make_row(&[Some("Alice")])).unwrap();
610        vc.add_row(make_row(&[Some("Bob")])).unwrap();
611        vc.add_row(make_row(&[None])).unwrap(); // UNDEF
612
613        assert_eq!(vc.row_count(), 3);
614        let exp = vc.expand();
615        assert_eq!(exp.rows[2][0].value, None);
616
617        let filtered = vc.filter_undef();
618        assert_eq!(filtered.row_count(), 2);
619
620        let projected = vc.project(&["name"]);
621        assert_eq!(projected.variable_count(), 1);
622    }
623
624    #[test]
625    fn test_join_with_no_variable_overlap_full_cross_product() {
626        let mut vc = ValuesClause::new(vec!["b".into()]);
627        vc.add_row(make_row(&[Some("x")])).unwrap();
628        vc.add_row(make_row(&[Some("y")])).unwrap();
629
630        let bindings = vec![{
631            let mut m = HashMap::new();
632            m.insert("a".to_string(), "1".to_string());
633            m
634        }];
635
636        let result = vc.join_with(&bindings);
637        // 1 binding × 2 rows = 2 merged rows
638        assert_eq!(result.len(), 2);
639        for r in &result {
640            assert!(r.contains_key("a"));
641            assert!(r.contains_key("b"));
642        }
643    }
644
645    #[test]
646    fn test_all_undef_row_filtered() {
647        let mut vc = ValuesClause::new(vec!["x".into()]);
648        vc.add_row(make_row(&[None])).unwrap();
649        let filtered = vc.filter_undef();
650        assert!(filtered.is_empty());
651    }
652}