1use std::collections::{BTreeMap, BTreeSet};
82
83use ikigai_core::{Error, Result};
84use serde::{Deserialize, Serialize};
85use spargebra::algebra::{
86 AggregateExpression, Expression, GraphPattern, GraphTarget, OrderExpression, QueryDataset,
87};
88use spargebra::term::{
89 GraphName, GraphNamePattern, GroundQuadPattern, GroundTermPattern, Literal, NamedNode,
90 NamedNodePattern, QuadPattern, TermPattern, TriplePattern, Variable,
91};
92use spargebra::{GraphUpdateOperation, Query, SparqlParser, Update};
93
94use crate::limits;
95
96pub const CAP_READ_GRAPH: &str = "urn:cap:store:read:graph:*";
99pub const READ_GRAPH_PREFIX: &str = "urn:cap:store:read:graph:";
101pub const WRITE_GRAPH_PREFIX: &str = "urn:cap:store:write:graph:";
103pub const CAP_STORE_READ: &str = "urn:cap:store:read";
106
107pub fn cap_read_graph(iri: &str) -> String {
109 format!("{READ_GRAPH_PREFIX}{iri}")
110}
111
112pub fn cap_write_graph(iri: &str) -> String {
114 format!("{WRITE_GRAPH_PREFIX}{iri}")
115}
116
117const XSD: &str = "http://www.w3.org/2001/XMLSchema#";
118const RDFS_RESOURCE: &str = "http://www.w3.org/2000/01/rdf-schema#Resource";
119
120pub const DATATYPES: [&str; 13] = [
123 "string",
124 "boolean",
125 "integer",
126 "decimal",
127 "double",
128 "float",
129 "date",
130 "dateTime",
131 "time",
132 "duration",
133 "dayTimeDuration",
134 "yearMonthDuration",
135 "anyURI",
136];
137
138pub const RESERVED_PARAMETERS: [&str; 7] = [
142 "as",
143 "name",
144 "content",
145 "received",
146 "client",
147 "principal",
148 "content-type",
149];
150
151pub const RESULT_FACES: [&str; 4] = [
153 "application/sparql-results+json",
154 "application/sparql-results+xml",
155 "text/csv",
156 "text/tab-separated-values",
157];
158pub const GRAPH_FACES: [&str; 2] = ["text/turtle", "application/n-triples"];
160
161#[derive(Clone, Debug, PartialEq, Eq)]
180#[non_exhaustive]
181pub struct SparqlDoor {
182 prefix: String,
183 default_graphs: BTreeSet<String>,
184}
185
186impl SparqlDoor {
187 pub fn store() -> SparqlDoor {
189 SparqlDoor::store_at("urn:iki:store:")
190 }
191
192 pub fn store_at(prefix: impl Into<String>) -> SparqlDoor {
195 SparqlDoor {
196 prefix: prefix.into(),
197 default_graphs: BTreeSet::new(),
198 }
199 }
200
201 pub fn default_graphs<I, S>(mut self, graphs: I) -> SparqlDoor
206 where
207 I: IntoIterator<Item = S>,
208 S: Into<String>,
209 {
210 self.default_graphs = graphs.into_iter().map(Into::into).collect();
211 self
212 }
213
214 pub fn query_iri(&self, form: Form) -> String {
216 format!("{}graph-{}", self.prefix, form.as_str())
217 }
218
219 pub fn graphs_iri(&self) -> String {
221 format!("{}graphs", self.prefix)
222 }
223
224 pub fn defaults(&self) -> &BTreeSet<String> {
226 &self.default_graphs
227 }
228}
229
230#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
236#[serde(rename_all = "lowercase")]
237pub enum Form {
238 Select,
240 Ask,
242 Construct,
244 Describe,
246 Update,
248}
249
250impl Form {
251 pub fn as_str(self) -> &'static str {
253 match self {
254 Form::Select => "select",
255 Form::Ask => "ask",
256 Form::Construct => "construct",
257 Form::Describe => "describe",
258 Form::Update => "update",
259 }
260 }
261
262 pub fn is_read(self) -> bool {
264 self != Form::Update
265 }
266
267 pub fn faces(self) -> &'static [&'static str] {
271 match self {
272 Form::Select | Form::Ask => &RESULT_FACES,
273 Form::Construct | Form::Describe => &GRAPH_FACES,
274 Form::Update => &["text/plain"],
275 }
276 }
277}
278
279#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
281#[serde(rename_all = "lowercase", tag = "kind", content = "iri")]
282pub enum ParamType {
283 Datatype(String),
285 Class(String),
287}
288
289impl ParamType {
290 pub fn iri(&self) -> &str {
292 match self {
293 ParamType::Datatype(iri) | ParamType::Class(iri) => iri,
294 }
295 }
296}
297
298#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
300pub struct Parameter {
301 pub name: String,
303 #[serde(rename = "type")]
305 pub kind: ParamType,
306 pub required: bool,
308 pub default: Option<String>,
310 pub summary: Option<String>,
312}
313
314#[derive(Clone, Debug, PartialEq, Eq)]
316pub enum Value {
317 Iri(NamedNode),
319 Literal(Literal),
321}
322
323impl Parameter {
324 pub fn value(&self, lexical: &str) -> Result<Value> {
350 let bad = |detail: String| Error::InvalidArgument {
351 name: self.name.clone(),
352 detail,
353 };
354 match &self.kind {
355 ParamType::Class(class) => {
356 NamedNode::new(lexical.trim()).map(Value::Iri).map_err(|e| {
357 bad(format!(
358 "`{lexical}` is not an IRI ({e}); this parameter names an instance of \
359 <{class}>"
360 ))
361 })
362 }
363 ParamType::Datatype(datatype) => {
364 let local = datatype.strip_prefix(XSD).unwrap_or(datatype);
365 let canonical = canonical(local, lexical)
366 .ok_or_else(|| bad(format!("`{lexical}` is not a valid xsd:{local}")))?;
367 Ok(Value::Literal(Literal::new_typed_literal(
368 canonical,
369 NamedNode::new_unchecked(datatype.clone()),
370 )))
371 }
372 }
373 }
374}
375
376fn canonical(local: &str, lexical: &str) -> Option<String> {
378 use oxsdatatypes::{
379 Boolean, Date, DateTime, DayTimeDuration, Decimal, Double, Duration, Float, Integer, Time,
380 YearMonthDuration,
381 };
382 let t = lexical.trim();
384 match local {
385 "string" => Some(lexical.to_string()),
386 "anyURI" => Some(t.to_string()),
387 "boolean" => t.parse::<Boolean>().ok().map(|v| v.to_string()),
388 "integer" => t.parse::<Integer>().ok().map(|v| v.to_string()),
389 "decimal" => t.parse::<Decimal>().ok().map(|v| v.to_string()),
390 "double" => t.parse::<Double>().ok().map(|v| v.to_string()),
391 "float" => t.parse::<Float>().ok().map(|v| v.to_string()),
392 "date" => t.parse::<Date>().ok().map(|v| v.to_string()),
393 "dateTime" => t.parse::<DateTime>().ok().map(|v| v.to_string()),
394 "time" => t.parse::<Time>().ok().map(|v| v.to_string()),
395 "duration" => t.parse::<Duration>().ok().map(|v| v.to_string()),
396 "dayTimeDuration" => t.parse::<DayTimeDuration>().ok().map(|v| v.to_string()),
397 "yearMonthDuration" => t.parse::<YearMonthDuration>().ok().map(|v| v.to_string()),
398 _ => None,
399 }
400}
401
402fn invalid(detail: impl Into<String>) -> Error {
403 Error::InvalidArgument {
404 name: "content".to_string(),
405 detail: detail.into(),
406 }
407}
408
409pub fn parameters(source: &str) -> Result<Vec<Parameter>> {
416 let mut out: Vec<Parameter> = Vec::new();
417 let mut header = true;
418 for (number, line) in source.lines().enumerate() {
419 let trimmed = line.trim();
420 let comment = trimmed.strip_prefix('#').map(str::trim_start);
421 if header && trimmed.is_empty() {
422 continue;
423 }
424 match comment {
425 Some(body) if header => {
426 if let Some(rest) = body.strip_prefix("@param") {
427 if !rest.starts_with(char::is_whitespace) {
428 return Err(invalid(format!(
429 "line {}: `@param` needs a name and a type",
430 number + 1
431 )));
432 }
433 let parameter = declaration(rest, number + 1)?;
434 if out.iter().any(|p| p.name == parameter.name) {
435 return Err(invalid(format!(
436 "line {}: `{}` is declared twice",
437 number + 1,
438 parameter.name
439 )));
440 }
441 out.push(parameter);
442 }
443 }
444 Some(body) if body.starts_with("@param") => {
445 return Err(invalid(format!(
446 "line {}: a parameter is declared only in the comment block before the \
447 query's first token; this one would be ignored, and its variable left \
448 unbound",
449 number + 1
450 )));
451 }
452 _ => header = false,
453 }
454 }
455 Ok(out)
456}
457
458fn declaration(rest: &str, line: usize) -> Result<Parameter> {
460 let bad = |detail: String| invalid(format!("line {line}: {detail}"));
461 let mut tokens = Tokens { rest: rest.trim() };
462 let name = tokens
463 .word()
464 .ok_or_else(|| bad("`@param` needs a name and a type".to_string()))?;
465 let name = name.strip_prefix(['?', '$']).unwrap_or(&name).to_string();
466 let valid = name
467 .chars()
468 .next()
469 .is_some_and(|c| c.is_ascii_alphabetic() || c == '_')
470 && name.chars().all(|c| c.is_ascii_alphanumeric() || c == '_');
471 if !valid {
472 return Err(bad(format!(
473 "`{name}` is not a parameter name: ASCII letters, digits and `_`, not starting \
474 with a digit"
475 )));
476 }
477 if RESERVED_PARAMETERS.contains(&name.as_str()) {
478 return Err(bad(format!(
479 "`{name}` is an argument a door already reads ({}); choose another name",
480 RESERVED_PARAMETERS.join(", ")
481 )));
482 }
483 let kind = tokens
484 .word()
485 .ok_or_else(|| {
486 bad(format!(
487 "`{name}` needs a type: `xsd:integer`, `<class IRI>`, …"
488 ))
489 })
490 .and_then(|t| param_type(&t).map_err(bad))?;
491 let mut parameter = Parameter {
492 name,
493 kind,
494 required: true,
495 default: None,
496 summary: None,
497 };
498 while let Some(word) = tokens.word() {
499 match word.as_str() {
500 "required" => parameter.required = true,
501 "optional" => parameter.required = false,
502 "default" => {
503 let value = tokens
504 .value()
505 .map_err(bad)?
506 .ok_or_else(|| bad("`default` needs a value".to_string()))?;
507 parameter.value(&value)?;
509 parameter.default = Some(value);
510 parameter.required = false;
511 }
512 "--" => {
513 let summary = tokens.rest.trim();
514 if !summary.is_empty() {
515 parameter.summary = Some(summary.to_string());
516 }
517 break;
518 }
519 other => {
520 return Err(bad(format!(
521 "`{other}` is not `required`, `optional`, `default <value>` or \
522 `-- <summary>`"
523 )))
524 }
525 }
526 }
527 Ok(parameter)
528}
529
530fn param_type(token: &str) -> std::result::Result<ParamType, String> {
531 if let Some(local) = token.strip_prefix("xsd:") {
532 return datatype(&format!("{XSD}{local}"));
533 }
534 if token == "rdfs:Resource" {
535 return Ok(ParamType::Class(RDFS_RESOURCE.to_string()));
536 }
537 if let Some(iri) = token.strip_prefix('<').and_then(|t| t.strip_suffix('>')) {
538 NamedNode::new(iri).map_err(|e| format!("`{token}` is not an IRI: {e}"))?;
539 if iri.starts_with(XSD) {
540 return datatype(iri);
541 }
542 return Ok(ParamType::Class(iri.to_string()));
543 }
544 Err(format!(
545 "`{token}` is not a type: an XSD datatype (`xsd:integer`, one of {}), a class \
546 (`<iri>`), or `rdfs:Resource` for any IRI",
547 DATATYPES.join(", ")
548 ))
549}
550
551fn datatype(iri: &str) -> std::result::Result<ParamType, String> {
552 let local = iri.strip_prefix(XSD).unwrap_or(iri);
553 if DATATYPES.contains(&local) {
554 Ok(ParamType::Datatype(iri.to_string()))
555 } else {
556 Err(format!(
557 "xsd:{local} is not a parameter datatype here; one of {}",
558 DATATYPES.join(", ")
559 ))
560 }
561}
562
563struct Tokens<'a> {
566 rest: &'a str,
567}
568
569impl Tokens<'_> {
570 fn word(&mut self) -> Option<String> {
571 let rest = self.rest.trim_start();
572 if rest.is_empty() {
573 return None;
574 }
575 let end = rest.find(char::is_whitespace).unwrap_or(rest.len());
576 let (word, tail) = rest.split_at(end);
577 self.rest = tail;
578 Some(word.to_string())
579 }
580
581 fn value(&mut self) -> std::result::Result<Option<String>, String> {
582 let rest = self.rest.trim_start();
583 if !rest.starts_with('"') {
584 return Ok(self.word());
585 }
586 let mut stream = serde_json::Deserializer::from_str(rest).into_iter::<String>();
588 match stream.next() {
589 Some(Ok(value)) => {
590 self.rest = &rest[stream.byte_offset()..];
591 Ok(Some(value))
592 }
593 _ => Err(format!(
594 "`{rest}` does not start with a complete JSON string"
595 )),
596 }
597 }
598}
599
600#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
606pub struct Analysis {
607 pub form: Form,
609 pub parameters: Vec<Parameter>,
611 pub graphs: BTreeSet<String>,
614 #[serde(rename = "anyGraph")]
617 pub any_graph: bool,
618 #[serde(rename = "updateReads")]
620 pub update_reads: bool,
621}
622
623impl Analysis {
624 pub fn requires(&self) -> BTreeSet<String> {
631 let mut out = BTreeSet::new();
632 for graph in &self.graphs {
633 if self.form == Form::Update {
634 out.insert(cap_write_graph(graph));
635 if self.update_reads {
636 out.insert(cap_read_graph(graph));
637 }
638 } else {
639 out.insert(cap_read_graph(graph));
640 }
641 }
642 if self.any_graph {
643 out.insert(CAP_READ_GRAPH.to_string());
644 }
645 out
646 }
647}
648
649pub fn analyze(source: &str, door: &SparqlDoor) -> Result<Analysis> {
653 limits::check_sparql(source, "content")?;
656 let parameters = parameters(source)?;
657 limits::on_sparql_stack(source, || {
658 let parsed = parse(source)?;
659 let shape = shape(&parsed, door)?;
660 check_parameters(¶meters, &shape)?;
661 Ok(Analysis {
662 form: parsed.form(),
663 parameters,
664 graphs: shape.graphs,
665 any_graph: shape.any_graph,
666 update_reads: shape.update_reads,
667 })
668 })
669}
670
671#[derive(Clone, Debug, PartialEq, Eq)]
673pub struct Bound {
674 pub form: Form,
676 pub text: String,
678 pub graphs: BTreeSet<String>,
680 pub any_graph: bool,
682}
683
684pub fn bind(source: &str, door: &SparqlDoor, values: &BTreeMap<String, Value>) -> Result<Bound> {
694 limits::check_sparql(source, "content")?;
695 limits::on_sparql_stack(source, || {
696 let mut parsed = parse(source)?;
697 let subst = Subst { values };
698 match &mut parsed {
699 Parsed::Query(query) => subst.query(query)?,
700 Parsed::Update(update) => subst.update(update)?,
701 }
702 let shape = shape(&parsed, door)?;
703 let text = match &mut parsed {
704 Parsed::Query(query) => {
705 *dataset_of(query) = None;
708 query.to_string()
709 }
710 Parsed::Update(update) => update.to_string(),
711 };
712 Ok(Bound {
713 form: parsed.form(),
714 text,
715 graphs: shape.graphs,
716 any_graph: shape.any_graph,
717 })
718 })
719}
720
721enum Parsed {
722 Query(Box<Query>),
723 Update(Update),
724}
725
726impl Parsed {
727 fn form(&self) -> Form {
728 match self {
729 Parsed::Query(query) => match **query {
730 Query::Select { .. } => Form::Select,
731 Query::Ask { .. } => Form::Ask,
732 Query::Construct { .. } => Form::Construct,
733 Query::Describe { .. } => Form::Describe,
734 },
735 Parsed::Update(_) => Form::Update,
736 }
737 }
738}
739
740fn parse(source: &str) -> Result<Parsed> {
743 let query_error = match SparqlParser::new().parse_query(source) {
744 Ok(query) => return Ok(Parsed::Query(Box::new(query))),
745 Err(e) => e,
746 };
747 match SparqlParser::new().parse_update(source) {
748 Ok(update) => Ok(Parsed::Update(update)),
749 Err(update_error) => Err(invalid(format!(
750 "not a SPARQL query ({query_error}) nor an update ({update_error})"
751 ))),
752 }
753}
754
755fn dataset_of(query: &mut Query) -> &mut Option<QueryDataset> {
756 match query {
757 Query::Select { dataset, .. }
758 | Query::Ask { dataset, .. }
759 | Query::Construct { dataset, .. }
760 | Query::Describe { dataset, .. } => dataset,
761 }
762}
763
764#[derive(Default)]
766struct Scan {
767 reads_default: bool,
769 graphs: BTreeSet<String>,
771 graph_variables: BTreeSet<String>,
773 used: BTreeSet<String>,
775 bound: BTreeSet<String>,
778 iri_only: BTreeSet<String>,
780 service: bool,
782}
783
784struct Shape {
786 graphs: BTreeSet<String>,
787 any_graph: bool,
788 update_reads: bool,
789 scan: Scan,
790}
791
792fn shape(parsed: &Parsed, door: &SparqlDoor) -> Result<Shape> {
793 match parsed {
794 Parsed::Query(query) => query_shape(query, door),
795 Parsed::Update(update) => update_shape(update),
796 }
797}
798
799fn query_shape(query: &Query, door: &SparqlDoor) -> Result<Shape> {
800 let mut scan = Scan::default();
801 let (dataset, describe) = match query {
802 Query::Select {
803 dataset, pattern, ..
804 }
805 | Query::Ask {
806 dataset, pattern, ..
807 } => {
808 scan.pattern(pattern, false);
809 (dataset, false)
810 }
811 Query::Construct {
812 template,
813 dataset,
814 pattern,
815 ..
816 } => {
817 for triple in template {
818 scan.triple(triple, true);
819 }
820 scan.pattern(pattern, false);
821 (dataset, false)
822 }
823 Query::Describe {
824 dataset, pattern, ..
825 } => {
826 scan.pattern(pattern, false);
827 (dataset, true)
828 }
829 };
830 if scan.service {
831 return Err(invalid(
832 "a script cannot use SERVICE: the store refuses federation, and a run's authority \
833 is derived from graphs this store holds",
834 ));
835 }
836 let from: BTreeSet<String> = dataset
837 .iter()
838 .flat_map(|d| d.default.iter())
839 .map(|g| g.as_str().to_string())
840 .collect();
841 let from_named: BTreeSet<String> = dataset
842 .iter()
843 .flat_map(|d| d.named.iter().flatten())
844 .map(|g| g.as_str().to_string())
845 .collect();
846 let any_graph = !scan.graph_variables.is_empty();
847 let reads_default = scan.reads_default || describe;
850 let graphs = if reads_default {
851 let base = if from.is_empty() {
852 door.defaults().clone()
853 } else {
854 from.clone()
855 };
856 if base.is_empty() {
857 return Err(invalid(
858 "this query reads the default graph (a pattern outside every GRAPH block, or \
859 a DESCRIBE) but names no graph with FROM, and this host gives no default \
860 dataset. Name the graphs it reads with FROM <…>, or put its patterns in \
861 GRAPH <…> blocks",
862 ));
863 }
864 let extra: Vec<&String> = from_named
865 .iter()
866 .chain(scan.graphs.iter())
867 .filter(|g| !base.contains(*g))
868 .collect();
869 if !extra.is_empty() {
870 return Err(invalid(format!(
871 "this query reads the default graph, which a script's dataset makes the merge \
872 of EVERY graph it names; it also names {} outside its FROM set ({}), so its \
873 bare patterns would read them too. Add them to FROM, or put the bare \
874 patterns in GRAPH blocks",
875 extra
876 .iter()
877 .map(|g| format!("<{g}>"))
878 .collect::<Vec<_>>()
879 .join(", "),
880 base.iter()
881 .map(|g| format!("<{g}>"))
882 .collect::<Vec<_>>()
883 .join(", ")
884 )));
885 }
886 if any_graph {
887 return Err(invalid(
888 "this query reads the default graph and also has a GRAPH ?variable, which \
889 runs over the caller's readable union: in a script's one dataset the bare \
890 patterns would read that whole union too. Put the bare patterns in GRAPH \
891 blocks, or fix the variable graph with GRAPH <…>",
892 ));
893 }
894 base
895 } else {
896 let mut all = from;
897 all.extend(from_named);
898 all.extend(scan.graphs.iter().cloned());
899 if all.is_empty() && !any_graph {
900 return Err(invalid(
901 "this query names no graph (no FROM, FROM NAMED or GRAPH), so it reads \
902 nothing; name the graphs it reads",
903 ));
904 }
905 all
906 };
907 Ok(Shape {
908 graphs,
909 any_graph,
910 update_reads: false,
911 scan,
912 })
913}
914
915fn update_shape(update: &Update) -> Result<Shape> {
916 let mut scan = Scan::default();
917 let mut targets: BTreeSet<String> = BTreeSet::new();
918 let mut reads: BTreeSet<String> = BTreeSet::new();
919 let mut has_where = false;
920 let unnamed_target = || {
921 invalid(
922 "an update script names the graph it writes: CLEAR GRAPH <…> or DROP GRAPH <…>, \
923 not DEFAULT, NAMED or ALL",
924 )
925 };
926 for operation in &update.operations {
927 match operation {
928 GraphUpdateOperation::InsertData { data } => {
929 for quad in data {
930 match &quad.graph_name {
931 GraphName::NamedNode(g) => targets.insert(g.as_str().to_string()),
932 GraphName::DefaultGraph => return Err(writes_default()),
933 };
934 }
935 }
936 GraphUpdateOperation::DeleteData { data } => {
937 for quad in data {
938 match &quad.graph_name {
939 GraphName::NamedNode(g) => targets.insert(g.as_str().to_string()),
940 GraphName::DefaultGraph => return Err(writes_default()),
941 };
942 }
943 }
944 GraphUpdateOperation::DeleteInsert {
945 delete,
946 insert,
947 using,
948 pattern,
949 } => {
950 has_where = true;
951 for quad in delete {
952 scan.ground_quad(quad);
953 template_target(&quad.graph_name, &mut targets)?;
954 }
955 for quad in insert {
956 scan.quad(quad);
957 template_target(&quad.graph_name, &mut targets)?;
958 }
959 let mut inner = Scan::default();
960 inner.pattern(pattern, false);
961 let using_default: Vec<String> = using
962 .iter()
963 .flat_map(|d| d.default.iter())
964 .map(|g| g.as_str().to_string())
965 .collect();
966 if inner.reads_default && using_default.is_empty() {
967 return Err(invalid(
968 "this update's WHERE reads the default graph, which the store's scoped \
969 door does not hold: match inside GRAPH <…>, or name the graph with \
970 USING <…> or WITH <…>",
971 ));
972 }
973 if !inner.graph_variables.is_empty() {
974 return Err(invalid(
975 "an update script's WHERE cannot use GRAPH ?variable: the graph it reads \
976 is part of its authority, so the text names it",
977 ));
978 }
979 reads.extend(using_default);
980 reads.extend(
981 using
982 .iter()
983 .flat_map(|d| d.named.iter().flatten())
984 .map(|g| g.as_str().to_string()),
985 );
986 reads.extend(inner.graphs.iter().cloned());
987 scan.absorb(inner);
988 }
989 GraphUpdateOperation::Load { .. } => {
990 return Err(invalid(
991 "a script cannot LOAD: the store fetches nothing over the network, and \
992 an update script writes only what its text says",
993 ))
994 }
995 GraphUpdateOperation::Clear { graph, .. }
996 | GraphUpdateOperation::Drop { graph, .. } => match graph {
997 GraphTarget::NamedNode(g) => {
998 targets.insert(g.as_str().to_string());
999 }
1000 _ => return Err(unnamed_target()),
1001 },
1002 GraphUpdateOperation::Create { graph, .. } => {
1003 targets.insert(graph.as_str().to_string());
1004 }
1005 }
1006 }
1007 if scan.service {
1008 return Err(invalid(
1009 "a script cannot use SERVICE: the store refuses federation",
1010 ));
1011 }
1012 let mut all = targets.clone();
1013 all.extend(reads);
1014 if all.len() != 1 || targets.len() != 1 {
1015 return Err(invalid(format!(
1016 "an update script writes exactly one named graph and reads no other (the store's \
1017 scoped door confines it to one); this one names {}",
1018 if all.is_empty() {
1019 "none".to_string()
1020 } else {
1021 all.iter()
1022 .map(|g| format!("<{g}>"))
1023 .collect::<Vec<_>>()
1024 .join(", ")
1025 }
1026 )));
1027 }
1028 Ok(Shape {
1029 graphs: targets,
1030 any_graph: false,
1031 update_reads: has_where,
1032 scan,
1033 })
1034}
1035
1036fn template_target(graph: &GraphNamePattern, targets: &mut BTreeSet<String>) -> Result<()> {
1038 match graph {
1039 GraphNamePattern::NamedNode(g) => {
1040 targets.insert(g.as_str().to_string());
1041 Ok(())
1042 }
1043 GraphNamePattern::DefaultGraph => Err(writes_default()),
1044 GraphNamePattern::Variable(_) => Err(invalid(
1045 "an update script cannot write GRAPH ?variable: the graph it writes is its \
1046 authority, so the text names it",
1047 )),
1048 }
1049}
1050
1051fn writes_default() -> Error {
1052 invalid(
1053 "this update writes the default graph, which a script cannot: the store's scoped \
1054 door confines an update to one named graph. Write inside GRAPH <…> (or use \
1055 WITH <…>)",
1056 )
1057}
1058
1059fn check_parameters(parameters: &[Parameter], shape: &Shape) -> Result<()> {
1061 let scan = &shape.scan;
1062 for p in parameters {
1063 if !scan.used.contains(&p.name) {
1064 return Err(invalid(format!(
1065 "parameter `{}` is declared but the query never uses ?{0}",
1066 p.name
1067 )));
1068 }
1069 if scan.bound.contains(&p.name) {
1070 return Err(invalid(format!(
1071 "parameter `{}` is a value, but the query binds ?{0} itself (BIND … AS, \
1072 VALUES, GROUP BY or an aggregate's name); rename one of them",
1073 p.name
1074 )));
1075 }
1076 if scan.iri_only.contains(&p.name) && !matches!(p.kind, ParamType::Class(_)) {
1077 return Err(invalid(format!(
1078 "parameter `{}` is used as a predicate or a graph name, which only an IRI can \
1079 fill; declare it with a class (`rdfs:Resource` for any IRI)",
1080 p.name
1081 )));
1082 }
1083 }
1084 Ok(())
1085}
1086
1087impl Scan {
1088 fn absorb(&mut self, other: Scan) {
1089 self.reads_default |= other.reads_default;
1090 self.graphs.extend(other.graphs);
1091 self.graph_variables.extend(other.graph_variables);
1092 self.used.extend(other.used);
1093 self.bound.extend(other.bound);
1094 self.iri_only.extend(other.iri_only);
1095 self.service |= other.service;
1096 }
1097
1098 fn var(&mut self, v: &Variable) {
1099 self.used.insert(v.as_str().to_string());
1100 }
1101
1102 fn term(&mut self, t: &TermPattern) {
1103 if let TermPattern::Variable(v) = t {
1106 self.var(v);
1107 }
1108 }
1109
1110 fn named(&mut self, n: &NamedNodePattern) {
1111 if let NamedNodePattern::Variable(v) = n {
1112 self.var(v);
1113 self.iri_only.insert(v.as_str().to_string());
1114 }
1115 }
1116
1117 fn triple(&mut self, t: &TriplePattern, in_graph: bool) {
1118 if !in_graph {
1119 self.reads_default = true;
1120 }
1121 self.term(&t.subject);
1122 self.named(&t.predicate);
1123 self.term(&t.object);
1124 }
1125
1126 fn quad(&mut self, q: &QuadPattern) {
1127 self.term(&q.subject);
1128 self.named(&q.predicate);
1129 self.term(&q.object);
1130 if let GraphNamePattern::Variable(v) = &q.graph_name {
1131 self.var(v);
1132 }
1133 }
1134
1135 fn ground_quad(&mut self, q: &GroundQuadPattern) {
1136 if let GroundTermPattern::Variable(v) = &q.subject {
1137 self.var(v);
1138 }
1139 self.named(&q.predicate);
1140 if let GroundTermPattern::Variable(v) = &q.object {
1141 self.var(v);
1142 }
1143 if let GraphNamePattern::Variable(v) = &q.graph_name {
1144 self.var(v);
1145 }
1146 }
1147
1148 fn pattern(&mut self, p: &GraphPattern, in_graph: bool) {
1149 match p {
1150 GraphPattern::Bgp { patterns } => {
1151 for t in patterns {
1152 self.triple(t, in_graph);
1153 }
1154 }
1155 GraphPattern::Path {
1156 subject, object, ..
1157 } => {
1158 if !in_graph {
1159 self.reads_default = true;
1160 }
1161 self.term(subject);
1162 self.term(object);
1163 }
1164 GraphPattern::Join { left, right }
1165 | GraphPattern::Union { left, right }
1166 | GraphPattern::Minus { left, right } => {
1167 self.pattern(left, in_graph);
1168 self.pattern(right, in_graph);
1169 }
1170 GraphPattern::LeftJoin {
1171 left,
1172 right,
1173 expression,
1174 } => {
1175 self.pattern(left, in_graph);
1176 self.pattern(right, in_graph);
1177 if let Some(e) = expression {
1178 self.expression(e, in_graph);
1179 }
1180 }
1181 GraphPattern::Filter { expr, inner } => {
1182 self.expression(expr, in_graph);
1183 self.pattern(inner, in_graph);
1184 }
1185 GraphPattern::Graph { name, inner } => {
1186 match name {
1187 NamedNodePattern::NamedNode(g) => {
1188 self.graphs.insert(g.as_str().to_string());
1189 }
1190 NamedNodePattern::Variable(v) => {
1191 self.graph_variables.insert(v.as_str().to_string());
1192 }
1193 }
1194 self.named(name);
1195 self.pattern(inner, true);
1196 }
1197 GraphPattern::Extend {
1198 inner,
1199 variable,
1200 expression,
1201 } => {
1202 self.bound.insert(variable.as_str().to_string());
1203 self.var(variable);
1204 self.expression(expression, in_graph);
1205 self.pattern(inner, in_graph);
1206 }
1207 GraphPattern::Values { variables, .. } => {
1208 for v in variables {
1209 self.var(v);
1210 self.bound.insert(v.as_str().to_string());
1211 }
1212 }
1213 GraphPattern::OrderBy { inner, expression } => {
1214 for e in expression {
1215 match e {
1216 OrderExpression::Asc(e) | OrderExpression::Desc(e) => {
1217 self.expression(e, in_graph)
1218 }
1219 }
1220 }
1221 self.pattern(inner, in_graph);
1222 }
1223 GraphPattern::Project { inner, variables } => {
1224 for v in variables {
1225 self.var(v);
1226 }
1227 self.pattern(inner, in_graph);
1228 }
1229 GraphPattern::Distinct { inner }
1230 | GraphPattern::Reduced { inner }
1231 | GraphPattern::Slice { inner, .. } => self.pattern(inner, in_graph),
1232 GraphPattern::Group {
1233 inner,
1234 variables,
1235 aggregates,
1236 } => {
1237 for v in variables {
1238 self.var(v);
1239 self.bound.insert(v.as_str().to_string());
1240 }
1241 for (v, aggregate) in aggregates {
1242 self.bound.insert(v.as_str().to_string());
1243 if let AggregateExpression::FunctionCall { expr, .. } = aggregate {
1244 self.expression(expr, in_graph);
1245 }
1246 }
1247 self.pattern(inner, in_graph);
1248 }
1249 GraphPattern::Service { inner, .. } => {
1250 self.service = true;
1251 self.pattern(inner, in_graph);
1252 }
1253 #[allow(unreachable_patterns)] _ => self.service = true,
1258 }
1259 }
1260
1261 fn expression(&mut self, e: &Expression, in_graph: bool) {
1262 match e {
1263 Expression::Variable(v) | Expression::Bound(v) => self.var(v),
1264 Expression::Exists(p) => self.pattern(p, in_graph),
1265 Expression::Or(a, b)
1266 | Expression::And(a, b)
1267 | Expression::Equal(a, b)
1268 | Expression::SameTerm(a, b)
1269 | Expression::Greater(a, b)
1270 | Expression::GreaterOrEqual(a, b)
1271 | Expression::Less(a, b)
1272 | Expression::LessOrEqual(a, b)
1273 | Expression::Add(a, b)
1274 | Expression::Subtract(a, b)
1275 | Expression::Multiply(a, b)
1276 | Expression::Divide(a, b) => {
1277 self.expression(a, in_graph);
1278 self.expression(b, in_graph);
1279 }
1280 Expression::In(a, list) => {
1281 self.expression(a, in_graph);
1282 for x in list {
1283 self.expression(x, in_graph);
1284 }
1285 }
1286 Expression::UnaryPlus(a) | Expression::UnaryMinus(a) | Expression::Not(a) => {
1287 self.expression(a, in_graph)
1288 }
1289 Expression::If(a, b, c) => {
1290 self.expression(a, in_graph);
1291 self.expression(b, in_graph);
1292 self.expression(c, in_graph);
1293 }
1294 Expression::Coalesce(list) | Expression::FunctionCall(_, list) => {
1295 for x in list {
1296 self.expression(x, in_graph);
1297 }
1298 }
1299 Expression::NamedNode(_) | Expression::Literal(_) => {}
1300 }
1301 }
1302}
1303
1304struct Subst<'a> {
1309 values: &'a BTreeMap<String, Value>,
1310}
1311
1312impl Subst<'_> {
1313 fn value(&self, v: &Variable) -> Option<&Value> {
1314 self.values.get(v.as_str())
1315 }
1316
1317 fn query(&self, query: &mut Query) -> Result<()> {
1318 match query {
1319 Query::Select { pattern, .. }
1320 | Query::Ask { pattern, .. }
1321 | Query::Describe { pattern, .. } => {
1322 self.pattern(pattern)?;
1323 }
1324 Query::Construct {
1325 template, pattern, ..
1326 } => {
1327 for triple in template {
1328 self.triple(triple)?;
1329 }
1330 self.pattern(pattern)?;
1331 }
1332 }
1333 Ok(())
1334 }
1335
1336 fn update(&self, update: &mut Update) -> Result<()> {
1337 for operation in &mut update.operations {
1338 if let GraphUpdateOperation::DeleteInsert {
1339 delete,
1340 insert,
1341 pattern,
1342 ..
1343 } = operation
1344 {
1345 for quad in delete {
1346 quad.subject = self.ground_term(quad.subject.clone());
1347 quad.predicate = self.named(quad.predicate.clone())?;
1348 quad.object = self.ground_term(quad.object.clone());
1349 }
1350 for quad in insert {
1351 quad.subject = self.term(quad.subject.clone());
1352 quad.predicate = self.named(quad.predicate.clone())?;
1353 quad.object = self.term(quad.object.clone());
1354 }
1355 self.pattern(pattern)?;
1356 }
1357 }
1358 Ok(())
1359 }
1360
1361 fn term(&self, t: TermPattern) -> TermPattern {
1362 match &t {
1363 TermPattern::Variable(v) => match self.value(v) {
1364 Some(Value::Iri(n)) => TermPattern::NamedNode(n.clone()),
1365 Some(Value::Literal(l)) => TermPattern::Literal(l.clone()),
1366 None => t,
1367 },
1368 _ => t,
1369 }
1370 }
1371
1372 fn ground_term(&self, t: GroundTermPattern) -> GroundTermPattern {
1373 match &t {
1374 GroundTermPattern::Variable(v) => match self.value(v) {
1375 Some(Value::Iri(n)) => GroundTermPattern::NamedNode(n.clone()),
1376 Some(Value::Literal(l)) => GroundTermPattern::Literal(l.clone()),
1377 None => t,
1378 },
1379 _ => t,
1380 }
1381 }
1382
1383 fn named(&self, n: NamedNodePattern) -> Result<NamedNodePattern> {
1384 match &n {
1385 NamedNodePattern::Variable(v) => match self.value(v) {
1386 Some(Value::Iri(iri)) => Ok(NamedNodePattern::NamedNode(iri.clone())),
1387 Some(Value::Literal(_)) => Err(Error::InvalidArgument {
1390 name: v.as_str().to_string(),
1391 detail: "is used as a predicate or a graph name, which only an IRI can fill"
1392 .to_string(),
1393 }),
1394 None => Ok(n),
1395 },
1396 NamedNodePattern::NamedNode(_) => Ok(n),
1397 }
1398 }
1399
1400 fn triple(&self, t: &mut TriplePattern) -> Result<()> {
1401 t.subject = self.term(t.subject.clone());
1402 t.predicate = self.named(t.predicate.clone())?;
1403 t.object = self.term(t.object.clone());
1404 Ok(())
1405 }
1406
1407 fn constant(&self, v: &Variable) -> Option<Expression> {
1408 self.value(v).map(|value| match value {
1409 Value::Iri(n) => Expression::NamedNode(n.clone()),
1410 Value::Literal(l) => Expression::Literal(l.clone()),
1411 })
1412 }
1413
1414 fn pattern(&self, p: &mut GraphPattern) -> Result<BTreeSet<String>> {
1417 Ok(match p {
1418 GraphPattern::Bgp { patterns } => {
1419 for t in patterns {
1420 self.triple(t)?;
1421 }
1422 BTreeSet::new()
1423 }
1424 GraphPattern::Path {
1425 subject, object, ..
1426 } => {
1427 *subject = self.term(subject.clone());
1428 *object = self.term(object.clone());
1429 BTreeSet::new()
1430 }
1431 GraphPattern::Join { left, right } | GraphPattern::Union { left, right } => {
1432 let mut out = self.pattern(left)?;
1433 out.extend(self.pattern(right)?);
1434 out
1435 }
1436 GraphPattern::Minus { left, right } => {
1437 self.pattern(right)?;
1438 self.pattern(left)?
1439 }
1440 GraphPattern::LeftJoin {
1441 left,
1442 right,
1443 expression,
1444 } => {
1445 let mut out = self.pattern(left)?;
1446 out.extend(self.pattern(right)?);
1447 if let Some(e) = expression {
1448 self.expression(e)?;
1449 }
1450 out
1451 }
1452 GraphPattern::Filter { expr, inner } => {
1453 self.expression(expr)?;
1454 self.pattern(inner)?
1455 }
1456 GraphPattern::Graph { name, inner } => {
1457 *name = self.named(name.clone())?;
1458 self.pattern(inner)?
1459 }
1460 GraphPattern::Extend {
1461 inner, expression, ..
1462 } => {
1463 self.expression(expression)?;
1464 self.pattern(inner)?
1465 }
1466 GraphPattern::Values { .. } => BTreeSet::new(),
1467 GraphPattern::OrderBy { inner, expression } => {
1468 for e in expression {
1469 match e {
1470 OrderExpression::Asc(e) | OrderExpression::Desc(e) => self.expression(e)?,
1471 }
1472 }
1473 self.pattern(inner)?
1474 }
1475 GraphPattern::Project { inner, variables } => {
1476 let exposed = self.pattern(inner)?;
1477 for v in variables.iter() {
1480 if exposed.contains(v.as_str()) {
1481 continue;
1482 }
1483 if let Some(constant) = self.constant(v) {
1484 let taken = std::mem::replace(
1485 &mut **inner,
1486 GraphPattern::Bgp {
1487 patterns: Vec::new(),
1488 },
1489 );
1490 **inner = GraphPattern::Extend {
1491 inner: Box::new(taken),
1492 variable: v.clone(),
1493 expression: constant,
1494 };
1495 }
1496 }
1497 variables
1498 .iter()
1499 .filter(|v| self.value(v).is_some())
1500 .map(|v| v.as_str().to_string())
1501 .collect()
1502 }
1503 GraphPattern::Distinct { inner }
1504 | GraphPattern::Reduced { inner }
1505 | GraphPattern::Slice { inner, .. } => self.pattern(inner)?,
1506 GraphPattern::Group {
1507 inner, aggregates, ..
1508 } => {
1509 for (_, aggregate) in aggregates {
1510 if let AggregateExpression::FunctionCall { expr, .. } = aggregate {
1511 self.expression(expr)?;
1512 }
1513 }
1514 self.pattern(inner)?;
1515 BTreeSet::new()
1516 }
1517 GraphPattern::Service { .. } => BTreeSet::new(),
1518 #[allow(unreachable_patterns)] _ => BTreeSet::new(),
1520 })
1521 }
1522
1523 fn expression(&self, e: &mut Expression) -> Result<()> {
1524 match e {
1525 Expression::Variable(v) => {
1526 if let Some(constant) = self.constant(v) {
1527 *e = constant;
1528 }
1529 }
1530 Expression::Bound(v) => {
1531 if self.value(v).is_some() {
1532 *e = Expression::Literal(Literal::from(true));
1533 }
1534 }
1535 Expression::Exists(p) => {
1536 self.pattern(p)?;
1537 }
1538 Expression::Or(a, b)
1539 | Expression::And(a, b)
1540 | Expression::Equal(a, b)
1541 | Expression::SameTerm(a, b)
1542 | Expression::Greater(a, b)
1543 | Expression::GreaterOrEqual(a, b)
1544 | Expression::Less(a, b)
1545 | Expression::LessOrEqual(a, b)
1546 | Expression::Add(a, b)
1547 | Expression::Subtract(a, b)
1548 | Expression::Multiply(a, b)
1549 | Expression::Divide(a, b) => {
1550 self.expression(a)?;
1551 self.expression(b)?;
1552 }
1553 Expression::In(a, list) => {
1554 self.expression(a)?;
1555 for x in list {
1556 self.expression(x)?;
1557 }
1558 }
1559 Expression::UnaryPlus(a) | Expression::UnaryMinus(a) | Expression::Not(a) => {
1560 self.expression(a)?
1561 }
1562 Expression::If(a, b, c) => {
1563 self.expression(a)?;
1564 self.expression(b)?;
1565 self.expression(c)?;
1566 }
1567 Expression::Coalesce(list) | Expression::FunctionCall(_, list) => {
1568 for x in list {
1569 self.expression(x)?;
1570 }
1571 }
1572 Expression::NamedNode(_) | Expression::Literal(_) => {}
1573 }
1574 Ok(())
1575 }
1576}
1577
1578#[cfg(test)]
1579mod tests {
1580 use super::*;
1581
1582 fn door() -> SparqlDoor {
1583 SparqlDoor::store()
1584 }
1585
1586 fn values(pairs: &[(&str, Value)]) -> BTreeMap<String, Value> {
1587 pairs
1588 .iter()
1589 .map(|(k, v)| (k.to_string(), v.clone()))
1590 .collect()
1591 }
1592
1593 fn int(n: i64) -> Value {
1594 Value::Literal(Literal::from(n))
1595 }
1596
1597 #[test]
1598 fn the_form_is_read_from_the_text() {
1599 let cases = [
1600 (
1601 "SELECT * WHERE { GRAPH <urn:g> { ?s ?p ?o } }",
1602 Form::Select,
1603 ),
1604 ("ASK { GRAPH <urn:g> { ?s ?p ?o } }", Form::Ask),
1605 (
1606 "CONSTRUCT { ?s ?p ?o } WHERE { GRAPH <urn:g> { ?s ?p ?o } }",
1607 Form::Construct,
1608 ),
1609 ("DESCRIBE <urn:x> FROM <urn:g>", Form::Describe),
1610 (
1611 "INSERT DATA { GRAPH <urn:g> { <urn:a> <urn:b> 1 } }",
1612 Form::Update,
1613 ),
1614 ];
1615 for (text, form) in cases {
1616 assert_eq!(analyze(text, &door()).unwrap().form, form, "{text}");
1617 }
1618 }
1619
1620 #[test]
1621 fn graphs_come_from_from_from_named_and_graph() {
1622 let a = analyze(
1623 "SELECT * FROM NAMED <urn:b> WHERE { GRAPH <urn:a> { ?s ?p ?o } GRAPH <urn:b> { ?s ?q ?r } }",
1624 &door(),
1625 )
1626 .unwrap();
1627 assert_eq!(
1628 a.graphs.iter().cloned().collect::<Vec<_>>(),
1629 vec!["urn:a", "urn:b"]
1630 );
1631 assert!(!a.any_graph);
1632 let a = analyze("SELECT ?g WHERE { GRAPH ?g { ?s ?p ?o } }", &door()).unwrap();
1633 assert!(a.any_graph && a.graphs.is_empty());
1634 assert_eq!(
1635 a.requires().into_iter().collect::<Vec<_>>(),
1636 vec![CAP_READ_GRAPH.to_string()]
1637 );
1638 }
1639
1640 #[test]
1641 fn a_query_whose_answer_would_change_is_refused() {
1642 let e = analyze(
1644 "SELECT * FROM <urn:a> WHERE { ?s ?p ?o GRAPH <urn:b> { ?s ?q ?r } }",
1645 &door(),
1646 )
1647 .unwrap_err();
1648 assert!(e.to_string().contains("<urn:b>"), "{e}");
1649 assert!(analyze("SELECT * WHERE { ?s ?p ?o }", &door()).is_err());
1651 let a = analyze(
1653 "SELECT * WHERE { ?s ?p ?o }",
1654 &door().default_graphs(["urn:d"]),
1655 )
1656 .unwrap();
1657 assert_eq!(a.graphs.into_iter().collect::<Vec<_>>(), vec!["urn:d"]);
1658 assert!(analyze(
1660 "SELECT * FROM <urn:a> WHERE { ?s ?p ?o GRAPH ?g { ?s ?q ?r } }",
1661 &door()
1662 )
1663 .is_err());
1664 assert!(analyze(
1666 "SELECT * WHERE { SERVICE <http://example.com/sparql> { ?s ?p ?o } }",
1667 &door()
1668 )
1669 .is_err());
1670 }
1671
1672 #[test]
1673 fn an_update_writes_one_named_graph() {
1674 let a = analyze(
1675 "DELETE { GRAPH <urn:g> { ?s <urn:p> ?o } } WHERE { GRAPH <urn:g> { ?s <urn:p> ?o } }",
1676 &door(),
1677 )
1678 .unwrap();
1679 assert_eq!(a.form, Form::Update);
1680 assert!(a.update_reads);
1681 assert_eq!(
1682 a.requires().into_iter().collect::<Vec<_>>(),
1683 vec![
1684 "urn:cap:store:read:graph:urn:g".to_string(),
1685 "urn:cap:store:write:graph:urn:g".to_string()
1686 ]
1687 );
1688 let a = analyze(
1689 "INSERT DATA { GRAPH <urn:g> { <urn:a> <urn:b> 1 } }",
1690 &door(),
1691 )
1692 .unwrap();
1693 assert!(!a.update_reads);
1694 let a = analyze(
1695 "WITH <urn:g> DELETE { ?s <urn:p> ?o } WHERE { ?s <urn:p> ?o }",
1696 &door(),
1697 )
1698 .unwrap();
1699 assert_eq!(a.graphs.into_iter().collect::<Vec<_>>(), vec!["urn:g"]);
1700 for refused in [
1701 "INSERT DATA { <urn:a> <urn:b> 1 }",
1702 "INSERT DATA { GRAPH <urn:g> { <urn:a> <urn:b> 1 } GRAPH <urn:h> { <urn:a> <urn:b> 1 } }",
1703 "DROP ALL",
1704 "COPY <urn:a> TO <urn:b>",
1705 "LOAD <http://example.com/x.ttl> INTO GRAPH <urn:g>",
1706 "DELETE { GRAPH ?g { ?s ?p ?o } } WHERE { GRAPH ?g { ?s ?p ?o } }",
1707 ] {
1708 assert!(analyze(refused, &door()).is_err(), "{refused}");
1709 }
1710 }
1711
1712 #[test]
1713 fn parameters_are_declared_in_the_header_and_checked() {
1714 let text = "# @param days xsd:integer default 7 -- how long\n\
1715 # @param who <urn:class:Person>\n\
1716 # @param note xsd:string default \"two words\"\n\
1717 SELECT * WHERE { GRAPH <urn:g> { ?who <urn:p> ?d FILTER(?d > ?days && ?note != \"\") } }";
1718 let a = analyze(text, &door()).unwrap();
1719 let names: Vec<_> = a.parameters.iter().map(|p| p.name.as_str()).collect();
1720 assert_eq!(names, vec!["days", "who", "note"]);
1721 assert_eq!(a.parameters[0].summary.as_deref(), Some("how long"));
1722 assert!(a.parameters[1].required);
1723 assert_eq!(a.parameters[2].default.as_deref(), Some("two words"));
1724
1725 for (bad, why) in [
1726 ("# @param x xsd:integer\nSELECT * WHERE { GRAPH <urn:g> { ?s ?p ?o } }", "unused"),
1727 ("# @param x xsd:nope\nSELECT * WHERE { GRAPH <urn:g> { ?s ?p ?x } }", "type"),
1728 ("# @param x xsd:integer default seven\nSELECT * WHERE { GRAPH <urn:g> { ?s ?p ?x } }", "default"),
1729 ("# @param as xsd:integer\nSELECT * WHERE { GRAPH <urn:g> { ?s ?p ?as } }", "reserved"),
1730 ("# @param p xsd:string\nSELECT * WHERE { GRAPH <urn:g> { ?s ?p ?o } }", "predicate"),
1731 ("# @param x xsd:integer\nSELECT * WHERE { GRAPH <urn:g> { ?s ?p ?o } BIND(1 AS ?x) }", "bound"),
1732 ("SELECT * WHERE { GRAPH <urn:g> { ?s ?p ?x } }\n# @param x xsd:integer", "misplaced"),
1733 ] {
1734 assert!(analyze(bad, &door()).is_err(), "{why}: {bad}");
1735 }
1736 }
1737
1738 #[test]
1739 fn binding_substitutes_terms_into_the_algebra() {
1740 let text = "# @param days xsd:integer\n\
1741 SELECT ?s ?days FROM <urn:g> WHERE { ?s <urn:age> ?a FILTER(?a > ?days) }";
1742 let bound = bind(text, &door(), &values(&[("days", int(7))])).unwrap();
1743 assert!(!bound.text.contains("FROM"), "{}", bound.text);
1744 assert!(
1745 !bound.text.contains("?days >") && !bound.text.contains("> ?days"),
1746 "{}",
1747 bound.text
1748 );
1749 assert!(bound.text.contains("AS ?days"), "{}", bound.text);
1750 assert_eq!(
1751 bound.graphs.iter().cloned().collect::<Vec<_>>(),
1752 vec!["urn:g"]
1753 );
1754 SparqlParser::new().parse_query(&bound.text).unwrap();
1756 }
1757
1758 #[test]
1759 fn a_hostile_string_is_one_literal() {
1760 let text = "# @param q xsd:string\n\
1761 SELECT ?v WHERE { GRAPH <urn:g> { ?s ?p ?o } BIND(?q AS ?v) }";
1762 let hostile = "\" } ; DROP ALL ; INSERT DATA { <urn:x> <urn:y> \"";
1763 let p = parameters(text).unwrap().remove(0);
1764 let bound = bind(text, &door(), &values(&[("q", p.value(hostile).unwrap())])).unwrap();
1765 let reparsed = SparqlParser::new().parse_query(&bound.text).unwrap();
1766 assert!(matches!(reparsed, Query::Select { .. }), "{}", bound.text);
1768 assert!(
1769 bound.text.contains("DROP ALL"),
1770 "the value is there, as a literal"
1771 );
1772 assert!(SparqlParser::new().parse_update(&bound.text).is_err());
1773 }
1774
1775 #[test]
1776 fn a_graph_parameter_becomes_a_fixed_graph() {
1777 let text = "# @param graph rdfs:Resource\nSELECT * WHERE { GRAPH ?graph { ?s ?p ?o } }";
1778 let a = analyze(text, &door()).unwrap();
1779 assert!(a.any_graph);
1780 let bound = bind(
1781 text,
1782 &door(),
1783 &values(&[("graph", Value::Iri(NamedNode::new("urn:g").unwrap()))]),
1784 )
1785 .unwrap();
1786 assert!(!bound.any_graph);
1787 assert_eq!(bound.graphs.into_iter().collect::<Vec<_>>(), vec!["urn:g"]);
1788 }
1789}