Skip to main content

oxirs_arq/
interactive_query_builder.rs

1//! Interactive SPARQL Query Builder
2//!
3//! Provides a fluent, type-safe API for constructing SPARQL queries programmatically.
4//! Supports SELECT, ASK, CONSTRUCT, DESCRIBE queries with comprehensive pattern building.
5
6use crate::algebra::{
7    Aggregate, Algebra, Expression, GroupCondition, Iri, Literal, OrderCondition, Term,
8    TriplePattern, Variable,
9};
10use anyhow::{anyhow, Result};
11use std::collections::HashMap;
12
13/// Interactive query builder for SPARQL queries
14#[derive(Debug, Clone)]
15pub struct InteractiveQueryBuilder {
16    query_type: Option<QueryType>,
17    variables: Vec<Variable>,
18    patterns: Vec<PatternBuilder>,
19    filters: Vec<Expression>,
20    optional_patterns: Vec<Vec<PatternBuilder>>,
21    unions: Vec<Vec<PatternBuilder>>,
22    bindings: Vec<(Variable, Expression)>,
23    order_by: Vec<OrderCondition>,
24    group_by: Vec<GroupCondition>,
25    aggregates: Vec<(Variable, Aggregate)>,
26    having: Option<Expression>,
27    limit: Option<usize>,
28    offset: Option<usize>,
29    distinct: bool,
30    reduced: bool,
31    prefixes: HashMap<String, String>,
32}
33
34/// Query type
35#[derive(Debug, Clone, Copy, PartialEq, Eq)]
36pub enum QueryType {
37    Select,
38    Ask,
39    Construct,
40    Describe,
41}
42
43/// Pattern builder for triple patterns
44#[derive(Debug, Clone)]
45pub struct PatternBuilder {
46    subject: Option<Term>,
47    predicate: Option<Term>,
48    object: Option<Term>,
49}
50
51impl InteractiveQueryBuilder {
52    /// Create a new query builder
53    pub fn new() -> Self {
54        Self {
55            query_type: None,
56            variables: Vec::new(),
57            patterns: Vec::new(),
58            filters: Vec::new(),
59            optional_patterns: Vec::new(),
60            unions: Vec::new(),
61            bindings: Vec::new(),
62            order_by: Vec::new(),
63            group_by: Vec::new(),
64            aggregates: Vec::new(),
65            having: None,
66            limit: None,
67            offset: None,
68            distinct: false,
69            reduced: false,
70            prefixes: HashMap::new(),
71        }
72    }
73
74    /// Start building a SELECT query
75    pub fn select(mut self, variables: Vec<&str>) -> Result<Self> {
76        self.query_type = Some(QueryType::Select);
77        self.variables = variables
78            .into_iter()
79            .map(Variable::new)
80            .collect::<std::result::Result<Vec<_>, _>>()?;
81        Ok(self)
82    }
83
84    /// Start building a SELECT * query
85    pub fn select_all(mut self) -> Self {
86        self.query_type = Some(QueryType::Select);
87        self.variables = Vec::new(); // Empty means SELECT *
88        self
89    }
90
91    /// Start building an ASK query
92    pub fn ask(mut self) -> Self {
93        self.query_type = Some(QueryType::Ask);
94        self
95    }
96
97    /// Start building a CONSTRUCT query
98    pub fn construct(mut self, variables: Vec<&str>) -> Result<Self> {
99        self.query_type = Some(QueryType::Construct);
100        self.variables = variables
101            .into_iter()
102            .map(Variable::new)
103            .collect::<std::result::Result<Vec<_>, _>>()?;
104        Ok(self)
105    }
106
107    /// Start building a DESCRIBE query
108    pub fn describe(mut self, resources: Vec<&str>) -> Result<Self> {
109        self.query_type = Some(QueryType::Describe);
110        self.variables = resources
111            .into_iter()
112            .map(Variable::new)
113            .collect::<std::result::Result<Vec<_>, _>>()?;
114        Ok(self)
115    }
116
117    /// Add a WHERE pattern
118    pub fn r#where(mut self, pattern: PatternBuilder) -> Self {
119        self.patterns.push(pattern);
120        self
121    }
122
123    /// Add multiple WHERE patterns
124    pub fn where_all(mut self, patterns: Vec<PatternBuilder>) -> Self {
125        self.patterns.extend(patterns);
126        self
127    }
128
129    /// Add a FILTER condition
130    pub fn filter(mut self, condition: Expression) -> Self {
131        self.filters.push(condition);
132        self
133    }
134
135    /// Add an OPTIONAL pattern
136    pub fn optional(mut self, patterns: Vec<PatternBuilder>) -> Self {
137        self.optional_patterns.push(patterns);
138        self
139    }
140
141    /// Add a UNION pattern
142    pub fn union(mut self, patterns: Vec<PatternBuilder>) -> Self {
143        self.unions.push(patterns);
144        self
145    }
146
147    /// Add a BIND clause
148    pub fn bind(mut self, var: &str, expr: Expression) -> Result<Self> {
149        self.bindings.push((Variable::new(var)?, expr));
150        Ok(self)
151    }
152
153    /// Add ORDER BY clause
154    pub fn order_by(mut self, conditions: Vec<OrderCondition>) -> Self {
155        self.order_by = conditions;
156        self
157    }
158
159    /// Add GROUP BY clause
160    pub fn group_by(mut self, conditions: Vec<GroupCondition>) -> Self {
161        self.group_by = conditions;
162        self
163    }
164
165    /// Add HAVING clause
166    pub fn having(mut self, condition: Expression) -> Self {
167        self.having = Some(condition);
168        self
169    }
170
171    /// Add COUNT aggregate
172    pub fn count(mut self, var: &str, expr: Option<Expression>, distinct: bool) -> Result<Self> {
173        let variable = Variable::new(var)?;
174        self.aggregates
175            .push((variable, Aggregate::Count { distinct, expr }));
176        Ok(self)
177    }
178
179    /// Add SUM aggregate
180    pub fn sum(mut self, var: &str, expr: Expression, distinct: bool) -> Result<Self> {
181        let variable = Variable::new(var)?;
182        self.aggregates
183            .push((variable, Aggregate::Sum { distinct, expr }));
184        Ok(self)
185    }
186
187    /// Add MIN aggregate
188    pub fn min(mut self, var: &str, expr: Expression, distinct: bool) -> Result<Self> {
189        let variable = Variable::new(var)?;
190        self.aggregates
191            .push((variable, Aggregate::Min { distinct, expr }));
192        Ok(self)
193    }
194
195    /// Add MAX aggregate
196    pub fn max(mut self, var: &str, expr: Expression, distinct: bool) -> Result<Self> {
197        let variable = Variable::new(var)?;
198        self.aggregates
199            .push((variable, Aggregate::Max { distinct, expr }));
200        Ok(self)
201    }
202
203    /// Add AVG aggregate
204    pub fn avg(mut self, var: &str, expr: Expression, distinct: bool) -> Result<Self> {
205        let variable = Variable::new(var)?;
206        self.aggregates
207            .push((variable, Aggregate::Avg { distinct, expr }));
208        Ok(self)
209    }
210
211    /// Add SAMPLE aggregate
212    pub fn sample(mut self, var: &str, expr: Expression, distinct: bool) -> Result<Self> {
213        let variable = Variable::new(var)?;
214        self.aggregates
215            .push((variable, Aggregate::Sample { distinct, expr }));
216        Ok(self)
217    }
218
219    /// Add GROUP_CONCAT aggregate
220    pub fn group_concat(
221        mut self,
222        var: &str,
223        expr: Expression,
224        distinct: bool,
225        separator: Option<String>,
226    ) -> Result<Self> {
227        let variable = Variable::new(var)?;
228        self.aggregates.push((
229            variable,
230            Aggregate::GroupConcat {
231                distinct,
232                expr,
233                separator,
234            },
235        ));
236        Ok(self)
237    }
238
239    /// Set LIMIT
240    pub fn limit(mut self, limit: usize) -> Self {
241        self.limit = Some(limit);
242        self
243    }
244
245    /// Set OFFSET
246    pub fn offset(mut self, offset: usize) -> Self {
247        self.offset = Some(offset);
248        self
249    }
250
251    /// Set DISTINCT modifier
252    pub fn distinct(mut self) -> Self {
253        self.distinct = true;
254        self
255    }
256
257    /// Set REDUCED modifier
258    pub fn reduced(mut self) -> Self {
259        self.reduced = true;
260        self
261    }
262
263    /// Add a prefix declaration
264    pub fn prefix(mut self, prefix: &str, uri: &str) -> Self {
265        self.prefixes.insert(prefix.to_string(), uri.to_string());
266        self
267    }
268
269    /// Build the SPARQL query string
270    pub fn build_string(&self) -> Result<String> {
271        let mut query = String::new();
272
273        // Add prefixes
274        for (prefix, uri) in &self.prefixes {
275            query.push_str(&format!("PREFIX {}: <{}>\n", prefix, uri));
276        }
277        if !self.prefixes.is_empty() {
278            query.push('\n');
279        }
280
281        // Add query type
282        let query_type = self
283            .query_type
284            .ok_or_else(|| anyhow!("Query type not set"))?;
285
286        match query_type {
287            QueryType::Select => {
288                query.push_str("SELECT ");
289                if self.distinct {
290                    query.push_str("DISTINCT ");
291                }
292                if self.reduced {
293                    query.push_str("REDUCED ");
294                }
295                if self.variables.is_empty() {
296                    query.push('*');
297                } else {
298                    for (i, var) in self.variables.iter().enumerate() {
299                        if i > 0 {
300                            query.push(' ');
301                        }
302                        query.push_str(&format!("{}", var));
303                    }
304                }
305                query.push('\n');
306            }
307            QueryType::Ask => {
308                query.push_str("ASK\n");
309            }
310            QueryType::Construct => {
311                query.push_str("CONSTRUCT {\n");
312                for pattern in &self.patterns {
313                    query.push_str(&format!("  {}\n", pattern.to_string()?));
314                }
315                query.push_str("}\n");
316            }
317            QueryType::Describe => {
318                query.push_str("DESCRIBE ");
319                for (i, var) in self.variables.iter().enumerate() {
320                    if i > 0 {
321                        query.push(' ');
322                    }
323                    query.push_str(&format!("{}", var));
324                }
325                query.push('\n');
326            }
327        }
328
329        // Add WHERE clause
330        query.push_str("WHERE {\n");
331
332        // Add patterns
333        for pattern in &self.patterns {
334            query.push_str(&format!("  {} .\n", pattern.to_string()?));
335        }
336
337        // Add FILTERs
338        for filter in &self.filters {
339            query.push_str(&format!("  FILTER ({:?})\n", filter));
340        }
341
342        // Add OPTIONALs
343        for optional in &self.optional_patterns {
344            query.push_str("  OPTIONAL {\n");
345            for pattern in optional {
346                query.push_str(&format!("    {} .\n", pattern.to_string()?));
347            }
348            query.push_str("  }\n");
349        }
350
351        // Add UNIONs
352        if !self.unions.is_empty() {
353            query.push_str("  {\n");
354            for (i, union_patterns) in self.unions.iter().enumerate() {
355                if i > 0 {
356                    query.push_str("  } UNION {\n");
357                }
358                for pattern in union_patterns {
359                    query.push_str(&format!("    {} .\n", pattern.to_string()?));
360                }
361            }
362            query.push_str("  }\n");
363        }
364
365        // Add BINDs
366        for (var, expr) in &self.bindings {
367            query.push_str(&format!("  BIND ({:?} AS {})\n", expr, var));
368        }
369
370        query.push_str("}\n");
371
372        // Add GROUP BY
373        if !self.group_by.is_empty() {
374            query.push_str("GROUP BY ");
375            for (i, condition) in self.group_by.iter().enumerate() {
376                if i > 0 {
377                    query.push(' ');
378                }
379                query.push_str(&format!("({:?})", condition.expr));
380                if let Some(alias) = &condition.alias {
381                    query.push_str(&format!(" AS {}", alias));
382                }
383            }
384            query.push('\n');
385        }
386
387        // Add HAVING
388        if let Some(having) = &self.having {
389            query.push_str(&format!("HAVING ({:?})\n", having));
390        }
391
392        // Add ORDER BY
393        if !self.order_by.is_empty() {
394            query.push_str("ORDER BY ");
395            for (i, condition) in self.order_by.iter().enumerate() {
396                if i > 0 {
397                    query.push(' ');
398                }
399                if condition.ascending {
400                    query.push_str(&format!("ASC({:?})", condition.expr));
401                } else {
402                    query.push_str(&format!("DESC({:?})", condition.expr));
403                }
404            }
405            query.push('\n');
406        }
407
408        // Add LIMIT
409        if let Some(limit) = self.limit {
410            query.push_str(&format!("LIMIT {}\n", limit));
411        }
412
413        // Add OFFSET
414        if let Some(offset) = self.offset {
415            query.push_str(&format!("OFFSET {}\n", offset));
416        }
417
418        Ok(query)
419    }
420
421    /// Build the algebra representation
422    pub fn build_algebra(&self) -> Result<Algebra> {
423        let query_type = self
424            .query_type
425            .ok_or_else(|| anyhow!("Query type not set"))?;
426
427        // Build basic graph pattern from patterns
428        let mut algebra = if !self.patterns.is_empty() {
429            let triple_patterns: Result<Vec<TriplePattern>> = self
430                .patterns
431                .iter()
432                .map(|p| p.to_triple_pattern())
433                .collect();
434            Algebra::Bgp(triple_patterns?)
435        } else {
436            Algebra::Bgp(Vec::new())
437        };
438
439        // Add filters
440        for filter in &self.filters {
441            algebra = Algebra::Filter {
442                pattern: Box::new(algebra),
443                condition: filter.clone(),
444            };
445        }
446
447        // Add optionals
448        for optional in &self.optional_patterns {
449            let optional_patterns: Result<Vec<TriplePattern>> =
450                optional.iter().map(|p| p.to_triple_pattern()).collect();
451            let optional_algebra = Algebra::Bgp(optional_patterns?);
452            algebra = Algebra::LeftJoin {
453                left: Box::new(algebra),
454                right: Box::new(optional_algebra),
455                filter: None,
456            };
457        }
458
459        // Add unions
460        if !self.unions.is_empty() {
461            let mut union_algebra = None;
462            for union_patterns in &self.unions {
463                let patterns: Result<Vec<TriplePattern>> = union_patterns
464                    .iter()
465                    .map(|p| p.to_triple_pattern())
466                    .collect();
467                let union_bgp = Algebra::Bgp(patterns?);
468                union_algebra = Some(match union_algebra {
469                    None => union_bgp,
470                    Some(left) => Algebra::Union {
471                        left: Box::new(left),
472                        right: Box::new(union_bgp),
473                    },
474                });
475            }
476            if let Some(union_alg) = union_algebra {
477                algebra = Algebra::Join {
478                    left: Box::new(algebra),
479                    right: Box::new(union_alg),
480                };
481            }
482        }
483
484        // Add bindings
485        for (var, expr) in &self.bindings {
486            algebra = Algebra::Extend {
487                pattern: Box::new(algebra),
488                variable: var.clone(),
489                expr: expr.clone(),
490            };
491        }
492
493        // Add group by
494        if !self.group_by.is_empty() || !self.aggregates.is_empty() {
495            algebra = Algebra::Group {
496                pattern: Box::new(algebra),
497                variables: self.group_by.clone(),
498                aggregates: self.aggregates.clone(),
499            };
500        }
501
502        // Add having
503        if let Some(having) = &self.having {
504            algebra = Algebra::Having {
505                pattern: Box::new(algebra),
506                condition: having.clone(),
507            };
508        }
509
510        // Add order by
511        if !self.order_by.is_empty() {
512            algebra = Algebra::OrderBy {
513                pattern: Box::new(algebra),
514                conditions: self.order_by.clone(),
515            };
516        }
517
518        // Add projection for SELECT
519        if query_type == QueryType::Select && !self.variables.is_empty() {
520            algebra = Algebra::Project {
521                pattern: Box::new(algebra),
522                variables: self.variables.clone(),
523            };
524        }
525
526        // Add distinct
527        if self.distinct {
528            algebra = Algebra::Distinct {
529                pattern: Box::new(algebra),
530            };
531        }
532
533        // Add reduced
534        if self.reduced {
535            algebra = Algebra::Reduced {
536                pattern: Box::new(algebra),
537            };
538        }
539
540        // Add slice
541        if self.limit.is_some() || self.offset.is_some() {
542            algebra = Algebra::Slice {
543                pattern: Box::new(algebra),
544                offset: self.offset,
545                limit: self.limit,
546            };
547        }
548
549        Ok(algebra)
550    }
551}
552
553impl Default for InteractiveQueryBuilder {
554    fn default() -> Self {
555        Self::new()
556    }
557}
558
559impl PatternBuilder {
560    /// Create a new pattern builder
561    pub fn new() -> Self {
562        Self {
563            subject: None,
564            predicate: None,
565            object: None,
566        }
567    }
568
569    /// Set the subject
570    pub fn subject(mut self, subject: Term) -> Self {
571        self.subject = Some(subject);
572        self
573    }
574
575    /// Set the subject as a variable
576    pub fn subject_var(self, var: &str) -> Result<Self> {
577        Ok(self.subject(Term::Variable(Variable::new(var)?)))
578    }
579
580    /// Set the subject as an IRI
581    pub fn subject_iri(self, iri: &str) -> Result<Self> {
582        Ok(self.subject(Term::Iri(Iri::new(iri)?)))
583    }
584
585    /// Set the predicate
586    pub fn predicate(mut self, predicate: Term) -> Self {
587        self.predicate = Some(predicate);
588        self
589    }
590
591    /// Set the predicate as a variable
592    pub fn predicate_var(self, var: &str) -> Result<Self> {
593        Ok(self.predicate(Term::Variable(Variable::new(var)?)))
594    }
595
596    /// Set the predicate as an IRI
597    pub fn predicate_iri(self, iri: &str) -> Result<Self> {
598        Ok(self.predicate(Term::Iri(Iri::new(iri)?)))
599    }
600
601    /// Set the object
602    pub fn object(mut self, object: Term) -> Self {
603        self.object = Some(object);
604        self
605    }
606
607    /// Set the object as a variable
608    pub fn object_var(self, var: &str) -> Result<Self> {
609        Ok(self.object(Term::Variable(Variable::new(var)?)))
610    }
611
612    /// Set the object as an IRI
613    pub fn object_iri(self, iri: &str) -> Result<Self> {
614        Ok(self.object(Term::Iri(Iri::new(iri)?)))
615    }
616
617    /// Set the object as a literal
618    pub fn object_literal(self, value: &str) -> Self {
619        self.object(Term::Literal(Literal {
620            value: value.to_string(),
621            language: None,
622            datatype: None,
623        }))
624    }
625
626    /// Convert to triple pattern
627    pub fn to_triple_pattern(&self) -> Result<TriplePattern> {
628        Ok(TriplePattern {
629            subject: self
630                .subject
631                .clone()
632                .ok_or_else(|| anyhow!("Subject not set"))?,
633            predicate: self
634                .predicate
635                .clone()
636                .ok_or_else(|| anyhow!("Predicate not set"))?,
637            object: self
638                .object
639                .clone()
640                .ok_or_else(|| anyhow!("Object not set"))?,
641        })
642    }
643
644    /// Convert to string representation
645    pub fn to_string(&self) -> Result<String> {
646        let subject = self
647            .subject
648            .as_ref()
649            .ok_or_else(|| anyhow!("Subject not set"))?;
650        let predicate = self
651            .predicate
652            .as_ref()
653            .ok_or_else(|| anyhow!("Predicate not set"))?;
654        let object = self
655            .object
656            .as_ref()
657            .ok_or_else(|| anyhow!("Object not set"))?;
658
659        Ok(format!("{} {} {}", subject, predicate, object))
660    }
661}
662
663impl Default for PatternBuilder {
664    fn default() -> Self {
665        Self::new()
666    }
667}
668
669/// Helper functions for building common patterns
670pub mod helpers {
671    use super::*;
672
673    /// Create a triple pattern: ?s ?p ?o
674    pub fn triple(s: &str, p: &str, o: &str) -> Result<PatternBuilder> {
675        PatternBuilder::new()
676            .subject_var(s)?
677            .predicate_var(p)?
678            .object_var(o)
679    }
680
681    /// Create a pattern: `?s <predicate> ?o`
682    pub fn triple_with_iri_predicate(s: &str, p: &str, o: &str) -> Result<PatternBuilder> {
683        PatternBuilder::new()
684            .subject_var(s)?
685            .predicate_iri(p)?
686            .object_var(o)
687    }
688
689    /// Create a pattern: `<subject> <predicate> ?o`
690    pub fn triple_with_iri_subject_predicate(s: &str, p: &str, o: &str) -> Result<PatternBuilder> {
691        PatternBuilder::new()
692            .subject_iri(s)?
693            .predicate_iri(p)?
694            .object_var(o)
695    }
696
697    /// Create a pattern with literal object
698    pub fn triple_with_literal(s: &str, p: &str, literal: &str) -> Result<PatternBuilder> {
699        Ok(PatternBuilder::new()
700            .subject_var(s)?
701            .predicate_iri(p)?
702            .object_literal(literal))
703    }
704}
705
706#[cfg(test)]
707mod tests {
708    use super::*;
709
710    #[test]
711    fn test_simple_select() -> Result<()> {
712        let query = InteractiveQueryBuilder::new()
713            .select(vec!["s", "p", "o"])?
714            .r#where(
715                PatternBuilder::new()
716                    .subject_var("s")?
717                    .predicate_var("p")?
718                    .object_var("o")?,
719            )
720            .build_string()?;
721
722        assert!(query.contains("SELECT ?s ?p ?o"));
723        assert!(query.contains("WHERE"));
724        Ok(())
725    }
726
727    #[test]
728    fn test_select_with_filter() -> Result<()> {
729        let builder = InteractiveQueryBuilder::new()
730            .select(vec!["name"])?
731            .r#where(
732                PatternBuilder::new()
733                    .subject_var("person")?
734                    .predicate_iri("http://xmlns.com/foaf/0.1/name")?
735                    .object_var("name")?,
736            )
737            .limit(10);
738
739        let query = builder.build_string()?;
740        assert!(query.contains("SELECT ?name"));
741        assert!(query.contains("LIMIT 10"));
742        Ok(())
743    }
744
745    #[test]
746    fn test_ask_query() -> Result<()> {
747        let query = InteractiveQueryBuilder::new()
748            .ask()
749            .r#where(
750                PatternBuilder::new()
751                    .subject_var("s")?
752                    .predicate_var("p")?
753                    .object_var("o")?,
754            )
755            .build_string()?;
756
757        assert!(query.contains("ASK"));
758        assert!(query.contains("WHERE"));
759        Ok(())
760    }
761
762    #[test]
763    fn test_distinct_modifier() -> Result<()> {
764        let query = InteractiveQueryBuilder::new()
765            .select(vec!["s"])?
766            .distinct()
767            .r#where(
768                PatternBuilder::new()
769                    .subject_var("s")?
770                    .predicate_var("p")?
771                    .object_var("o")?,
772            )
773            .build_string()?;
774
775        assert!(query.contains("SELECT DISTINCT"));
776        Ok(())
777    }
778
779    #[test]
780    fn test_with_prefixes() -> Result<()> {
781        let query = InteractiveQueryBuilder::new()
782            .prefix("foaf", "http://xmlns.com/foaf/0.1/")
783            .prefix("rdf", "http://www.w3.org/1999/02/22-rdf-syntax-ns#")
784            .select(vec!["name"])?
785            .r#where(
786                PatternBuilder::new()
787                    .subject_var("person")?
788                    .predicate_iri("http://xmlns.com/foaf/0.1/name")?
789                    .object_var("name")?,
790            )
791            .build_string()?;
792
793        assert!(query.contains("PREFIX foaf:"));
794        assert!(query.contains("PREFIX rdf:"));
795        Ok(())
796    }
797
798    #[test]
799    fn test_algebra_generation() -> Result<()> {
800        let algebra = InteractiveQueryBuilder::new()
801            .select(vec!["s", "p", "o"])?
802            .r#where(
803                PatternBuilder::new()
804                    .subject_var("s")?
805                    .predicate_var("p")?
806                    .object_var("o")?,
807            )
808            .limit(10)
809            .build_algebra()?;
810
811        // Should have Slice > Project > Bgp structure
812        match algebra {
813            Algebra::Slice { .. } => {}
814            _ => panic!("Expected Slice algebra"),
815        }
816
817        Ok(())
818    }
819
820    #[test]
821    fn test_helper_functions() -> Result<()> {
822        let pattern = helpers::triple("s", "p", "o")?;
823        let triple = pattern.to_triple_pattern()?;
824
825        assert!(matches!(triple.subject, Term::Variable(_)));
826        assert!(matches!(triple.predicate, Term::Variable(_)));
827        assert!(matches!(triple.object, Term::Variable(_)));
828
829        Ok(())
830    }
831
832    #[test]
833    fn test_optional_pattern() -> Result<()> {
834        let query = InteractiveQueryBuilder::new()
835            .select(vec!["name", "email"])?
836            .r#where(
837                PatternBuilder::new()
838                    .subject_var("person")?
839                    .predicate_iri("http://xmlns.com/foaf/0.1/name")?
840                    .object_var("name")?,
841            )
842            .optional(vec![PatternBuilder::new()
843                .subject_var("person")?
844                .predicate_iri("http://xmlns.com/foaf/0.1/mbox")?
845                .object_var("email")?])
846            .build_string()?;
847
848        assert!(query.contains("OPTIONAL"));
849        Ok(())
850    }
851
852    #[test]
853    fn test_limit_offset() -> Result<()> {
854        let query = InteractiveQueryBuilder::new()
855            .select(vec!["s"])?
856            .r#where(
857                PatternBuilder::new()
858                    .subject_var("s")?
859                    .predicate_var("p")?
860                    .object_var("o")?,
861            )
862            .limit(10)
863            .offset(20)
864            .build_string()?;
865
866        assert!(query.contains("LIMIT 10"));
867        assert!(query.contains("OFFSET 20"));
868        Ok(())
869    }
870
871    #[test]
872    fn test_count_aggregate() -> Result<()> {
873        let algebra = InteractiveQueryBuilder::new()
874            .select_all()
875            .r#where(
876                PatternBuilder::new()
877                    .subject_var("s")?
878                    .predicate_var("p")?
879                    .object_var("o")?,
880            )
881            .count("count", None, false)?
882            .build_algebra()?;
883
884        // Check that aggregate is in the algebra
885        match algebra {
886            Algebra::Group { aggregates, .. } => {
887                assert_eq!(aggregates.len(), 1);
888                let (var, agg) = &aggregates[0];
889                assert_eq!(var.name(), "count");
890                matches!(agg, Aggregate::Count { .. });
891            }
892            _ => panic!("Expected Group algebra"),
893        }
894
895        Ok(())
896    }
897
898    #[test]
899    fn test_sum_aggregate() -> Result<()> {
900        let algebra = InteractiveQueryBuilder::new()
901            .select_all()
902            .r#where(
903                PatternBuilder::new()
904                    .subject_var("s")?
905                    .predicate_var("p")?
906                    .object_var("o")?,
907            )
908            .sum(
909                "total",
910                Expression::Variable(Variable::new_unchecked("value")),
911                false,
912            )?
913            .build_algebra()?;
914
915        match algebra {
916            Algebra::Group { aggregates, .. } => {
917                assert_eq!(aggregates.len(), 1);
918                let (var, agg) = &aggregates[0];
919                assert_eq!(var.name(), "total");
920                matches!(agg, Aggregate::Sum { .. });
921            }
922            _ => panic!("Expected Group algebra"),
923        }
924
925        Ok(())
926    }
927
928    #[test]
929    fn test_multiple_aggregates() -> Result<()> {
930        let algebra = InteractiveQueryBuilder::new()
931            .select_all()
932            .r#where(
933                PatternBuilder::new()
934                    .subject_var("s")?
935                    .predicate_var("p")?
936                    .object_var("o")?,
937            )
938            .count("cnt", None, false)?
939            .sum(
940                "total",
941                Expression::Variable(Variable::new_unchecked("value")),
942                false,
943            )?
944            .avg(
945                "avg",
946                Expression::Variable(Variable::new_unchecked("value")),
947                false,
948            )?
949            .build_algebra()?;
950
951        match algebra {
952            Algebra::Group { aggregates, .. } => {
953                assert_eq!(aggregates.len(), 3);
954                assert_eq!(aggregates[0].0.name(), "cnt");
955                assert_eq!(aggregates[1].0.name(), "total");
956                assert_eq!(aggregates[2].0.name(), "avg");
957            }
958            _ => panic!("Expected Group algebra"),
959        }
960
961        Ok(())
962    }
963
964    #[test]
965    fn test_group_concat_aggregate() -> Result<()> {
966        let algebra = InteractiveQueryBuilder::new()
967            .select_all()
968            .r#where(
969                PatternBuilder::new()
970                    .subject_var("s")?
971                    .predicate_var("p")?
972                    .object_var("o")?,
973            )
974            .group_concat(
975                "concat",
976                Expression::Variable(Variable::new_unchecked("name")),
977                false,
978                Some(", ".to_string()),
979            )?
980            .build_algebra()?;
981
982        match algebra {
983            Algebra::Group { aggregates, .. } => {
984                assert_eq!(aggregates.len(), 1);
985                match &aggregates[0].1 {
986                    Aggregate::GroupConcat { separator, .. } => {
987                        assert_eq!(separator.as_deref(), Some(", "));
988                    }
989                    _ => panic!("Expected GroupConcat aggregate"),
990                }
991            }
992            _ => panic!("Expected Group algebra"),
993        }
994
995        Ok(())
996    }
997}