1
2
3
4
5
6
7
8
9
10
11
12
13
14 package eu.fbk.rdfpro.util;
15
16 import java.util.ArrayList;
17 import java.util.Collections;
18 import java.util.HashMap;
19 import java.util.HashSet;
20 import java.util.LinkedHashSet;
21 import java.util.List;
22 import java.util.Map;
23 import java.util.Objects;
24 import java.util.Set;
25 import java.util.concurrent.atomic.AtomicInteger;
26 import java.util.concurrent.atomic.AtomicReference;
27
28 import javax.annotation.Nullable;
29
30 import org.openrdf.model.BNode;
31 import org.openrdf.model.Literal;
32 import org.openrdf.model.URI;
33 import org.openrdf.model.impl.URIImpl;
34 import org.openrdf.model.vocabulary.FN;
35 import org.openrdf.model.vocabulary.XMLSchema;
36 import org.openrdf.query.BindingSet;
37 import org.openrdf.query.Dataset;
38 import org.openrdf.query.algebra.Add;
39 import org.openrdf.query.algebra.And;
40 import org.openrdf.query.algebra.ArbitraryLengthPath;
41 import org.openrdf.query.algebra.Avg;
42 import org.openrdf.query.algebra.BNodeGenerator;
43 import org.openrdf.query.algebra.BinaryValueOperator;
44 import org.openrdf.query.algebra.BindingSetAssignment;
45 import org.openrdf.query.algebra.Bound;
46 import org.openrdf.query.algebra.Clear;
47 import org.openrdf.query.algebra.Coalesce;
48 import org.openrdf.query.algebra.Compare;
49 import org.openrdf.query.algebra.Compare.CompareOp;
50 import org.openrdf.query.algebra.CompareAll;
51 import org.openrdf.query.algebra.CompareAny;
52 import org.openrdf.query.algebra.Copy;
53 import org.openrdf.query.algebra.Count;
54 import org.openrdf.query.algebra.Create;
55 import org.openrdf.query.algebra.Datatype;
56 import org.openrdf.query.algebra.DeleteData;
57 import org.openrdf.query.algebra.DescribeOperator;
58 import org.openrdf.query.algebra.Difference;
59 import org.openrdf.query.algebra.Distinct;
60 import org.openrdf.query.algebra.EmptySet;
61 import org.openrdf.query.algebra.Exists;
62 import org.openrdf.query.algebra.Extension;
63 import org.openrdf.query.algebra.ExtensionElem;
64 import org.openrdf.query.algebra.Filter;
65 import org.openrdf.query.algebra.FunctionCall;
66 import org.openrdf.query.algebra.Group;
67 import org.openrdf.query.algebra.GroupConcat;
68 import org.openrdf.query.algebra.GroupElem;
69 import org.openrdf.query.algebra.IRIFunction;
70 import org.openrdf.query.algebra.If;
71 import org.openrdf.query.algebra.In;
72 import org.openrdf.query.algebra.InsertData;
73 import org.openrdf.query.algebra.Intersection;
74 import org.openrdf.query.algebra.IsBNode;
75 import org.openrdf.query.algebra.IsLiteral;
76 import org.openrdf.query.algebra.IsNumeric;
77 import org.openrdf.query.algebra.IsResource;
78 import org.openrdf.query.algebra.IsURI;
79 import org.openrdf.query.algebra.Join;
80 import org.openrdf.query.algebra.Label;
81 import org.openrdf.query.algebra.Lang;
82 import org.openrdf.query.algebra.LangMatches;
83 import org.openrdf.query.algebra.LeftJoin;
84 import org.openrdf.query.algebra.Like;
85 import org.openrdf.query.algebra.ListMemberOperator;
86 import org.openrdf.query.algebra.Load;
87 import org.openrdf.query.algebra.LocalName;
88 import org.openrdf.query.algebra.MathExpr;
89 import org.openrdf.query.algebra.MathExpr.MathOp;
90 import org.openrdf.query.algebra.Max;
91 import org.openrdf.query.algebra.Min;
92 import org.openrdf.query.algebra.Modify;
93 import org.openrdf.query.algebra.Move;
94 import org.openrdf.query.algebra.MultiProjection;
95 import org.openrdf.query.algebra.Namespace;
96 import org.openrdf.query.algebra.Not;
97 import org.openrdf.query.algebra.Or;
98 import org.openrdf.query.algebra.Order;
99 import org.openrdf.query.algebra.OrderElem;
100 import org.openrdf.query.algebra.Projection;
101 import org.openrdf.query.algebra.ProjectionElem;
102 import org.openrdf.query.algebra.ProjectionElemList;
103 import org.openrdf.query.algebra.QueryModelNode;
104 import org.openrdf.query.algebra.QueryModelVisitor;
105 import org.openrdf.query.algebra.QueryRoot;
106 import org.openrdf.query.algebra.Reduced;
107 import org.openrdf.query.algebra.Regex;
108 import org.openrdf.query.algebra.SameTerm;
109 import org.openrdf.query.algebra.Sample;
110 import org.openrdf.query.algebra.Service;
111 import org.openrdf.query.algebra.SingletonSet;
112 import org.openrdf.query.algebra.Slice;
113 import org.openrdf.query.algebra.StatementPattern;
114 import org.openrdf.query.algebra.StatementPattern.Scope;
115 import org.openrdf.query.algebra.Str;
116 import org.openrdf.query.algebra.Sum;
117 import org.openrdf.query.algebra.TupleExpr;
118 import org.openrdf.query.algebra.UnaryTupleOperator;
119 import org.openrdf.query.algebra.Union;
120 import org.openrdf.query.algebra.ValueConstant;
121 import org.openrdf.query.algebra.ValueExpr;
122 import org.openrdf.query.algebra.Var;
123 import org.openrdf.query.algebra.ZeroLengthPath;
124 import org.openrdf.query.algebra.helpers.QueryModelVisitorBase;
125
126 final class SPARQLRenderer {
127
128 private static final Map<String, String> NAMES;
129
130 static {
131 final Map<String, String> names = new HashMap<>();
132 names.put("RAND", "RAND");
133 names.put("TZ", "TZ");
134 names.put("NOW", "NOW");
135 names.put("UUID", "UUID");
136 names.put("STRUUID", "STRUUID");
137 names.put("MD5", "MD5");
138 names.put("SHA1", "SHA1");
139 names.put("SHA256", "SHA256");
140 names.put("SHA384", "SHA384");
141 names.put("SHA512", "SHA512");
142 names.put("STRLANG", "STRLANG");
143 names.put("STRDT", "STRDT");
144 names.put(FN.STRING_LENGTH.stringValue(), "STRLEN");
145 names.put(FN.SUBSTRING.stringValue(), "SUBSTR");
146 names.put(FN.UPPER_CASE.stringValue(), "UCASE");
147 names.put(FN.LOWER_CASE.stringValue(), "LCASE");
148 names.put(FN.STARTS_WITH.stringValue(), "STRSTARTS");
149 names.put(FN.ENDS_WITH.stringValue(), "STRENDS");
150 names.put(FN.CONTAINS.stringValue(), "CONTAINS");
151 names.put(FN.SUBSTRING_BEFORE.stringValue(), "STRBEFORE");
152 names.put(FN.SUBSTRING_AFTER.stringValue(), "STRAFTER");
153 names.put(FN.ENCODE_FOR_URI.stringValue(), "ENCODE_FOR_URI");
154 names.put(FN.CONCAT.stringValue(), "CONCAT");
155 names.put(FN.NAMESPACE + "matches", "REGEX");
156 names.put(FN.REPLACE.stringValue(), "REPLACE");
157 names.put(FN.NUMERIC_ABS.stringValue(), "ABS");
158 names.put(FN.NUMERIC_ROUND.stringValue(), "ROUND");
159 names.put(FN.NUMERIC_CEIL.stringValue(), "CEIL");
160 names.put(FN.NUMERIC_FLOOR.stringValue(), "FLOOR");
161 names.put(FN.YEAR_FROM_DATETIME.stringValue(), "YEAR");
162 names.put(FN.MONTH_FROM_DATETIME.stringValue(), "MONTH");
163 names.put(FN.DAY_FROM_DATETIME.stringValue(), "DAY");
164 names.put(FN.HOURS_FROM_DATETIME.stringValue(), "HOURS");
165 names.put(FN.MINUTES_FROM_DATETIME.stringValue(), "MINUTES");
166 names.put(FN.SECONDS_FROM_DATETIME.stringValue(), "SECONDS");
167 names.put(FN.TIMEZONE_FROM_DATETIME.stringValue(), "TIMEZONE");
168 NAMES = Collections.unmodifiableMap(names);
169
170 }
171
172 private final Map<String, String> prefixes;
173
174 private final boolean forceSelect;
175
176 public SPARQLRenderer(@Nullable final Map<String, String> prefixes,
177 @Nullable final boolean forceSelect) {
178 this.prefixes = prefixes != null ? prefixes : Collections.<String, String>emptyMap();
179 this.forceSelect = forceSelect;
180 }
181
182 public String render(final TupleExpr expr, @Nullable final Dataset dataset) {
183 final Rendering rendering = new Rendering(Algebra.normalizeVars(expr), dataset, true);
184 final StringBuilder builder = new StringBuilder();
185 boolean newline = false;
186 if (!rendering.namespaces.isEmpty()) {
187 final List<String> sortedNamespaces = new ArrayList<>(rendering.namespaces);
188 Collections.sort(sortedNamespaces);
189 for (final String namespace : sortedNamespaces) {
190 final String prefix = this.prefixes.get(namespace);
191 if ("bif".equals(prefix) && "http://www.openlinksw.com/schema/sparql/extensions#" //
192 .equals(namespace)) {
193 continue;
194 }
195 builder.append("PREFIX ").append(prefix).append(": <");
196 escape(namespace, builder);
197 builder.append(">\n");
198 newline = true;
199 }
200 }
201 if (rendering.base != null) {
202 builder.append("BASE <");
203 escape(rendering.base, builder);
204 builder.append(">\n");
205 newline = true;
206 }
207 if (newline) {
208 builder.append("\n");
209 }
210 builder.append(rendering.body);
211 return builder.toString();
212 }
213
214 public String renderTupleExpr(final TupleExpr expr) {
215 return new Rendering(Algebra.normalizeVars(expr), null, false).body;
216 }
217
218
219
220 private static void escape(final String label, final StringBuilder builder) {
221 final int length = label.length();
222 for (int i = 0; i < length; ++i) {
223 final char c = label.charAt(i);
224 if (c == '\\') {
225 builder.append("\\\\");
226 } else if (c == '"') {
227 builder.append("\\\"");
228 } else if (c == '\n') {
229 builder.append("\\n");
230 } else if (c == '\r') {
231 builder.append("\\r");
232 } else if (c == '\t') {
233 builder.append("\\t");
234 }
235
236
237
238
239
240
241
242
243
244 else {
245 builder.append(Character.toString(c));
246 }
247 }
248 }
249
250 private static String sanitize(final String string) {
251 final int length = string.length();
252 final StringBuilder builder = new StringBuilder(length + 10);
253 for (int i = 0; i < length; ++i) {
254 final char ch = string.charAt(i);
255 if (Character.isLetter(ch) || ch == '_' || i > 0 && Character.isDigit(ch)) {
256 builder.append(ch);
257 } else {
258 builder.append("_");
259 }
260 }
261 return builder.toString();
262 }
263
264 private static <T> boolean equalOrNull(@Nullable final T first, @Nullable final T second) {
265 return first != null && first.equals(second) || first == null && second == null;
266 }
267
268 private static <T> T defaultIfNull(@Nullable final T value, @Nullable final T defaultValue) {
269 return value != null ? value : defaultValue;
270 }
271
272 private static List<StatementPattern> getBGP(final QueryModelNode n) {
273 if (n instanceof StatementPattern) {
274 return Collections.singletonList((StatementPattern) n);
275 } else if (!(n instanceof Join)) {
276 return null;
277 }
278 final Join j = (Join) n;
279 final List<StatementPattern> l = getBGP(j.getLeftArg());
280 final List<StatementPattern> r = getBGP(j.getRightArg());
281 if (l == null || r == null) {
282 return null;
283 }
284 if (l.isEmpty()) {
285 return r;
286 } else if (r.isEmpty()) {
287 return l;
288 } else if (!equalOrNull(l.get(0).getContextVar(), r.get(0).getContextVar())) {
289 return null;
290 } else {
291 final List<StatementPattern> s = new ArrayList<StatementPattern>(l.size() + r.size());
292 s.addAll(l);
293 s.addAll(r);
294 return s;
295 }
296 }
297
298 private static int getVarRefs(final QueryModelNode node, final String name) {
299 final AtomicInteger count = new AtomicInteger(0);
300 node.visit(new QueryModelVisitorBase<RuntimeException>() {
301
302 @Override
303 public void meet(final Var var) {
304 if (var.getName().equals(name)) {
305 count.set(count.get() + 1);
306 }
307 }
308
309 });
310 return count.get();
311 }
312
313 private static ValueExpr getVarExpr(final QueryModelNode node, final String name) {
314 final AtomicReference<ValueExpr> result = new AtomicReference<ValueExpr>(null);
315 node.visit(new QueryModelVisitorBase<RuntimeException>() {
316
317 @Override
318 protected void meetNode(final QueryModelNode node) throws RuntimeException {
319 if (result.get() == null) {
320 super.meetNode(node);
321 }
322 }
323
324 @Override
325 public void meet(final Var var) {
326 if (var.getName().equals(name) && var.getValue() != null) {
327 result.set(new ValueConstant(var.getValue()));
328 }
329 }
330
331 @Override
332 public void meet(final ExtensionElem node) throws RuntimeException {
333 if (node.getName().equals(name)) {
334 result.set(node.getExpr());
335 } else {
336 super.meet(node);
337 }
338 }
339
340 });
341 return result.get();
342 }
343
344 private static void check(final boolean condition, final String errorMessage) {
345 if (!condition) {
346 throw new IllegalArgumentException(errorMessage);
347 }
348 }
349
350 private static <T> List<T> list(final Iterable<? extends T> iterable) {
351 final List<T> result = new ArrayList<>();
352 for (final T element : iterable) {
353 result.add(element);
354 }
355 return result;
356 }
357
358 private final class Rendering implements QueryModelVisitor<RuntimeException> {
359
360 final TupleExpr root;
361
362 @Nullable
363 final Dataset dataset;
364
365 final String body;
366
367 String base;
368
369 private final StringBuilder builder;
370
371 private final Set<String> namespaces;
372
373 private int indent;
374
375 Rendering(final TupleExpr node, @Nullable final Dataset dataset, final boolean query) {
376 this.root = new QueryRoot(Objects.requireNonNull(node));
377 this.dataset = dataset;
378 this.builder = new StringBuilder();
379 this.namespaces = new HashSet<>();
380 this.indent = 0;
381 if (query) {
382 emit(Query.create(this.root, this.dataset, SPARQLRenderer.this.forceSelect));
383 } else {
384 emit(node);
385 }
386 this.body = this.builder.toString();
387 this.builder.setLength(0);
388 }
389
390
391
392 private Rendering emitIf(final boolean condition, final Object object) {
393 if (condition) {
394 emit(object);
395 }
396 return this;
397 }
398
399 private Rendering emit(final Iterable<?> values, final String separator) {
400 boolean first = true;
401 for (final Object value : values) {
402 if (!first) {
403 emit(separator);
404 }
405 emit(value);
406 first = false;
407 }
408 return this;
409 }
410
411 @SuppressWarnings("unchecked")
412 private Rendering emit(final Object value) {
413 if (value instanceof String) {
414 return emit((String) value);
415 } else if (value instanceof QueryModelNode) {
416 emit((QueryModelNode) value);
417 } else if (value instanceof BNode) {
418 emit((BNode) value);
419 } else if (value instanceof URI) {
420 emit((URI) value);
421 } else if (value instanceof Literal) {
422 emit((Literal) value);
423 } else if (value instanceof List<?>) {
424 emit((List<StatementPattern>) value);
425 } else if (value instanceof Query) {
426 emit((Query) value);
427 }
428 return this;
429 }
430
431 private Rendering emit(final String string) {
432 this.builder.append(string);
433 return this;
434 }
435
436 private Rendering emit(final Literal literal) {
437 if (XMLSchema.INTEGER.equals(literal.getDatatype())) {
438 this.builder.append(literal.getLabel());
439 } else {
440 this.builder.append("\"");
441 escape(literal.getLabel(), this.builder);
442 this.builder.append("\"");
443 if (literal.getLanguage() != null) {
444 this.builder.append("@").append(literal.getLanguage());
445 } else if (literal.getDatatype() != null) {
446 this.builder.append("^^");
447 emit(literal.getDatatype());
448 }
449 }
450 return this;
451 }
452
453 private Rendering emit(final BNode bnode) {
454 this.builder.append("_:").append(bnode.getID());
455 return this;
456 }
457
458 private Rendering emit(final URI uri) {
459 if (uri.getNamespace().equals("http://www.openlinksw.com/schema/sparql/extensions#")) {
460 this.builder.append("bif:").append(uri.getLocalName());
461 } else {
462 final String prefix = SPARQLRenderer.this.prefixes.get(uri.getNamespace());
463 if (prefix != null) {
464 if (this.namespaces != null) {
465 this.namespaces.add(uri.getNamespace());
466 }
467 this.builder.append(prefix).append(':').append(uri.getLocalName());
468 } else {
469 this.builder.append("<");
470 escape(uri.toString(), this.builder);
471 this.builder.append(">");
472 }
473 }
474 return this;
475 }
476
477 private Rendering emit(final List<StatementPattern> bgp) {
478 if (bgp.isEmpty()) {
479 return this;
480 }
481 final Var c = bgp.get(0).getContextVar();
482 if (c != null) {
483 emit("GRAPH ").emit(c).emit(" ").openBrace();
484 }
485 StatementPattern l = null;
486 for (final StatementPattern n : bgp) {
487 final Var s = n.getSubjectVar();
488 final Var p = n.getPredicateVar();
489 final Var o = n.getObjectVar();
490 if (l == null) {
491 emit(s).emit(" ").emit(p).emit(" ").emit(o);
492 } else if (!l.getSubjectVar().equals(n.getSubjectVar())) {
493 emit(" .").newline().emit(s).emit(" ").emit(p).emit(" ").emit(o);
494 } else if (!l.getPredicateVar().equals(n.getPredicateVar())) {
495 emit(" ;").newline().emit("\t").emit(p).emit(" ").emit(o);
496 } else if (!l.getObjectVar().equals(n.getObjectVar())) {
497 emit(" , ").emit(o);
498 }
499 l = n;
500 }
501 emit(" .");
502 if (c != null) {
503 closeBrace();
504 }
505 return this;
506 }
507
508 private Rendering emit(final Query query) {
509 if (query.root != this.root) {
510 openBrace();
511 }
512 if (query.form == Form.ASK) {
513 emit("ASK");
514 } else if (query.form == Form.CONSTRUCT) {
515 emit("CONSTRUCT ").openBrace().emit(query.construct).closeBrace();
516 } else if (query.form == Form.DESCRIBE) {
517 emit("DESCRIBE");
518 for (final ProjectionElem p : query.select) {
519 final ExtensionElem e = p.getSourceExpression();
520 emit(" ").emit(
521 e != null && e.getExpr() instanceof ValueConstant ? e.getExpr() : p);
522 }
523 } else if (query.form == Form.SELECT) {
524 emit("SELECT");
525 if (query.modifier != null) {
526 emit(" ").emit(query.modifier.toString().toUpperCase());
527 }
528 if (query.select.isEmpty()) {
529 int count = 0;
530 for (final String var : query.where.getBindingNames()) {
531 final ValueExpr expr = getVarExpr(query.where, var);
532 if (!var.startsWith("-")) {
533 if (expr == null) {
534 emit(" ?").emit(var);
535 } else {
536 emit(" (").emit(expr).emit(" AS ?").emit(var).emit(")");
537 }
538 ++count;
539 }
540 }
541 if (count == 0) {
542 emit(" *");
543 }
544 } else {
545 emit(" ").emit(query.select, " ");
546 }
547 }
548 if (query.from != null) {
549 for (final URI uri : query.from.getDefaultGraphs()) {
550 newline().emit("FROM ").emit(uri);
551 }
552 for (final URI uri : query.from.getNamedGraphs()) {
553 newline().emit("FROM NAMED ").emit(uri);
554 }
555 }
556 if (query.form != Form.DESCRIBE || !(query.where instanceof SingletonSet)) {
557 newline().emit("WHERE ").openBrace().emit(query.where).closeBrace();
558 }
559 if (!query.groupBy.isEmpty()) {
560 newline().emit("GROUP BY");
561 for (final ProjectionElem n : query.groupBy) {
562 emit(" ?").emit(n.getTargetName());
563 }
564 }
565 if (query.having != null) {
566 newline().emit("HAVING (").emit(query.having).emit(")");
567 }
568 if (!query.orderBy.isEmpty()) {
569 newline().emit("ORDER BY ").emit(query.orderBy, " ");
570 }
571 if (query.form != Form.ASK) {
572 if (query.offset != null) {
573 newline().emit("OFFSET " + query.offset);
574 }
575 if (query.limit != null) {
576 newline().emit("LIMIT " + query.limit);
577
578 }
579 }
580 if (query.root != this.root) {
581 closeBrace();
582 }
583 return this;
584 }
585
586 private Rendering emit(final QueryModelNode n) {
587 final QueryModelNode p = n.getParentNode();
588 final boolean braces = n instanceof TupleExpr && p != null
589 && !(p instanceof TupleExpr);
590 if (braces) {
591 openBrace();
592 }
593 n.visit(this);
594 if (braces) {
595 closeBrace();
596 }
597 return this;
598 }
599
600 private Rendering emit(final QueryModelNode node, final boolean parenthesis) {
601 if (parenthesis) {
602 if (node instanceof TupleExpr) {
603 openBrace();
604 } else {
605 emit("(");
606 }
607 }
608 emit(node);
609 if (parenthesis) {
610 if (node instanceof TupleExpr) {
611 closeBrace();
612 } else {
613 emit(")");
614 }
615 }
616 return this;
617 }
618
619 private Rendering openBrace() {
620 emit("{");
621 ++this.indent;
622 newline();
623 return this;
624 }
625
626 private Rendering closeBrace() {
627 --this.indent;
628 newline();
629 emit("}");
630 return this;
631 }
632
633 private Rendering newline() {
634 emit("\n");
635 for (int i = 0; i < this.indent; ++i) {
636 emit("\t");
637 }
638 return this;
639 }
640
641 private Rendering fail(final String message, final QueryModelNode node) {
642 throw new IllegalArgumentException("SPARQL rendering failed. " + message
643 + (node == null ? "null" : node.getClass().getSimpleName() + "\n" + node));
644 }
645
646
647
648 @Override
649 public void meet(final OrderElem n) {
650 emit(n.isAscending() ? "ASC(" : "DESC(").emit(n.getExpr()).emit(")");
651 }
652
653 @Override
654 public void meet(final ProjectionElemList node) {
655 emit(node.getElements(), " ");
656 }
657
658 @Override
659 public void meet(final ProjectionElem n) {
660
661 final String source = n.getSourceName();
662 final String target = n.getTargetName();
663 final ValueExpr expr = n.getSourceExpression() == null ? null : n
664 .getSourceExpression().getExpr();
665
666 if (target.startsWith("-")) {
667 if (expr != null) {
668 emit("(").emit(expr).emit(" AS ?").emit(sanitize(target)).emit(")");
669 }
670 } else if (expr != null) {
671 emit("(").emit(expr).emit(" AS ?").emit(target).emit(")");
672 } else if (!equalOrNull(source, target)) {
673 emit("(?").emit(source).emit(" AS ?").emit(target).emit(")");
674 } else {
675 emit("?").emit(target);
676 }
677 }
678
679 @Override
680 public void meet(final GroupElem n) {
681 final ProjectionElem e = new ProjectionElem();
682 e.setTargetName(n.getName());
683 e.setSourceName(n.getName());
684 e.setSourceExpression(new ExtensionElem(n.getOperator(), n.getName()));
685 meet(e);
686 }
687
688 @Override
689 public void meet(final DescribeOperator n) {
690 emit(Query.create(n, null, SPARQLRenderer.this.forceSelect));
691 }
692
693 @Override
694 public void meet(final QueryRoot n) {
695 emit(Query.create(n, null, SPARQLRenderer.this.forceSelect));
696 }
697
698 @Override
699 public void meet(final Projection n) {
700 emit(Query.create(n, null, SPARQLRenderer.this.forceSelect));
701 }
702
703 @Override
704 public void meet(final MultiProjection n) {
705 emit(Query.create(n, null, SPARQLRenderer.this.forceSelect));
706 }
707
708 @Override
709 public void meet(final Distinct n) {
710 emit(Query.create(n, null, SPARQLRenderer.this.forceSelect));
711 }
712
713 @Override
714 public void meet(final Reduced n) {
715 emit(Query.create(n, null, SPARQLRenderer.this.forceSelect));
716 }
717
718 @Override
719 public void meet(final Group n) {
720 emit(Query.create(n, null, SPARQLRenderer.this.forceSelect));
721 }
722
723 @Override
724 public void meet(final Order n) {
725 emit(Query.create(n, null, SPARQLRenderer.this.forceSelect));
726 }
727
728 @Override
729 public void meet(final Slice n) {
730 emit(Query.create(n, null, SPARQLRenderer.this.forceSelect));
731 }
732
733
734
735 @Override
736 public void meet(final EmptySet n) {
737 final QueryModelNode p = n.getParentNode();
738 if (p.getParentNode() != null && !(p.getParentNode() instanceof QueryRoot)) {
739 throw new IllegalArgumentException(
740 "Cannot translate EmptySet inside the body of a query / update operation");
741 }
742 emit("CONSTRUCT {} WHERE {}");
743 }
744
745 @Override
746 public void meet(final SingletonSet n) {
747
748 }
749
750 @Override
751 public void meet(final BindingSetAssignment n) {
752
753 final Set<String> names = n.getBindingNames();
754
755 if (names.isEmpty()) {
756 emit("VALUES {}");
757
758 } else if (names.size() == 1) {
759 final String name = names.iterator().next();
760 emit("VALUES ?").emit(name).emit(" ").openBrace();
761 boolean first = true;
762 for (final BindingSet bindings : n.getBindingSets()) {
763 emitIf(!first, " ").emit(defaultIfNull(bindings.getValue(name), "UNDEF"));
764 first = false;
765 }
766 closeBrace();
767
768 } else {
769 emit("VALUES (?").emit(names, " ?").emit(") ").openBrace();
770 boolean firstBinding = true;
771 for (final BindingSet bindings : n.getBindingSets()) {
772 if (!firstBinding) {
773 newline();
774 }
775 emit("(");
776 boolean first = true;
777 for (final String name : names) {
778 emitIf(!first, " ").emit(defaultIfNull(bindings.getValue(name), "UNDEF"));
779 first = false;
780 }
781 emit(")");
782 firstBinding = false;
783 }
784 closeBrace();
785 }
786 }
787
788 @Override
789 public void meet(final StatementPattern n) {
790 emit(getBGP(n));
791 }
792
793
794
795 @Override
796 public void meet(final Extension n) {
797 emit(n.getArg());
798 if (!(n.getArg() instanceof SingletonSet)) {
799 newline();
800 }
801 boolean first = true;
802 for (final ExtensionElem e : n.getElements()) {
803 final ValueExpr expr = e.getExpr();
804 if (!(expr instanceof Var) || !((Var) expr).getName().equals(e.getName())) {
805 if (!first) {
806 newline();
807 }
808 emit("BIND (").emit(expr).emit(" AS ?").emit(e.getName()).emit(")");
809 first = false;
810 }
811 }
812 }
813
814 @Override
815 public void meet(final ExtensionElem n) {
816 throw new Error("Should not be directly called");
817 }
818
819 @Override
820 public void meet(final Filter n) {
821 final ValueExpr cond = n.getCondition();
822 final boolean nopar = cond instanceof Exists || cond instanceof Not
823 && ((Not) cond).getArg() instanceof Exists;
824 emit(n.getArg());
825 if (!(n.getArg() instanceof SingletonSet)) {
826 newline();
827 }
828 emit("FILTER ").emit(cond, !nopar);
829 }
830
831 @Override
832 public void meet(final Service n) {
833 newline().emit("SERVICE ").emitIf(n.isSilent(), "SILENT ").openBrace()
834 .emit(n.getServiceExpr()).closeBrace().emit(" ").emit(n.getServiceRef());
835 }
836
837
838
839 @Override
840 public void meet(final Join n) {
841 final List<StatementPattern> bgp = getBGP(n);
842 if (bgp != null) {
843 emit(bgp);
844 } else {
845 final TupleExpr l = n.getLeftArg();
846 final TupleExpr r = n.getRightArg();
847 final boolean norpar = r instanceof Join || r instanceof StatementPattern
848 || r instanceof SingletonSet || r instanceof Service || r instanceof Union
849 || r instanceof BindingSetAssignment || r instanceof ArbitraryLengthPath;
850 emit(l).newline().emit(r, !norpar);
851 }
852 }
853
854 @Override
855 public void meet(final LeftJoin n) {
856 final TupleExpr l = n.getLeftArg();
857 final TupleExpr r = n.getCondition() == null ? n.getRightArg() :
858 new Filter(n.getRightArg(), n.getCondition());
859 emit(l);
860 if (!(l instanceof SingletonSet)) {
861 newline();
862 }
863 emit("OPTIONAL ").emit(r, true);
864 }
865
866 @Override
867 public void meet(final Union n) {
868 final TupleExpr l = n.getLeftArg();
869 final TupleExpr r = n.getRightArg();
870 final ZeroLengthPath p = l instanceof ZeroLengthPath ? (ZeroLengthPath) l
871 : r instanceof ZeroLengthPath ? (ZeroLengthPath) r : null;
872 if (p == null) {
873 emit(l, !(l instanceof Union)).emit(" UNION ").emit(r, !(r instanceof Union));
874 } else {
875 final Var s = p.getSubjectVar();
876 final Var o = p.getObjectVar();
877 final Var c = p.getContextVar();
878 if (c != null) {
879 emit("GRAPH ").emit(c).emit(" ").openBrace();
880 }
881 emit(s).emit(" ").emitPropertyPath(n, s, o).emit(" ").emit(o);
882 if (c != null) {
883 closeBrace();
884 }
885 }
886 }
887
888 @Override
889 public void meet(final Difference n) {
890 final TupleExpr l = n.getLeftArg();
891 final TupleExpr r = n.getRightArg();
892 emit(l, true).emit(" MINUS ").emit(r, true);
893 }
894
895
896
897 @Override
898 public void meet(final ArbitraryLengthPath n) {
899 final Var s = n.getSubjectVar();
900 final Var o = n.getObjectVar();
901 final Var c = n.getContextVar();
902 if (c != null) {
903 emit("GRAPH ").emit(c).openBrace();
904 }
905 emit(s).emit(" ").emitPropertyPath(n, s, o).emit(" ").emit(o).emit(" .");
906 if (c != null) {
907 closeBrace();
908 }
909 }
910
911 @Override
912 public void meet(final ZeroLengthPath node) {
913 throw new Error("Should not be directly called");
914 }
915
916 private Rendering emitPropertyPath(final TupleExpr node, final Var start, final Var end) {
917
918
919
920 final boolean parenthesis = !(node instanceof StatementPattern)
921 && (node.getParentNode() instanceof ArbitraryLengthPath || node
922 .getParentNode() instanceof Union);
923
924 emitIf(parenthesis, "(");
925
926 if (node instanceof StatementPattern) {
927
928 final StatementPattern pattern = (StatementPattern) node;
929 final boolean inverse = isInversePath(pattern, start, end);
930 if (!pattern.getPredicateVar().hasValue()
931 || !pattern.getPredicateVar().isAnonymous()) {
932 fail("Unsupported path expression. Check node: ", node);
933 }
934 emitIf(inverse, "^").emit(pattern.getPredicateVar().getValue());
935
936 } else if (node instanceof Join) {
937 final Join j = (Join) node;
938 final TupleExpr l = j.getLeftArg();
939 final TupleExpr r = j.getRightArg();
940 final StatementPattern s = l instanceof StatementPattern ? (StatementPattern) l
941 : r instanceof StatementPattern ? (StatementPattern) r : null;
942 if (s == null) {
943 return fail("Cannot process property path", node);
944 }
945 final Var m = s.getSubjectVar().equals(start) || s.getSubjectVar().equals(end) ? s
946 .getObjectVar() : s.getSubjectVar();
947 emitPropertyPath(l, start, m);
948 emit("/");
949 emitPropertyPath(r, m, end);
950
951 } else if (node instanceof ArbitraryLengthPath) {
952
953 final ArbitraryLengthPath path = (ArbitraryLengthPath) node;
954 check(path.getMinLength() <= 1, "Invalid path length");
955 emitPropertyPath(path.getPathExpression(), start, end).emit(
956 path.getMinLength() == 0 ? "*" : "+");
957
958 } else if (node instanceof Union) {
959
960 final Union union = (Union) node;
961 if (union.getLeftArg() instanceof ZeroLengthPath) {
962 emitPropertyPath(union.getRightArg(), start, end).emit("?");
963 } else if (union.getRightArg() instanceof ZeroLengthPath) {
964 emitPropertyPath(union.getLeftArg(), start, end).emit("?");
965 } else {
966 emitPropertyPath(union.getLeftArg(), start, end);
967 emit("|");
968 emitPropertyPath(union.getRightArg(), start, end);
969 }
970
971 } else if (node instanceof Filter) {
972
973 final Filter filter = (Filter) node;
974
975 check(filter.getArg() instanceof StatementPattern, "Invalid path expression");
976 final StatementPattern pattern = (StatementPattern) filter.getArg();
977 final boolean inverse = isInversePath(pattern, start, end);
978 check(!pattern.getPredicateVar().hasValue()
979 && pattern.getPredicateVar().isAnonymous(), "Invalid path expression");
980
981 final Set<URI> negatedProperties = new LinkedHashSet<>();
982 extractNegatedProperties(filter.getCondition(), negatedProperties);
983
984 if (negatedProperties.size() == 1) {
985 emit("!").emitIf(inverse, "^").emit(negatedProperties.iterator().next());
986
987 } else {
988 emit("!(");
989 boolean first = true;
990 for (final URI negatedProperty : negatedProperties) {
991 emitIf(!first, "|").emitIf(inverse, "^").emit(negatedProperty);
992 first = false;
993 }
994 emit(")");
995 }
996
997 } else {
998 fail("Unsupported path expression node", node);
999 }
1000
1001 return emitIf(parenthesis, ")");
1002 }
1003
1004 private void extractNegatedProperties(final ValueExpr condition,
1005 final Set<URI> negatedProperties) {
1006 if (condition instanceof And) {
1007 final And and = (And) condition;
1008 extractNegatedProperties(and.getLeftArg(), negatedProperties);
1009 extractNegatedProperties(and.getRightArg(), negatedProperties);
1010
1011 } else if (condition instanceof Compare) {
1012 final Compare compare = (Compare) condition;
1013 check(compare.getOperator() == CompareOp.NE, "Invalid path expression");
1014 if (compare.getLeftArg() instanceof ValueConstant) {
1015 check(compare.getRightArg() instanceof Var, "Invalid path expression");
1016 negatedProperties.add((URI) ((ValueConstant) compare.getLeftArg()).getValue());
1017 } else if (compare.getRightArg() instanceof ValueConstant) {
1018 check(compare.getLeftArg() instanceof Var, "Invalid path expression");
1019 negatedProperties
1020 .add((URI) ((ValueConstant) compare.getRightArg()).getValue());
1021 } else {
1022 fail("Unsupported path expression. Check condition node: ", condition);
1023 }
1024 }
1025 }
1026
1027 private boolean isInversePath(final StatementPattern node, final Var start, final Var end) {
1028 if (node.getSubjectVar().equals(start)) {
1029 check(node.getObjectVar().equals(end), "Invalid path expression");
1030 return false;
1031 } else if (node.getObjectVar().equals(start)) {
1032 check(node.getSubjectVar().equals(end), "Invalid path expression");
1033 return true;
1034 } else {
1035 fail("Unsupported path expression. Check node: ", node);
1036 return false;
1037 }
1038 }
1039
1040
1041
1042 @Override
1043 public void meet(final Intersection n) {
1044 fail("Not a SPARQL 1.1 node", n);
1045 }
1046
1047
1048
1049 @Override
1050 public void meet(final Add add) {
1051 throw new UnsupportedOperationException();
1052 }
1053
1054 @Override
1055 public void meet(final Clear clear) {
1056 throw new UnsupportedOperationException();
1057 }
1058
1059 @Override
1060 public void meet(final Copy copy) {
1061 throw new UnsupportedOperationException();
1062 }
1063
1064 @Override
1065 public void meet(final Create create) {
1066 throw new UnsupportedOperationException();
1067 }
1068
1069 @Override
1070 public void meet(final DeleteData deleteData) {
1071 throw new UnsupportedOperationException();
1072 }
1073
1074 @Override
1075 public void meet(final InsertData insertData) {
1076 throw new UnsupportedOperationException();
1077 }
1078
1079 @Override
1080 public void meet(final Load load) {
1081 throw new UnsupportedOperationException();
1082 }
1083
1084 @Override
1085 public void meet(final Modify modify) {
1086 throw new UnsupportedOperationException();
1087 }
1088
1089 @Override
1090 public void meet(final Move move) {
1091 throw new UnsupportedOperationException();
1092 }
1093
1094
1095
1096
1097
1098 @Override
1099 public void meet(final ValueConstant n) {
1100 emit(n.getValue());
1101 }
1102
1103 @Override
1104 public void meet(final Var n) {
1105 final String name = n.getName();
1106 if (n.getValue() != null) {
1107 emit(n.getValue());
1108 } else if (!n.isAnonymous()) {
1109 emit("?" + n.getName());
1110 } else {
1111 final ValueExpr expr = getVarExpr(this.root, n.getName());
1112 if (expr != null) {
1113 emit(expr);
1114 } else if (getVarRefs(this.root, n.getName()) <= 1) {
1115 emit("[]");
1116 } else {
1117 emit("?").emit(sanitize(name));
1118 }
1119 }
1120 }
1121
1122
1123
1124 @Override
1125 public void meet(final Compare n) {
1126 final QueryModelNode p = n.getParentNode();
1127 final boolean par = p instanceof Not || p instanceof MathExpr;
1128 emitIf(par, "(").emit(n.getLeftArg()).emit(" ").emit(n.getOperator().getSymbol())
1129 .emit(" ").emit(n.getRightArg()).emitIf(par, ")");
1130 }
1131
1132 @Override
1133 public void meet(final ListMemberOperator n) {
1134 final QueryModelNode p = n.getParentNode();
1135 final boolean par = p instanceof Not || p instanceof MathExpr;
1136 final List<ValueExpr> args = n.getArguments();
1137 emitIf(par, "(").emit(args.get(0)).emit(" in (")
1138 .emit(args.subList(1, args.size()), ", ").emit(")").emitIf(par, ")");
1139 }
1140
1141 @Override
1142 public void meet(final MathExpr n) {
1143 final QueryModelNode p = n.getParentNode();
1144 final MathOp op = n.getOperator();
1145 final MathOp pop = p instanceof MathExpr ? ((MathExpr) p).getOperator() : null;
1146 final boolean r = p instanceof BinaryValueOperator
1147 && n == ((BinaryValueOperator) p).getRightArg();
1148 final boolean par = p instanceof Not
1149 || (op == MathOp.PLUS || op == MathOp.MINUS)
1150 && (pop == MathOp.MINUS && r
1151 || pop == MathOp.DIVIDE || pop == MathOp.MULTIPLY)
1152 || (op == MathOp.MULTIPLY || op == MathOp.DIVIDE) && pop == MathOp.DIVIDE && r;
1153 emitIf(par, "(").emit(n.getLeftArg()).emit(" ").emit(op.getSymbol()).emit(" ")
1154 .emit(n.getRightArg()).emitIf(par, ")");
1155 }
1156
1157 @Override
1158 public void meet(final And n) {
1159 final QueryModelNode p = n.getParentNode();
1160 final boolean needPar = p instanceof Not || p instanceof MathExpr
1161 || p instanceof ListMemberOperator || p instanceof Compare;
1162 emitIf(needPar, "(").emit(n.getLeftArg()).emit(" && ").emit(n.getRightArg())
1163 .emitIf(needPar, ")");
1164 }
1165
1166 @Override
1167 public void meet(final Or n) {
1168 final QueryModelNode p = n.getParentNode();
1169 final boolean needPar = p instanceof Not || p instanceof And || p instanceof MathExpr
1170 || p instanceof ListMemberOperator || p instanceof Compare;
1171 emitIf(needPar, "(").emit(n.getLeftArg()).emit(" || ").emit(n.getRightArg())
1172 .emitIf(needPar, ")");
1173 }
1174
1175 @Override
1176 public void meet(final Not n) {
1177 final String op = n.getArg() instanceof Exists ? "NOT " : "!";
1178 emit(op).emit(n.getArg());
1179 }
1180
1181
1182
1183 @Override
1184 public void meet(final Count node) {
1185 emit("COUNT(").emitIf(node.isDistinct(), "DISTINCT ")
1186 .emit(defaultIfNull(node.getArg(), "*")).emit(")");
1187 }
1188
1189 @Override
1190 public void meet(final Sum node) {
1191 emit("SUM(").emitIf(node.isDistinct(), "DISTINCT ").emit(node.getArg()).emit(")");
1192 }
1193
1194 @Override
1195 public void meet(final Min node) {
1196 emit("MIN(").emitIf(node.isDistinct(), "DISTINCT ").emit(node.getArg()).emit(")");
1197 }
1198
1199 @Override
1200 public void meet(final Max node) {
1201 emit("MAX(").emitIf(node.isDistinct(), "DISTINCT ").emit(node.getArg()).emit(")");
1202 }
1203
1204 @Override
1205 public void meet(final Avg node) {
1206 emit("AVG(").emitIf(node.isDistinct(), "DISTINCT ").emit(node.getArg()).emit(")");
1207 }
1208
1209 @Override
1210 public void meet(final Sample node) {
1211 emit("SAMPLE(").emitIf(node.isDistinct(), "DISTINCT ").emit(node.getArg()).emit(")");
1212 }
1213
1214 @Override
1215 public void meet(final GroupConcat node) {
1216 emit("GROUP_CONCAT(").emitIf(node.isDistinct(), "DISTINCT ").emit(node.getArg());
1217 if (node.getSeparator() != null) {
1218 emit(" ; separator=").emit(node.getSeparator());
1219 }
1220 emit(")");
1221 }
1222
1223
1224
1225 @Override
1226 public void meet(final Str n) {
1227 emit("STR(").emit(n.getArg()).emit(")");
1228 }
1229
1230 @Override
1231 public void meet(final Lang n) {
1232 emit("LANG(").emit(n.getArg()).emit(")");
1233 }
1234
1235 @Override
1236 public void meet(final LangMatches n) {
1237 emit("LANGMATCHES(").emit(n.getLeftArg()).emit(", ").emit(n.getRightArg()).emit(")");
1238 }
1239
1240 @Override
1241 public void meet(final Datatype n) {
1242 emit("DATATYPE(").emit(n.getArg()).emit(")");
1243 }
1244
1245 @Override
1246 public void meet(final Bound n) {
1247 emit("BOUND(").emit(n.getArg()).emit(")");
1248 }
1249
1250 @Override
1251 public void meet(final IRIFunction n) {
1252 emit("IRI(").emit(n.getArg()).emit(")");
1253 if (n.getBaseURI() != null) {
1254 this.base = n.getBaseURI();
1255 }
1256 }
1257
1258 @Override
1259 public void meet(final BNodeGenerator n) {
1260 final ValueExpr expr = n.getNodeIdExpr();
1261 emit("BNODE(").emitIf(expr != null, expr).emit(")");
1262 }
1263
1264 @Override
1265 public void meet(final FunctionCall n) {
1266 final String uri = n.getURI();
1267 String name = NAMES.get(uri);
1268 if (name == null && NAMES.values().contains(uri.toUpperCase())) {
1269 name = n.getURI().toUpperCase();
1270 }
1271 emit(name != null ? name : new URIImpl(uri)).emit("(").emit(n.getArgs(), ", ")
1272 .emit(")");
1273 }
1274
1275 @Override
1276 public void meet(final Coalesce n) {
1277 emit("COALESCE(").emit(n.getArguments(), ", ").emit(")");
1278 }
1279
1280 @Override
1281 public void meet(final If n) {
1282 emit("IF(").emit(n.getCondition()).emit(", ").emit(n.getResult()).emit(", ")
1283 .emit(n.getAlternative()).emit(")");
1284 }
1285
1286 @Override
1287 public void meet(final SameTerm n) {
1288 emit("sameTerm(").emit(n.getLeftArg()).emit(", ").emit(n.getRightArg()).emit(")");
1289 }
1290
1291 @Override
1292 public void meet(final IsURI n) {
1293 emit("isIRI(").emit(n.getArg()).emit(")");
1294 }
1295
1296 @Override
1297 public void meet(final IsBNode n) {
1298 emit("isBLANK(").emit(n.getArg()).emit(")");
1299 }
1300
1301 @Override
1302 public void meet(final IsLiteral n) {
1303 emit("isLITERAL(").emit(n.getArg()).emit(")");
1304 }
1305
1306 @Override
1307 public void meet(final IsNumeric n) {
1308 emit("isNUMERIC(").emit(n.getArg()).emit(")");
1309 }
1310
1311 @Override
1312 public void meet(final Regex n) {
1313 emit("REGEX(").emit(n.getArg()).emit(", ").emit(n.getPatternArg());
1314 if (n.getFlagsArg() != null) {
1315 emit(", ").emit(n.getFlagsArg());
1316 }
1317 emit(")");
1318 }
1319
1320 @Override
1321 public void meet(final Exists node) {
1322 emit("EXISTS ").emit(node.getSubQuery());
1323 }
1324
1325
1326
1327 @Override
1328 public void meet(final IsResource n) {
1329 fail("Not a SPARQL 1.1 node", n);
1330 }
1331
1332 @Override
1333 public void meet(final Label n) {
1334 fail("Not a SPARQL 1.1 node", n);
1335 }
1336
1337 @Override
1338 public void meet(final Like n) {
1339 fail("Not a SPARQL 1.1 node", n);
1340 }
1341
1342 @Override
1343 public void meet(final LocalName n) {
1344 fail("Not a SPARQL 1.1 node", n);
1345 }
1346
1347 @Override
1348 public void meet(final Namespace n) {
1349 fail("Not a SPARQL 1.1 node", n);
1350 }
1351
1352 @Override
1353 public void meet(final In n) {
1354 fail("Not a SPARQL 1.1 node", n);
1355 }
1356
1357 @Override
1358 public void meet(final CompareAll n) {
1359 fail("Not a SPARQL 1.1 node", n);
1360 }
1361
1362 @Override
1363 public void meet(final CompareAny n) {
1364 fail("Not a SPARQL 1.1 node", n);
1365 }
1366
1367
1368
1369 @Override
1370 public void meetOther(final QueryModelNode n) {
1371 fail("Unknown node", n);
1372 }
1373
1374 }
1375
1376 private enum Form {
1377 SELECT, CONSTRUCT, ASK, DESCRIBE
1378 }
1379
1380 private enum Modifier {
1381 DISTINCT, REDUCED
1382 }
1383
1384 private static final class Query {
1385
1386 final QueryModelNode root;
1387
1388 final Form form;
1389
1390 @Nullable
1391 final Modifier modifier;
1392
1393 final List<ProjectionElem> select;
1394
1395 @Nullable
1396 final TupleExpr construct;
1397
1398 @Nullable
1399 final Dataset from;
1400
1401 final TupleExpr where;
1402
1403 final List<ProjectionElem> groupBy;
1404
1405 @Nullable
1406 final ValueExpr having;
1407
1408 final List<OrderElem> orderBy;
1409
1410 @Nullable
1411 final Long offset;
1412
1413 @Nullable
1414 final Long limit;
1415
1416 static Query create(final TupleExpr rootNode, @Nullable final Dataset dataset,
1417 final boolean forceSelect) {
1418
1419 Objects.requireNonNull(rootNode);
1420
1421
1422 if (rootNode instanceof EmptySet) {
1423 return new Query(rootNode, Form.CONSTRUCT, null, null, rootNode, dataset,
1424 rootNode, null, null, null, null, null);
1425 }
1426
1427
1428 Form form = null;
1429 Modifier modifier = null;
1430 final List<ProjectionElem> select = new ArrayList<>();
1431 TupleExpr construct = null;
1432 TupleExpr where = null;
1433 final List<ProjectionElem> groupBy = new ArrayList<>();
1434 ValueExpr having = null;
1435 final List<OrderElem> orderBy = new ArrayList<>();
1436 Long offset = null;
1437 Long limit = null;
1438
1439 final List<UnaryTupleOperator> nodes = extractQueryNodes(rootNode, false);
1440
1441 where = nodes.size() > 0 ? nodes.get(nodes.size() - 1).getArg() : rootNode;
1442
1443 for (final UnaryTupleOperator node : nodes) {
1444
1445 if (node instanceof DescribeOperator) {
1446 form = Form.DESCRIBE;
1447
1448 } else if (node instanceof Distinct) {
1449 modifier = Modifier.DISTINCT;
1450
1451 } else if (node instanceof Reduced) {
1452 modifier = Modifier.REDUCED;
1453
1454 } else if (node instanceof Projection) {
1455 final Map<String, ExtensionElem> extensions = extractExtensions(node);
1456 final List<ProjectionElem> projections = ((Projection) node)
1457 .getProjectionElemList().getElements();
1458 final boolean isConstruct = projections.size() >= 3
1459 && "subject".equals(projections.get(0).getTargetName())
1460 && "predicate".equals(projections.get(1).getTargetName())
1461 && "object".equals(projections.get(2).getTargetName())
1462 && (projections.size() == 3 || projections.size() == 4
1463 && "context".equals(projections.get(3).getTargetName()));
1464 if (isConstruct && !forceSelect) {
1465 form = Form.CONSTRUCT;
1466 construct = extractConstructExpression(extensions,
1467 Collections.singleton(((Projection) node)
1468 .getProjectionElemList()));
1469 } else {
1470 form = form == null ? Form.SELECT : form;
1471 for (final ProjectionElem projection : projections) {
1472 final String variable = projection.getTargetName();
1473 ExtensionElem extension = extensions.get(variable);
1474 if (extension == null && projection.getSourceName() != null) {
1475 extension = extensions.get(projection.getSourceName());
1476 }
1477 final ProjectionElem newProjection = new ProjectionElem();
1478 newProjection.setTargetName(variable);
1479 newProjection.setSourceExpression(extension);
1480 newProjection.setSourceName(extension == null
1481 || !(extension.getExpr() instanceof Var) ? projection
1482 .getSourceName() : ((Var) extension.getExpr()).getName());
1483 select.add(newProjection);
1484 }
1485 }
1486
1487 } else if (node instanceof MultiProjection) {
1488 form = Form.CONSTRUCT;
1489 construct = extractConstructExpression(extractExtensions(node),
1490 ((MultiProjection) node).getProjections());
1491
1492 } else if (node instanceof Group) {
1493 final Group group = (Group) node;
1494 final Map<String, ExtensionElem> extensions = extractExtensions(group.getArg());
1495 for (final String variableName : group.getGroupBindingNames()) {
1496 final ExtensionElem extension = extensions.get(variableName);
1497 final ProjectionElem projection = new ProjectionElem();
1498 projection.setTargetName(variableName);
1499 projection.setSourceExpression(extension);
1500 projection.setSourceName(extension == null
1501 || !(extension.getExpr() instanceof Var) ? variableName
1502 : ((Var) extension.getExpr()).getName());
1503 groupBy.add(projection);
1504 }
1505
1506 } else if (node instanceof Order) {
1507 orderBy.addAll(((Order) node).getElements());
1508
1509 } else if (node instanceof Slice) {
1510 final Slice slice = (Slice) node;
1511 offset = slice.getOffset() < 0 ? null : slice.getOffset();
1512 limit = slice.getLimit() < 0 ? null : slice.getLimit();
1513 if (form == null && slice.getOffset() == 0 && slice.getLimit() == 1) {
1514 if (forceSelect) {
1515 form = Form.SELECT;
1516 limit = 1L;
1517
1518 } else {
1519 form = Form.ASK;
1520 }
1521 }
1522
1523 } else if (node instanceof Filter) {
1524 having = ((Filter) node).getCondition();
1525 }
1526 }
1527
1528 form = defaultIfNull(form, Form.SELECT);
1529 if (form == Form.CONSTRUCT && construct == null) {
1530 construct = new SingletonSet();
1531 }
1532
1533 return new Query(rootNode, form, modifier, select, construct, dataset, where, groupBy,
1534 having, orderBy, offset, limit);
1535 }
1536
1537 private static List<UnaryTupleOperator> extractQueryNodes(final TupleExpr rootNode,
1538 final boolean haltOnGroup) {
1539 final List<UnaryTupleOperator> nodes = new ArrayList<>();
1540
1541 TupleExpr queryNode = rootNode;
1542 while (queryNode instanceof UnaryTupleOperator) {
1543 nodes.add((UnaryTupleOperator) queryNode);
1544 queryNode = ((UnaryTupleOperator) queryNode).getArg();
1545 }
1546
1547 boolean describeFound = false;
1548 boolean modifierFound = false;
1549 boolean projectionFound = false;
1550 boolean groupFound = false;
1551 boolean orderFound = false;
1552 boolean sliceFound = false;
1553 boolean extensionFound = false;
1554
1555 int index = 0;
1556 while (index < nodes.size()) {
1557 final UnaryTupleOperator node = nodes.get(index);
1558 if (node instanceof DescribeOperator && !describeFound) {
1559 describeFound = true;
1560
1561 } else if ((node instanceof Distinct || node instanceof Reduced) && !modifierFound
1562 && !projectionFound) {
1563 modifierFound = true;
1564
1565 } else if ((node instanceof Projection || node instanceof MultiProjection)
1566 && !projectionFound) {
1567 projectionFound = true;
1568
1569 } else if (node instanceof Group && !groupFound && !haltOnGroup) {
1570 groupFound = true;
1571
1572 } else if (node instanceof Order && !orderFound) {
1573 orderFound = true;
1574
1575 } else if (node instanceof Slice && !sliceFound) {
1576 sliceFound = true;
1577
1578 } else if (node instanceof Filter && !groupFound && !haltOnGroup) {
1579 int i = index + 1;
1580 for (; i < nodes.size() && nodes.get(i) instanceof Extension;) {
1581 ++i;
1582 }
1583 if (i < nodes.size() && nodes.get(i) instanceof Group) {
1584 groupFound = true;
1585 index = i;
1586 } else {
1587 break;
1588 }
1589
1590 } else if (node instanceof Extension && !extensionFound) {
1591 extensionFound = true;
1592
1593 } else if (!(node instanceof QueryRoot) || index > 0) {
1594 break;
1595 }
1596 ++index;
1597 }
1598
1599 return nodes.subList(0, index);
1600 }
1601
1602 private static Map<String, ExtensionElem> extractExtensions(final TupleExpr rootNode) {
1603 final Map<String, ExtensionElem> map = new HashMap<>();
1604 for (final UnaryTupleOperator node : extractQueryNodes(rootNode, true)) {
1605 if (node instanceof Extension) {
1606 for (final ExtensionElem elem : ((Extension) node).getElements()) {
1607 final String variable = elem.getName();
1608 final ValueExpr expression = elem.getExpr();
1609 if (!(expression instanceof Var)
1610 || !((Var) expression).getName().equals(variable)) {
1611 map.put(variable, elem);
1612 }
1613 }
1614 }
1615 }
1616 return map;
1617 }
1618
1619 private static TupleExpr extractConstructExpression(
1620 final Map<String, ExtensionElem> extensions,
1621 final Iterable<? extends ProjectionElemList> multiProjections) {
1622 TupleExpr expression = null;
1623 for (final ProjectionElemList projections : multiProjections) {
1624 final Var subj = extractConstructVar(extensions, projections.getElements().get(0));
1625 final Var pred = extractConstructVar(extensions, projections.getElements().get(1));
1626 final Var obj = extractConstructVar(extensions, projections.getElements().get(2));
1627 final Var ctx = projections.getElements().size() < 4 ? null : extractConstructVar(
1628 extensions, projections.getElements().get(3));
1629 final StatementPattern pattern = new StatementPattern(
1630 ctx == null ? Scope.DEFAULT_CONTEXTS : Scope.NAMED_CONTEXTS, subj, pred,
1631 obj, ctx);
1632 expression = expression == null ? pattern : new Join(expression, pattern);
1633 }
1634 return expression;
1635 }
1636
1637 private static Var extractConstructVar(final Map<String, ExtensionElem> extensions,
1638 final ProjectionElem projection) {
1639 final ExtensionElem extension = extensions.get(projection.getSourceName());
1640 String name = projection.getSourceName();
1641 if (name.startsWith("-anon-")) {
1642 name += "-construct";
1643 }
1644 if (extension == null || extension.getExpr() instanceof BNodeGenerator) {
1645 final Var var = new Var(name);
1646 var.setAnonymous(name.startsWith("-anon-"));
1647 return var;
1648 } else if (extension.getExpr() instanceof ValueConstant) {
1649 final ValueConstant constant = (ValueConstant) extension.getExpr();
1650 return new Var(name, constant.getValue());
1651 } else {
1652 throw new UnsupportedOperationException(
1653 "Unsupported extension in construct query: " + extension);
1654 }
1655 }
1656
1657 private Query(
1658 final QueryModelNode root,
1659 final Form form,
1660 @Nullable final Modifier modifier,
1661 @Nullable final Iterable<? extends ProjectionElem> selectList,
1662 @Nullable final TupleExpr construct,
1663 @Nullable final Dataset from,
1664 final TupleExpr where,
1665 @Nullable final Iterable<? extends ProjectionElem> groupBy,
1666 @Nullable final ValueExpr having,
1667 @Nullable final Iterable<? extends OrderElem> orderBy,
1668 @Nullable final Long offset,
1669 @Nullable final Long limit) {
1670
1671 this.root = Objects.requireNonNull(root);
1672 this.form = Objects.requireNonNull(form);
1673 this.modifier = modifier;
1674 this.select = selectList == null ? Collections.emptyList() : list(selectList);
1675 this.construct = construct;
1676 this.from = from;
1677 this.where = Objects.requireNonNull(where);
1678 this.groupBy = groupBy == null ? Collections.emptyList() : list(groupBy);
1679 this.having = having;
1680 this.orderBy = orderBy == null ? Collections.emptyList() : list(orderBy);
1681 this.offset = offset;
1682 this.limit = limit;
1683 }
1684
1685 }
1686
1687 }