knowrob  2.1.0
A Knowledge Base System for Cognition-enabled Robots
SPARQLQuery.cpp
Go to the documentation of this file.
1 /*
2  * This file is part of KnowRob, please consult
3  * https://github.com/knowrob/knowrob for license details.
4  */
5 
6 #include <sstream>
7 #include "knowrob/semweb/SPARQLQuery.h"
8 #include "knowrob/terms/Atom.h"
9 #include "knowrob/semweb/GraphSequence.h"
10 #include "knowrob/semweb/PrefixRegistry.h"
11 #include "knowrob/semweb/Resource.h"
12 
13 using namespace knowrob;
14 
16  : flags_(flags), varCounter_(0) {
17  std::stringstream os, os_query;
18  selectBegin(os_query);
19  add(os_query, triplePattern);
20  selectEnd(os_query);
21 
22  appendPrefixes(os);
23  os << os_query.str();
24  queryString_ = os.str();
25 }
26 
27 SPARQLQuery::SPARQLQuery(const std::shared_ptr<GraphQuery> &query, SPARQLFlag flags)
28  : flags_(flags), varCounter_(0) {
29  std::stringstream os_query;
30  add(os_query, query->term());
31 
32  std::stringstream os;
33  appendPrefixes(os);
34  selectBegin(os);
35  os << os_query.str();
36  selectEnd(os);
37  if (query->ctx()->queryFlags & QUERY_FLAG_ONE_SOLUTION) {
38  os << "\nLIMIT 1";
39  }
40 
41  queryString_ = os.str();
42 }
43 
44 void SPARQLQuery::appendPrefixes(std::ostream &os) {
45  for (const auto &[alias, uri]: aliases_) {
46  os << "PREFIX " << alias << ": <" << uri << "#>\n";
47  }
48 }
49 
50 
51 void SPARQLQuery::add(std::ostream &os, const std::shared_ptr<GraphTerm> &graphTerm) { // NOLINT
52  switch (graphTerm->termType()) {
53  case GraphTermType::Union: {
54  auto sequence = std::static_pointer_cast<GraphSequence>(graphTerm);
55  bool isFirst = true;
56  for (const auto &term: sequence->terms()) {
57  if (!isFirst) {
58  os << " UNION\n";
59  } else {
60  isFirst = false;
61  }
62  os << " {\n";
63  add(os, term);
64  os << " }\n";
65  }
66  break;
67 
68  }
70  auto sequence = std::static_pointer_cast<GraphSequence>(graphTerm);
71  for (const auto &term: sequence->terms()) {
72  add(os, term);
73  }
74  break;
75  }
77  add(os, *std::static_pointer_cast<GraphPattern>(graphTerm)->value());
78  break;
80  add(os, *std::static_pointer_cast<GraphBuiltin>(graphTerm));
81  break;
82  }
83 }
84 
85 void SPARQLQuery::add(std::ostream &os, const TriplePattern &triplePattern) {
86  os << " ";
87  if (triplePattern.isNegated()) {
88  // Handle negated patterns. There is literature available for negation in SPARQL:
89  // https://ceur-ws.org/Vol-1644/paper11.pdf
90  // The authors list some approaches:
91  // - use OPTIONAL and then check with BOUND that it failed. But that would only do the trick
92  // if the negated pattern actually has some runtime variables, as far as I understand.
93  // - use FILTER NOT EXISTS: `FILTER NOT EXISTS { ?x ?y ?z }`.
94  // - use MINUS: `MINUS { ?x ?y ?z }`.
95  // NOTE: MINUS will not work without a positive statement preceding the negated one
96  // as far as I understand because both can only be used to "eliminate" solutions that were produced before.
97  // Which is actually fine as the KB does order positive/negative literals in the query,
98  // However queries with only negated patterns will not work with this code!
100  // NOTE: redland does not support NOT-EXISTS or MINUS.
101  negationViaOptional(os, triplePattern);
102  } else {
103  negationViaNotExists(os, triplePattern);
104  }
105  } else if (triplePattern.isOptional()) {
106  if (optional(os, triplePattern)) {
107  filter_optional(os, lastVar_, triplePattern.objectTerm(), triplePattern.objectOperator());
108  }
109  } else {
110  where_with_filter(os, triplePattern);
111  }
112  os << '\n';
113 }
114 
115 void SPARQLQuery::comparison(std::ostream &os, const GraphBuiltin &builtin, const char *comparisonOperator) {
116  // Filter using a comparison operator, also succeed if one of the arguments is not bound.
117  // e.g. `FILTER (!BOUND(?begin) || !BOUND(?end) || ?begin < ?end)`
118  os << " FILTER ( ";
119  if (builtin.isOptional()) {
120  if (builtin.arguments()[0]->isVariable()) {
121  os << "!BOUND(?" << std::static_pointer_cast<Variable>(builtin.arguments()[0])->name() << ") || ";
122  }
123  if (builtin.arguments()[1]->isVariable()) {
124  os << "!BOUND(?" << std::static_pointer_cast<Variable>(builtin.arguments()[1])->name() << ") || ";
125  }
126  }
127  os << '(';
128  where(os, builtin.arguments()[0]);
129  os << comparisonOperator << ' ';
130  where(os, builtin.arguments()[1]);
131  os << "))\n";
132 }
133 
134 void SPARQLQuery::bindOneOfIf(std::ostream &os, const GraphBuiltin &builtin, const char *comparisonOperator) {
135  // e.g. `BIND(IF(BOUND(?optional_val) && !(?begin < ?optional_val), ?optional_val, ?begin) AS ?begin)`
136  // NOTE: Here it is assumed that only the second argument could be undefined in the evaluation context.
137  os << " BIND ( IF( (";
138  if (builtin.arguments()[1]->isVariable()) {
139  os << "BOUND(?" << std::static_pointer_cast<Variable>(builtin.arguments()[1])->name() << ") && ";
140  }
141  os << "(";
142  {
143  where(os, builtin.arguments()[0]);
144  os << ' ' << comparisonOperator << ' ';
145  where(os, builtin.arguments()[1]);
146  }
147  os << ")), ";
148  where(os, builtin.arguments()[1]);
149  os << ", ";
150  where(os, builtin.arguments()[0]);
151  os << ") AS ?" << builtin.bindVar()->name() << ")\n";
152 }
153 
154 void SPARQLQuery::add(std::ostream &os, const GraphBuiltin &builtin) {
155  switch (builtin.builtinType()) {
157  comparison(os, builtin, "=");
158  break;
160  comparison(os, builtin, "!=");
161  break;
163  comparison(os, builtin, "<");
164  break;
166  comparison(os, builtin, ">");
167  break;
169  comparison(os, builtin, "<=");
170  break;
172  comparison(os, builtin, ">=");
173  break;
175  os << " BIND (";
176  where(os, builtin.arguments()[0]);
177  os << "AS ?" << builtin.bindVar()->name();
178  os << ")\n";
179  break;
181  bindOneOfIf(os, builtin, ">");
182  break;
184  bindOneOfIf(os, builtin, "<");
185  break;
186  }
187 }
188 
189 void SPARQLQuery::selectBegin(std::ostream &os) {
190  if (variables_.empty()) {
191  os << "SELECT * ";
192  } else {
193  os << "SELECT ";
194  for (const auto &var: variables_) {
195  os << '?' << var << ' ';
196  }
197  }
198  os << '\n';
199  os << "WHERE {\n";
200 }
201 
202 void SPARQLQuery::selectEnd(std::ostream &os) {
203  os << "}";
204 }
205 
206 void SPARQLQuery::filter_optional(std::ostream &os, std::string_view varName, const TermPtr &term, FilterType operatorType) {
207  if (!term->isAtomic()) return;
208  os << "FILTER (";
209  os << " !BOUND(?" << varName << ") || ";
210  doFilter(os, varName, std::static_pointer_cast<Atomic>(term), operatorType);
211  os << ") ";
212 }
213 
214 void SPARQLQuery::filter(std::ostream &os, std::string_view varName, const TermPtr &term, FilterType operatorType) {
215  if (!term->isAtomic()) return;
216  auto atomic = std::static_pointer_cast<Atomic>(term);
217  os << "FILTER (";
218  doFilter(os, varName, std::static_pointer_cast<Atomic>(term), operatorType);
219  os << ") ";
220 }
221 
222 void SPARQLQuery::doFilter(std::ostream &os, std::string_view varName, const std::shared_ptr<Atomic> &atomic, FilterType operatorType) {
223  os << '?' << varName;
224  switch (operatorType) {
225  case FilterType::LT:
226  os << " < ";
227  break;
228  case FilterType::GT:
229  os << " > ";
230  break;
231  case FilterType::LEQ:
232  os << " <= ";
233  break;
234  case FilterType::GEQ:
235  os << " >= ";
236  break;
237  case FilterType::EQ:
238  os << " = ";
239  break;
240  case FilterType::NEQ:
241  os << " != ";
242  break;
243  }
244  if (atomic->isNumeric()) {
245  os << '"' << *atomic << '"' << "^^";
246  iri(os, xsdTypeToIRI(std::static_pointer_cast<XSDAtomic>(atomic)->xsdType()));
247  }
248  else if (atomic->isString()) {
249  os << *atomic << "^^";
250  iri(os, xsdTypeToIRI(std::static_pointer_cast<XSDAtomic>(atomic)->xsdType()));
251  } else {
252  os << atomic->stringForm();
253  }
254  os << " ";
255 }
256 
257 void SPARQLQuery::negationViaNotExists(std::ostream &os, const TriplePattern &triplePattern) {
258  os << "FILTER NOT EXISTS { ";
259  where_with_filter(os, triplePattern);
260  os << "} ";
261 }
262 
263 void SPARQLQuery::negationViaOptional(std::ostream &os, const TriplePattern &triplePattern) {
264  if (triplePattern.objectTerm()->isVariable()) {
265  bool hasObjectOperator = optional(os, triplePattern);
266  os << "FILTER (";
267  if (hasObjectOperator) {
268  auto inverseOperator = inverseFilterType(triplePattern.objectOperator());
269  auto atomic = std::static_pointer_cast<Atomic>(triplePattern.objectTerm());
270  doFilter(os, lastVar_, atomic, inverseOperator);
271  } else {
272  os << " !BOUND(";
273  where(os, triplePattern.objectTerm());
274  os << ") ";
275  }
276  os << ") ";
277  } else {
278  KB_WARN("Negation via optional is only supported for variable objects.");
279  }
280 }
281 
282 bool SPARQLQuery::optional(std::ostream &os, const TriplePattern &triplePattern) {
283  os << "OPTIONAL { ";
284  bool needsFilter = where(os, triplePattern);
285  os << "} ";
286  return needsFilter;
287 }
288 
289 void SPARQLQuery::iri(std::ostream &os, std::string_view iri) {
290  auto ns = semweb::Resource::iri_ns(iri);
291  bool hasAlias = false;
292  if (!ns.empty()) {
293  auto alias = PrefixRegistry::uriToAlias(ns);
294  if (alias.has_value()) {
295  auto &v_alias = alias.value().get();
296  os << v_alias << ':' << semweb::Resource::iri_name(iri);
297  aliases_[v_alias] = ns;
298  hasAlias = true;
299  }
300  }
301  if (!hasAlias) {
302  os << '<' << iri << '>';
303  }
304  os << ' ';
305 }
306 
307 void SPARQLQuery::where_with_filter(std::ostream &os, const TriplePattern &triplePattern) {
308  if (where(os, triplePattern)) {
309  filter(os, lastVar_, triplePattern.objectTerm(), triplePattern.objectOperator());
310  }
311 }
312 
313 bool SPARQLQuery::where(std::ostream &os, const TriplePattern &triplePattern) {
314  where(os, triplePattern.subjectTerm());
315  where(os, triplePattern.propertyTerm());
316  if (triplePattern.objectTerm()->isVariable() || triplePattern.objectOperator() == FilterType::EQ) {
317  if (!triplePattern.objectTerm()->isVariable() && triplePattern.objectVariable()) {
318  where(os, triplePattern.objectVariable());
319  os << ". ";
320  lastVar_ = triplePattern.objectVariable()->name();
321  return true;
322  } else {
323  where(os, triplePattern.objectTerm());
324  os << ". ";
325  return false;
326  }
327  } else if (triplePattern.objectVariable()) {
328  // value, operator and variable are provided in the pattern
329  lastVar_ = triplePattern.objectVariable()->name();
330  variables_.insert(triplePattern.objectVariable()->name());
331  os << "?" << lastVar_ << " . ";
332  return true;
333  } else {
334  // we need to introduce a temporary variable to handle value expressions like `<(5)` such
335  // that they can be filtered after the match.
336  static const std::string adhocVarPrefix = "v_adhoc";
337  std::stringstream varName_os;
338  varName_os << adhocVarPrefix << (varCounter_++);
339  lastVar_ = varName_os.str();
340  os << "?" << lastVar_ << " . ";
341  return true;
342  }
343 }
344 
345 void SPARQLQuery::where(std::ostream &os, const TermPtr &term) {
346  switch (term->termType()) {
347  case TermType::VARIABLE: {
348  auto var = std::static_pointer_cast<Variable>(term);
349  variables_.insert(var->name());
350  os << "?" << var->name() << " ";
351  break;
352  }
353  case TermType::ATOMIC: {
354  if (term->isIRI()) {
355  iri(os, std::static_pointer_cast<Atomic>(term)->stringForm());
356  break;
357  } else if (term->isBlank()) {
358  os << "_:" << std::static_pointer_cast<Atomic>(term)->stringForm() << " ";
359  break;
360  }
361  [[fallthrough]];
362  }
363  default:
364  if (term->isNumeric() || term->isString()) {
365  auto xsdAtomic = std::static_pointer_cast<XSDAtomic>(term);
366  if (xsdAtomic->xsdType() == XSDType::BOOLEAN) {
367  auto numeric = std::static_pointer_cast<Numeric>(term);
368  os << (numeric->asBoolean() ? "\"true\"" : "\"false\"");
369  } else if (xsdAtomic->isString()) {
370  os << *term;
371  } else {
372  os << '"' << *term << '"';
373  }
374  os << "^^";
375  iri(os, xsdTypeToIRI(xsdAtomic->xsdType()));
376  os << " ";
377  } else if (term->termType() == TermType::ATOMIC) {
378  // handle the case that an IRI was not handed in as an IRIAtom
379  iri(os, std::static_pointer_cast<Atomic>(term)->stringForm());
380  } else {
381  KB_WARN("Invalid term `{}` in SPARQL query.", *term);
382  }
383  break;
384  }
385 }
386 
387 SPARQLFlag knowrob::operator|(SPARQLFlag a, SPARQLFlag b) {
388  return static_cast<SPARQLFlag>(static_cast<std::uint8_t>(a) | static_cast<std::uint8_t>(b));
389 }
390 
391 bool knowrob::operator&(SPARQLFlag a, SPARQLFlag b) {
392  return static_cast<std::uint8_t>(a) & static_cast<std::uint8_t>(b);
393 }
#define KB_WARN
Definition: Logger.h:27
auto & arguments() const
Definition: Function.h:47
auto bindVar() const
Definition: GraphBuiltin.h:113
auto builtinType() const
Definition: GraphBuiltin.h:108
bool isOptional() const
Definition: GraphBuiltin.h:246
static OptionalStringRef uriToAlias(std::string_view uri)
void comparison(std::ostream &os, const GraphBuiltin &builtin, const char *comparisonOperator)
bool optional(std::ostream &os, const TriplePattern &triplePattern)
std::map< std::string_view, std::string_view > aliases_
Definition: SPARQLQuery.h:74
void filter(std::ostream &os, std::string_view varName, const TermPtr &term, FilterType operatorType)
void bindOneOfIf(std::ostream &os, const GraphBuiltin &builtin, const char *comparisonOperator)
void doFilter(std::ostream &os, std::string_view varName, const std::shared_ptr< Atomic > &atomic, FilterType operatorType)
void selectBegin(std::ostream &os)
SPARQLQuery(const TriplePattern &triplePattern, SPARQLFlags flags=SPARQLFlag::NOTHING)
Definition: SPARQLQuery.cpp:15
void where_with_filter(std::ostream &os, const TriplePattern &triplePattern)
std::string lastVar_
Definition: SPARQLQuery.h:77
void iri(std::ostream &os, std::string_view iri)
std::set< std::string_view > variables_
Definition: SPARQLQuery.h:75
SPARQLFlags flags_
Definition: SPARQLQuery.h:72
void appendPrefixes(std::ostream &os)
Definition: SPARQLQuery.cpp:44
bool where(std::ostream &os, const TriplePattern &triplePattern)
void negationViaNotExists(std::ostream &os, const TriplePattern &triplePattern)
static void selectEnd(std::ostream &os)
void filter_optional(std::ostream &os, std::string_view varName, const TermPtr &term, FilterType operatorType)
void add(std::ostream &os, const TriplePattern &triplePattern)
Definition: SPARQLQuery.cpp:85
std::string queryString_
Definition: SPARQLQuery.h:76
void negationViaOptional(std::ostream &os, const TriplePattern &triplePattern)
auto objectOperator() const
auto & objectTerm() const
Definition: TriplePattern.h:97
static std::string_view iri_name(std::string_view iri)
Definition: Resource.cpp:57
static std::string_view iri_ns(std::string_view iri, bool includeDelimiter=false)
Definition: Resource.cpp:69
GraphTermRule & graphTerm()
Definition: graph.cpp:77
VariableRule & var()
Definition: terms.cpp:91
TermRule & atomic()
Definition: terms.cpp:79
TermRule & term()
Definition: terms.cpp:136
std::string_view xsdTypeToIRI(XSDType type)
Definition: XSDAtomic.cpp:70
@ QUERY_FLAG_ONE_SOLUTION
Definition: QueryFlag.h:17
std::shared_ptr< Term > TermPtr
Definition: Term.h:117
FilterType inverseFilterType(FilterType op)
IRIAtomPtr iri(std::string_view ns, std::string_view name)
Definition: IRIAtom.cpp:62