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