Skip to main content

oxirs_core/query/
sparql_query.rs

1//! SPARQL Query representation
2//!
3//! Extracted and adapted from OxiGraph spargebra with OxiRS enhancements.
4//! Based on W3C SPARQL 1.1 Query specification:
5//! <https://www.w3.org/TR/sparql11-query/>
6
7use super::sparql_algebra::{GraphPattern, TriplePattern};
8use crate::model::{NamedNode, Variable};
9use std::fmt;
10
11/// A parsed [SPARQL query](https://www.w3.org/TR/sparql11-query/).
12#[derive(Eq, PartialEq, Debug, Clone, Hash)]
13#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
14pub enum Query {
15    /// [SELECT](https://www.w3.org/TR/sparql11-query/#select).
16    Select {
17        /// The [query dataset specification](https://www.w3.org/TR/sparql11-query/#specifyingDataset).
18        dataset: Option<QueryDataset>,
19        /// The query selection graph pattern.
20        pattern: GraphPattern,
21        /// The query base IRI.
22        base_iri: Option<String>,
23    },
24    /// [CONSTRUCT](https://www.w3.org/TR/sparql11-query/#construct).
25    Construct {
26        /// The query construction template.
27        template: Vec<TriplePattern>,
28        /// The [query dataset specification](https://www.w3.org/TR/sparql11-query/#specifyingDataset).
29        dataset: Option<QueryDataset>,
30        /// The query selection graph pattern.
31        pattern: GraphPattern,
32        /// The query base IRI.
33        base_iri: Option<String>,
34    },
35    /// [DESCRIBE](https://www.w3.org/TR/sparql11-query/#describe).
36    Describe {
37        /// The [query dataset specification](https://www.w3.org/TR/sparql11-query/#specifyingDataset).
38        dataset: Option<QueryDataset>,
39        /// The query selection graph pattern.
40        pattern: GraphPattern,
41        /// The query base IRI.
42        base_iri: Option<String>,
43    },
44    /// [ASK](https://www.w3.org/TR/sparql11-query/#ask).
45    Ask {
46        /// The [query dataset specification](https://www.w3.org/TR/sparql11-query/#specifyingDataset).
47        dataset: Option<QueryDataset>,
48        /// The query selection graph pattern.
49        pattern: GraphPattern,
50        /// The query base IRI.
51        base_iri: Option<String>,
52    },
53}
54
55impl Query {
56    /// Creates a new SELECT query
57    pub fn select(pattern: GraphPattern) -> Self {
58        Self::Select {
59            dataset: None,
60            pattern,
61            base_iri: None,
62        }
63    }
64
65    /// Creates a new CONSTRUCT query
66    pub fn construct(template: Vec<TriplePattern>, pattern: GraphPattern) -> Self {
67        Self::Construct {
68            template,
69            dataset: None,
70            pattern,
71            base_iri: None,
72        }
73    }
74
75    /// Creates a new ASK query
76    pub fn ask(pattern: GraphPattern) -> Self {
77        Self::Ask {
78            dataset: None,
79            pattern,
80            base_iri: None,
81        }
82    }
83
84    /// Creates a new DESCRIBE query
85    pub fn describe(pattern: GraphPattern) -> Self {
86        Self::Describe {
87            dataset: None,
88            pattern,
89            base_iri: None,
90        }
91    }
92
93    /// Returns the dataset specification for this query
94    pub fn dataset(&self) -> Option<&QueryDataset> {
95        match self {
96            Query::Select { dataset, .. }
97            | Query::Construct { dataset, .. }
98            | Query::Describe { dataset, .. }
99            | Query::Ask { dataset, .. } => dataset.as_ref(),
100        }
101    }
102
103    /// Returns a mutable reference to the dataset specification for this query
104    pub fn dataset_mut(&mut self) -> Option<&mut QueryDataset> {
105        match self {
106            Query::Select { dataset, .. }
107            | Query::Construct { dataset, .. }
108            | Query::Describe { dataset, .. }
109            | Query::Ask { dataset, .. } => dataset.as_mut(),
110        }
111    }
112
113    /// Returns the base IRI for this query
114    pub fn base_iri(&self) -> Option<&str> {
115        match self {
116            Query::Select { base_iri, .. }
117            | Query::Construct { base_iri, .. }
118            | Query::Describe { base_iri, .. }
119            | Query::Ask { base_iri, .. } => base_iri.as_deref(),
120        }
121    }
122
123    /// Returns the graph pattern for this query
124    pub fn pattern(&self) -> &GraphPattern {
125        match self {
126            Query::Select { pattern, .. }
127            | Query::Construct { pattern, .. }
128            | Query::Describe { pattern, .. }
129            | Query::Ask { pattern, .. } => pattern,
130        }
131    }
132
133    /// Sets the base IRI for this query
134    pub fn with_base_iri(mut self, base_iri: impl Into<String>) -> Self {
135        let base_iri = Some(base_iri.into());
136        match &mut self {
137            Query::Select { base_iri: iri, .. }
138            | Query::Construct { base_iri: iri, .. }
139            | Query::Describe { base_iri: iri, .. }
140            | Query::Ask { base_iri: iri, .. } => *iri = base_iri,
141        }
142        self
143    }
144
145    /// Sets the dataset for this query
146    pub fn with_dataset(mut self, dataset: QueryDataset) -> Self {
147        let dataset = Some(dataset);
148        match &mut self {
149            Query::Select { dataset: ds, .. }
150            | Query::Construct { dataset: ds, .. }
151            | Query::Describe { dataset: ds, .. }
152            | Query::Ask { dataset: ds, .. } => *ds = dataset,
153        }
154        self
155    }
156
157    /// Formats using the SPARQL S-Expression syntax
158    pub fn to_sse(&self) -> String {
159        let mut buffer = String::new();
160        self.fmt_sse(&mut buffer)
161            .expect("writing to String should not fail");
162        buffer
163    }
164
165    /// Formats using the SPARQL S-Expression syntax
166    pub fn fmt_sse(&self, f: &mut impl fmt::Write) -> fmt::Result {
167        match self {
168            Self::Select {
169                dataset,
170                pattern,
171                base_iri,
172            } => {
173                if let Some(base_iri) = base_iri {
174                    write!(f, "(base <{base_iri}> ")?;
175                }
176                if let Some(dataset) = dataset {
177                    f.write_str("(dataset ")?;
178                    dataset.fmt_sse(f)?;
179                    f.write_str(" ")?;
180                }
181                pattern.fmt_sse(f)?;
182                if dataset.is_some() {
183                    f.write_str(")")?;
184                }
185                if base_iri.is_some() {
186                    f.write_str(")")?;
187                }
188                Ok(())
189            }
190            Self::Construct {
191                template,
192                dataset,
193                pattern,
194                base_iri,
195            } => {
196                if let Some(base_iri) = base_iri {
197                    write!(f, "(base <{base_iri}> ")?;
198                }
199                f.write_str("(construct (")?;
200                for (i, triple) in template.iter().enumerate() {
201                    if i > 0 {
202                        f.write_str(" ")?;
203                    }
204                    triple.fmt_sse(f)?;
205                }
206                f.write_str(") ")?;
207                if let Some(dataset) = dataset {
208                    f.write_str("(dataset ")?;
209                    dataset.fmt_sse(f)?;
210                    f.write_str(" ")?;
211                }
212                pattern.fmt_sse(f)?;
213                if dataset.is_some() {
214                    f.write_str(")")?;
215                }
216                f.write_str(")")?;
217                if base_iri.is_some() {
218                    f.write_str(")")?;
219                }
220                Ok(())
221            }
222            Self::Describe {
223                dataset,
224                pattern,
225                base_iri,
226            } => {
227                if let Some(base_iri) = base_iri {
228                    write!(f, "(base <{base_iri}> ")?;
229                }
230                f.write_str("(describe ")?;
231                if let Some(dataset) = dataset {
232                    f.write_str("(dataset ")?;
233                    dataset.fmt_sse(f)?;
234                    f.write_str(" ")?;
235                }
236                pattern.fmt_sse(f)?;
237                if dataset.is_some() {
238                    f.write_str(")")?;
239                }
240                f.write_str(")")?;
241                if base_iri.is_some() {
242                    f.write_str(")")?;
243                }
244                Ok(())
245            }
246            Self::Ask {
247                dataset,
248                pattern,
249                base_iri,
250            } => {
251                if let Some(base_iri) = base_iri {
252                    write!(f, "(base <{base_iri}> ")?;
253                }
254                f.write_str("(ask ")?;
255                if let Some(dataset) = dataset {
256                    f.write_str("(dataset ")?;
257                    dataset.fmt_sse(f)?;
258                    f.write_str(" ")?;
259                }
260                pattern.fmt_sse(f)?;
261                if dataset.is_some() {
262                    f.write_str(")")?;
263                }
264                f.write_str(")")?;
265                if base_iri.is_some() {
266                    f.write_str(")")?;
267                }
268                Ok(())
269            }
270        }
271    }
272}
273
274impl fmt::Display for Query {
275    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
276        match self {
277            Self::Select {
278                dataset,
279                pattern,
280                base_iri,
281            } => {
282                if let Some(base_iri) = base_iri {
283                    writeln!(f, "BASE <{base_iri}>")?;
284                }
285                write!(
286                    f,
287                    "{}",
288                    SparqlGraphRootPattern::new(pattern, dataset.as_ref())
289                )
290            }
291            Self::Construct {
292                template,
293                dataset,
294                pattern,
295                base_iri,
296            } => {
297                if let Some(base_iri) = base_iri {
298                    writeln!(f, "BASE <{base_iri}>")?;
299                }
300                f.write_str("CONSTRUCT { ")?;
301                for triple in template {
302                    write!(f, "{triple} . ")?;
303                }
304                f.write_str("}")?;
305                if let Some(dataset) = dataset {
306                    dataset.fmt(f)?;
307                }
308                write!(
309                    f,
310                    " WHERE {{ {} }}",
311                    SparqlGraphRootPattern::new(pattern, None)
312                )
313            }
314            Self::Describe {
315                dataset,
316                pattern,
317                base_iri,
318            } => {
319                if let Some(base_iri) = base_iri {
320                    writeln!(f, "BASE <{base_iri}>")?;
321                }
322                f.write_str("DESCRIBE *")?;
323                if let Some(dataset) = dataset {
324                    dataset.fmt(f)?;
325                }
326                write!(
327                    f,
328                    " WHERE {{ {} }}",
329                    SparqlGraphRootPattern::new(pattern, None)
330                )
331            }
332            Self::Ask {
333                dataset,
334                pattern,
335                base_iri,
336            } => {
337                if let Some(base_iri) = base_iri {
338                    writeln!(f, "BASE <{base_iri}>")?;
339                }
340                f.write_str("ASK")?;
341                if let Some(dataset) = dataset {
342                    dataset.fmt(f)?;
343                }
344                write!(
345                    f,
346                    " WHERE {{ {} }}",
347                    SparqlGraphRootPattern::new(pattern, None)
348                )
349            }
350        }
351    }
352}
353
354/// A SPARQL query [dataset specification](https://www.w3.org/TR/sparql11-query/#specifyingDataset).
355#[derive(Eq, PartialEq, Debug, Clone, Hash)]
356#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
357pub struct QueryDataset {
358    /// Default graphs (FROM clauses)
359    pub default: Vec<NamedNode>,
360    /// Named graphs (FROM NAMED clauses)
361    pub named: Option<Vec<NamedNode>>,
362}
363
364impl QueryDataset {
365    /// Creates a new empty dataset
366    pub fn new() -> Self {
367        Self {
368            default: Vec::new(),
369            named: None,
370        }
371    }
372
373    /// Creates a dataset with default graphs
374    pub fn with_default_graphs(graphs: Vec<NamedNode>) -> Self {
375        Self {
376            default: graphs,
377            named: None,
378        }
379    }
380
381    /// Creates a dataset with named graphs
382    pub fn with_named_graphs(graphs: Vec<NamedNode>) -> Self {
383        Self {
384            default: Vec::new(),
385            named: Some(graphs),
386        }
387    }
388
389    /// Adds a default graph
390    pub fn add_default_graph(&mut self, graph: NamedNode) {
391        self.default.push(graph);
392    }
393
394    /// Adds a named graph
395    pub fn add_named_graph(&mut self, graph: NamedNode) {
396        if let Some(ref mut named) = self.named {
397            named.push(graph);
398        } else {
399            self.named = Some(vec![graph]);
400        }
401    }
402
403    /// Returns true if the dataset is empty
404    pub fn is_empty(&self) -> bool {
405        self.default.is_empty() && self.named.as_ref().map_or(true, |v| v.is_empty())
406    }
407
408    /// Formats using the SPARQL S-Expression syntax
409    pub fn fmt_sse(&self, f: &mut impl fmt::Write) -> fmt::Result {
410        f.write_str("(")?;
411        for (i, graph_name) in self.default.iter().enumerate() {
412            if i > 0 {
413                f.write_str(" ")?;
414            }
415            write!(f, "{graph_name}")?;
416        }
417        if let Some(named) = &self.named {
418            for (i, graph_name) in named.iter().enumerate() {
419                if !self.default.is_empty() || i > 0 {
420                    f.write_str(" ")?;
421                }
422                write!(f, "(named {graph_name})")?;
423            }
424        }
425        f.write_str(")")
426    }
427}
428
429impl Default for QueryDataset {
430    fn default() -> Self {
431        Self::new()
432    }
433}
434
435impl fmt::Display for QueryDataset {
436    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
437        for g in &self.default {
438            write!(f, " FROM {g}")?;
439        }
440        if let Some(named) = &self.named {
441            for g in named {
442                write!(f, " FROM NAMED {g}")?;
443            }
444        }
445        Ok(())
446    }
447}
448
449/// Helper struct for formatting graph patterns in SPARQL syntax
450pub struct SparqlGraphRootPattern<'a> {
451    pattern: &'a GraphPattern,
452    dataset: Option<&'a QueryDataset>,
453}
454
455impl<'a> SparqlGraphRootPattern<'a> {
456    pub fn new(pattern: &'a GraphPattern, dataset: Option<&'a QueryDataset>) -> Self {
457        Self { pattern, dataset }
458    }
459}
460
461impl fmt::Display for SparqlGraphRootPattern<'_> {
462    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
463        let mut distinct = false;
464        let mut reduced = false;
465        let mut order = None;
466        let mut start = 0;
467        let mut length = None;
468        let mut project: &[Variable] = &[];
469
470        let mut child = self.pattern;
471        loop {
472            match child {
473                GraphPattern::OrderBy { inner, expression } => {
474                    order = Some(expression);
475                    child = inner;
476                }
477                GraphPattern::Project { inner, variables } => {
478                    project = variables;
479                    child = inner;
480                }
481                GraphPattern::Distinct { inner } => {
482                    distinct = true;
483                    child = inner;
484                }
485                GraphPattern::Reduced { inner } => {
486                    reduced = true;
487                    child = inner;
488                }
489                GraphPattern::Slice {
490                    inner,
491                    start: s,
492                    length: l,
493                } => {
494                    start = *s;
495                    length = *l;
496                    child = inner;
497                }
498                _ => break,
499            }
500        }
501
502        if project.is_empty() {
503            f.write_str("SELECT ")?;
504        } else {
505            f.write_str("SELECT ")?;
506            if distinct {
507                f.write_str("DISTINCT ")?;
508            } else if reduced {
509                f.write_str("REDUCED ")?;
510            }
511            for (i, var) in project.iter().enumerate() {
512                if i > 0 {
513                    f.write_str(" ")?;
514                }
515                write!(f, "{var}")?;
516            }
517        }
518
519        if let Some(dataset) = self.dataset {
520            dataset.fmt(f)?;
521        }
522
523        write!(f, " WHERE {{ {} }}", SparqlInnerGraphPattern::new(child))?;
524
525        if let Some(order) = order {
526            f.write_str(" ORDER BY")?;
527            for expr in order {
528                write!(f, " {expr}")?;
529            }
530        }
531
532        if start > 0 {
533            write!(f, " OFFSET {start}")?;
534        }
535
536        if let Some(length) = length {
537            write!(f, " LIMIT {length}")?;
538        }
539
540        Ok(())
541    }
542}
543
544/// Helper struct for formatting inner graph patterns
545struct SparqlInnerGraphPattern<'a> {
546    pattern: &'a GraphPattern,
547}
548
549impl<'a> SparqlInnerGraphPattern<'a> {
550    fn new(pattern: &'a GraphPattern) -> Self {
551        Self { pattern }
552    }
553}
554
555impl fmt::Display for SparqlInnerGraphPattern<'_> {
556    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
557        match self.pattern {
558            GraphPattern::Bgp { patterns } => {
559                for (i, pattern) in patterns.iter().enumerate() {
560                    if i > 0 {
561                        f.write_str(" . ")?;
562                    }
563                    write!(f, "{pattern}")?;
564                }
565                Ok(())
566            }
567            GraphPattern::Path {
568                subject,
569                path,
570                object,
571            } => {
572                write!(f, "{subject} {path} {object}")
573            }
574            GraphPattern::Join { left, right } => {
575                write!(
576                    f,
577                    "{} . {}",
578                    SparqlInnerGraphPattern::new(left),
579                    SparqlInnerGraphPattern::new(right)
580                )
581            }
582            GraphPattern::LeftJoin {
583                left,
584                right,
585                expression,
586            } => {
587                write!(
588                    f,
589                    "{} OPTIONAL {{ {}",
590                    SparqlInnerGraphPattern::new(left),
591                    SparqlInnerGraphPattern::new(right)
592                )?;
593                if let Some(expr) = expression {
594                    write!(f, " FILTER ({expr})")?;
595                }
596                f.write_str(" }")
597            }
598            GraphPattern::Filter { expr, inner } => {
599                write!(
600                    f,
601                    "{} FILTER ({})",
602                    SparqlInnerGraphPattern::new(inner),
603                    expr
604                )
605            }
606            GraphPattern::Union { left, right } => {
607                write!(
608                    f,
609                    "{{ {} }} UNION {{ {} }}",
610                    SparqlInnerGraphPattern::new(left),
611                    SparqlInnerGraphPattern::new(right)
612                )
613            }
614            GraphPattern::Graph { name, inner } => {
615                write!(
616                    f,
617                    "GRAPH {} {{ {} }}",
618                    name,
619                    SparqlInnerGraphPattern::new(inner)
620                )
621            }
622            GraphPattern::Extend {
623                inner,
624                variable,
625                expression,
626            } => {
627                write!(
628                    f,
629                    "{} BIND ({} AS {})",
630                    SparqlInnerGraphPattern::new(inner),
631                    expression,
632                    variable
633                )
634            }
635            GraphPattern::Minus { left, right } => {
636                write!(
637                    f,
638                    "{} MINUS {{ {} }}",
639                    SparqlInnerGraphPattern::new(left),
640                    SparqlInnerGraphPattern::new(right)
641                )
642            }
643            GraphPattern::Values {
644                variables,
645                bindings,
646            } => {
647                f.write_str("VALUES ")?;
648                if variables.len() == 1 {
649                    write!(f, "{}", variables[0])?;
650                } else {
651                    f.write_str("(")?;
652                    for (i, var) in variables.iter().enumerate() {
653                        if i > 0 {
654                            f.write_str(" ")?;
655                        }
656                        write!(f, "{var}")?;
657                    }
658                    f.write_str(")")?;
659                }
660                f.write_str(" { ")?;
661                for (i, binding) in bindings.iter().enumerate() {
662                    if i > 0 {
663                        f.write_str(" ")?;
664                    }
665                    if variables.len() == 1 {
666                        if let Some(term) = &binding[0] {
667                            write!(f, "{term}")?;
668                        } else {
669                            f.write_str("UNDEF")?;
670                        }
671                    } else {
672                        f.write_str("(")?;
673                        for (j, value) in binding.iter().enumerate() {
674                            if j > 0 {
675                                f.write_str(" ")?;
676                            }
677                            if let Some(term) = value {
678                                write!(f, "{term}")?;
679                            } else {
680                                f.write_str("UNDEF")?;
681                            }
682                        }
683                        f.write_str(")")?;
684                    }
685                }
686                f.write_str(" }")
687            }
688            GraphPattern::Service {
689                name,
690                inner,
691                silent,
692            } => {
693                if *silent {
694                    write!(
695                        f,
696                        "SERVICE SILENT {} {{ {} }}",
697                        name,
698                        SparqlInnerGraphPattern::new(inner)
699                    )
700                } else {
701                    write!(
702                        f,
703                        "SERVICE {} {{ {} }}",
704                        name,
705                        SparqlInnerGraphPattern::new(inner)
706                    )
707                }
708            }
709            GraphPattern::Group {
710                inner,
711                variables: _,
712                aggregates: _,
713            } => {
714                // For display purposes, just show the inner pattern
715                // The GROUP BY clause is handled at a higher level
716                write!(f, "{}", SparqlInnerGraphPattern::new(inner))
717            }
718            // These should be handled at the root level
719            GraphPattern::Project { inner, .. }
720            | GraphPattern::Distinct { inner }
721            | GraphPattern::Reduced { inner }
722            | GraphPattern::Slice { inner, .. }
723            | GraphPattern::OrderBy { inner, .. } => {
724                write!(f, "{}", SparqlInnerGraphPattern::new(inner))
725            }
726        }
727    }
728}
729
730#[cfg(test)]
731mod tests {
732    use super::*;
733    use crate::model::{NamedNode, Variable};
734    use crate::query::sparql_algebra::TermPattern;
735
736    #[test]
737    fn test_query_creation() {
738        let var = Variable::new("s").expect("valid variable name");
739        let pattern = GraphPattern::Bgp {
740            patterns: vec![TriplePattern::new(
741                TermPattern::Variable(var.clone()),
742                TermPattern::Variable(Variable::new("p").expect("valid variable name")),
743                TermPattern::Variable(Variable::new("o").expect("valid variable name")),
744            )],
745        };
746
747        let query = Query::select(pattern);
748        assert!(matches!(query, Query::Select { .. }));
749
750        let sse = query.to_sse();
751        assert!(sse.contains("bgp"));
752    }
753
754    #[test]
755    fn test_dataset() {
756        let mut dataset = QueryDataset::new();
757        assert!(dataset.is_empty());
758
759        let graph = NamedNode::new("http://example.org/graph").expect("valid IRI");
760        dataset.add_default_graph(graph.clone());
761        dataset.add_named_graph(graph);
762
763        assert!(!dataset.is_empty());
764        assert_eq!(dataset.default.len(), 1);
765        assert_eq!(
766            dataset
767                .named
768                .as_ref()
769                .expect("operation should succeed")
770                .len(),
771            1
772        );
773    }
774
775    #[test]
776    fn test_query_display() {
777        let pattern = GraphPattern::Bgp {
778            patterns: vec![TriplePattern::new(
779                TermPattern::Variable(Variable::new("s").expect("valid variable name")),
780                TermPattern::Variable(Variable::new("p").expect("valid variable name")),
781                TermPattern::Variable(Variable::new("o").expect("valid variable name")),
782            )],
783        };
784
785        let query = Query::select(pattern);
786        let query_str = query.to_string();
787
788        assert!(query_str.contains("SELECT"));
789        assert!(query_str.contains("WHERE"));
790        assert!(query_str.contains("?s"));
791    }
792}