package eu.fbk.knowledgestore.triplestore;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import javax.annotation.Nullable;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Ordering;
import com.google.common.collect.Sets;
import org.openrdf.model.BNode;
import org.openrdf.model.Literal;
import org.openrdf.model.URI;
import org.openrdf.model.impl.URIImpl;
import org.openrdf.model.vocabulary.FN;
import org.openrdf.model.vocabulary.XMLSchema;
import org.openrdf.query.BindingSet;
import org.openrdf.query.Dataset;
import org.openrdf.query.QueryLanguage;
import org.openrdf.query.algebra.Add;
import org.openrdf.query.algebra.And;
import org.openrdf.query.algebra.ArbitraryLengthPath;
import org.openrdf.query.algebra.Avg;
import org.openrdf.query.algebra.BNodeGenerator;
import org.openrdf.query.algebra.BinaryValueOperator;
import org.openrdf.query.algebra.BindingSetAssignment;
import org.openrdf.query.algebra.Bound;
import org.openrdf.query.algebra.Clear;
import org.openrdf.query.algebra.Coalesce;
import org.openrdf.query.algebra.Compare;
import org.openrdf.query.algebra.Compare.CompareOp;
import org.openrdf.query.algebra.CompareAll;
import org.openrdf.query.algebra.CompareAny;
import org.openrdf.query.algebra.Copy;
import org.openrdf.query.algebra.Count;
import org.openrdf.query.algebra.Create;
import org.openrdf.query.algebra.Datatype;
import org.openrdf.query.algebra.DeleteData;
import org.openrdf.query.algebra.DescribeOperator;
import org.openrdf.query.algebra.Difference;
import org.openrdf.query.algebra.Distinct;
import org.openrdf.query.algebra.EmptySet;
import org.openrdf.query.algebra.Exists;
import org.openrdf.query.algebra.Extension;
import org.openrdf.query.algebra.ExtensionElem;
import org.openrdf.query.algebra.Filter;
import org.openrdf.query.algebra.FunctionCall;
import org.openrdf.query.algebra.Group;
import org.openrdf.query.algebra.GroupConcat;
import org.openrdf.query.algebra.GroupElem;
import org.openrdf.query.algebra.IRIFunction;
import org.openrdf.query.algebra.If;
import org.openrdf.query.algebra.In;
import org.openrdf.query.algebra.InsertData;
import org.openrdf.query.algebra.Intersection;
import org.openrdf.query.algebra.IsBNode;
import org.openrdf.query.algebra.IsLiteral;
import org.openrdf.query.algebra.IsNumeric;
import org.openrdf.query.algebra.IsResource;
import org.openrdf.query.algebra.IsURI;
import org.openrdf.query.algebra.Join;
import org.openrdf.query.algebra.Label;
import org.openrdf.query.algebra.Lang;
import org.openrdf.query.algebra.LangMatches;
import org.openrdf.query.algebra.LeftJoin;
import org.openrdf.query.algebra.Like;
import org.openrdf.query.algebra.ListMemberOperator;
import org.openrdf.query.algebra.Load;
import org.openrdf.query.algebra.LocalName;
import org.openrdf.query.algebra.MathExpr;
import org.openrdf.query.algebra.MathExpr.MathOp;
import org.openrdf.query.algebra.Max;
import org.openrdf.query.algebra.Min;
import org.openrdf.query.algebra.Modify;
import org.openrdf.query.algebra.Move;
import org.openrdf.query.algebra.MultiProjection;
import org.openrdf.query.algebra.Namespace;
import org.openrdf.query.algebra.Not;
import org.openrdf.query.algebra.Or;
import org.openrdf.query.algebra.Order;
import org.openrdf.query.algebra.OrderElem;
import org.openrdf.query.algebra.Projection;
import org.openrdf.query.algebra.ProjectionElem;
import org.openrdf.query.algebra.ProjectionElemList;
import org.openrdf.query.algebra.QueryModelNode;
import org.openrdf.query.algebra.QueryModelVisitor;
import org.openrdf.query.algebra.QueryRoot;
import org.openrdf.query.algebra.Reduced;
import org.openrdf.query.algebra.Regex;
import org.openrdf.query.algebra.SameTerm;
import org.openrdf.query.algebra.Sample;
import org.openrdf.query.algebra.Service;
import org.openrdf.query.algebra.SingletonSet;
import org.openrdf.query.algebra.Slice;
import org.openrdf.query.algebra.StatementPattern;
import org.openrdf.query.algebra.StatementPattern.Scope;
import org.openrdf.query.algebra.Str;
import org.openrdf.query.algebra.Sum;
import org.openrdf.query.algebra.TupleExpr;
import org.openrdf.query.algebra.UnaryTupleOperator;
import org.openrdf.query.algebra.Union;
import org.openrdf.query.algebra.ValueConstant;
import org.openrdf.query.algebra.ValueExpr;
import org.openrdf.query.algebra.Var;
import org.openrdf.query.algebra.ZeroLengthPath;
import org.openrdf.query.algebra.helpers.QueryModelVisitorBase;
import org.openrdf.query.parser.ParsedQuery;
import org.openrdf.queryrender.QueryRenderer;
final class SPARQLRenderer implements QueryRenderer {
private static final Map<String, String> NAMES;
static {
final Map<String, String> names = Maps.newHashMap();
names.put("RAND", "RAND");
names.put("TZ", "TZ");
names.put("NOW", "NOW");
names.put("UUID", "UUID");
names.put("STRUUID", "STRUUID");
names.put("MD5", "MD5");
names.put("SHA1", "SHA1");
names.put("SHA256", "SHA256");
names.put("SHA384", "SHA384");
names.put("SHA512", "SHA512");
names.put("STRLANG", "STRLANG");
names.put("STRDT", "STRDT");
names.put(FN.STRING_LENGTH.stringValue(), "STRLEN");
names.put(FN.SUBSTRING.stringValue(), "SUBSTR");
names.put(FN.UPPER_CASE.stringValue(), "UCASE");
names.put(FN.LOWER_CASE.stringValue(), "LCASE");
names.put(FN.STARTS_WITH.stringValue(), "STRSTARTS");
names.put(FN.ENDS_WITH.stringValue(), "STRENDS");
names.put(FN.CONTAINS.stringValue(), "CONTAINS");
names.put(FN.SUBSTRING_BEFORE.stringValue(), "STRBEFORE");
names.put(FN.SUBSTRING_AFTER.stringValue(), "STRAFTER");
names.put(FN.ENCODE_FOR_URI.stringValue(), "ENCODE_FOR_URI");
names.put(FN.CONCAT.stringValue(), "CONCAT");
names.put(FN.NAMESPACE + "matches", "REGEX");
names.put(FN.REPLACE.stringValue(), "REPLACE");
names.put(FN.NUMERIC_ABS.stringValue(), "ABS");
names.put(FN.NUMERIC_ROUND.stringValue(), "ROUND");
names.put(FN.NUMERIC_CEIL.stringValue(), "CEIL");
names.put(FN.NUMERIC_FLOOR.stringValue(), "FLOOR");
names.put(FN.YEAR_FROM_DATETIME.stringValue(), "YEAR");
names.put(FN.MONTH_FROM_DATETIME.stringValue(), "MONTH");
names.put(FN.DAY_FROM_DATETIME.stringValue(), "DAY");
names.put(FN.HOURS_FROM_DATETIME.stringValue(), "HOURS");
names.put(FN.MINUTES_FROM_DATETIME.stringValue(), "MINUTES");
names.put(FN.SECONDS_FROM_DATETIME.stringValue(), "SECONDS");
names.put(FN.TIMEZONE_FROM_DATETIME.stringValue(), "TIMEZONE");
NAMES = Collections.unmodifiableMap(names);
}
private final Map<String, String> prefixes;
private final boolean forceSelect;
public SPARQLRenderer(@Nullable final Map<String, String> prefixes,
@Nullable final boolean forceSelect) {
this.prefixes = prefixes != null ? prefixes : Collections.<String, String>emptyMap();
this.forceSelect = forceSelect;
}
@Override
public QueryLanguage getLanguage() {
return QueryLanguage.SPARQL;
}
@Override
public String render(final ParsedQuery query) {
return render(query.getTupleExpr(), query.getDataset());
}
public String render(final TupleExpr expr, final Dataset dataset) {
final Rendering rendering = new Rendering(expr, dataset);
final StringBuilder builder = new StringBuilder();
boolean newline = false;
if (!rendering.namespaces.isEmpty()) {
for (final String namespace : Ordering.natural().sortedCopy(rendering.namespaces)) {
final String prefix = this.prefixes.get(namespace);
if ("bif".equals(prefix) && "http://www.openlinksw.com/schema/sparql/extensions#" //
.equals(namespace)) {
continue; // do not emit Virtuoso bif: binding, as Virtuoso will complain
}
builder.append("PREFIX ").append(prefix).append(": <");
escape(namespace, builder);
builder.append(">\n");
newline = true;
}
}
if (rendering.base != null) {
builder.append("BASE <");
escape(rendering.base, builder);
builder.append(">\n");
newline = true;
}
if (newline) {
builder.append("\n");
}
builder.append(rendering.body);
return builder.toString();
}
// Helper functions
private static void escape(final String label, final StringBuilder builder) {
final int length = label.length();
for (int i = 0; i < length; ++i) {
final char c = label.charAt(i);
if (c == '\\') {
builder.append("\\\\");
} else if (c == '"') {
builder.append("\\\"");
} else if (c == '\n') {
builder.append("\\n");
} else if (c == '\r') {
builder.append("\\r");
} else if (c == '\t') {
builder.append("\\t");
}
// TODO: not accepted by Virtuoso :-(
// else if (c >= 0x0 && c <= 0x8 || c == 0xB || c == 0xC || c >= 0xE && c <= 0x1F
// || c >= 0x7F && c <= 0xFFFF) {
// builder.append("\\u").append(
// Strings.padStart(Integer.toHexString(c).toUpperCase(), 4, '0'));
// } else if (c >= 0x10000 && c <= 0x10FFFF) {
// builder.append("\\U").append(
// Strings.padStart(Integer.toHexString(c).toUpperCase(), 8, '0'));
// }
else {
builder.append(Character.toString(c));
}
}
}
private static String sanitize(final String string) {
final int length = string.length();
final StringBuilder builder = new StringBuilder(length + 10);
for (int i = 0; i < length; ++i) {
final char ch = string.charAt(i);
if (Character.isLetter(ch) || ch == '_' || i > 0 && Character.isDigit(ch)) {
builder.append(ch);
} else {
builder.append("_");
}
}
return builder.toString();
}
private static <T> boolean equalOrNull(@Nullable final T first, @Nullable final T second) {
return first != null && first.equals(second) || first == null && second == null;
}
private static <T> T defaultIfNull(@Nullable final T value, @Nullable final T defaultValue) {
return value != null ? value : defaultValue;
}
private static List<StatementPattern> getBGP(final QueryModelNode n) {
if (n instanceof StatementPattern) {
return Collections.singletonList((StatementPattern) n);
} else if (!(n instanceof Join)) {
return null;
}
final Join j = (Join) n;
final List<StatementPattern> l = getBGP(j.getLeftArg());
final List<StatementPattern> r = getBGP(j.getRightArg());
if (l == null || r == null) {
return null;
}
if (l.isEmpty()) {
return r;
} else if (r.isEmpty()) {
return l;
} else if (!equalOrNull(l.get(0).getContextVar(), r.get(0).getContextVar())) {
return null;
} else {
final List<StatementPattern> s = new ArrayList<StatementPattern>(l.size() + r.size());
s.addAll(l);
s.addAll(r);
return s;
}
}
private static int getVarRefs(final QueryModelNode node, final String name) {
final AtomicInteger count = new AtomicInteger(0);
node.visit(new QueryModelVisitorBase<RuntimeException>() {
@Override
public void meet(final Var var) {
if (var.getName().equals(name)) {
count.set(count.get() + 1);
}
}
});
return count.get();
}
private static ValueExpr getVarExpr(final QueryModelNode node, final String name) {
final AtomicReference<ValueExpr> result = new AtomicReference<ValueExpr>(null);
node.visit(new QueryModelVisitorBase<RuntimeException>() {
@Override
protected void meetNode(final QueryModelNode node) throws RuntimeException {
if (result.get() == null) {
super.meetNode(node);
}
}
@Override
public void meet(final Var var) {
if (var.getName().equals(name) && var.getValue() != null) {
result.set(new ValueConstant(var.getValue()));
}
}
@Override
public void meet(final ExtensionElem node) throws RuntimeException {
if (node.getName().equals(name)) {
result.set(node.getExpr());
} else {
super.meet(node);
}
}
});
return result.get();
}
private final class Rendering implements QueryModelVisitor<RuntimeException> {
final TupleExpr root;
@Nullable
final Dataset dataset;
final String body;
String base;
private final StringBuilder builder;
private final Set<String> namespaces;
private int indent;
private Rendering(final TupleExpr node, @Nullable final Dataset dataset) {
this.root = new QueryRoot(Preconditions.checkNotNull(node));
this.dataset = dataset;
this.builder = new StringBuilder();
this.namespaces = Sets.newHashSet();
this.indent = 0;
emit(Query.create(this.root, this.dataset, SPARQLRenderer.this.forceSelect));
this.body = this.builder.toString();
this.builder.setLength(0);
}
// BASIC RENDERING METHODS (STRING, VALUES, CONDITIONALS, NEWLINE AND BRACES, ERRORS)
private Rendering emitIf(final boolean condition, final Object object) {
if (condition) {
emit(object);
}
return this;
}
private Rendering emit(final Iterable<?> values, final String separator) {
boolean first = true;
for (final Object value : values) {
if (!first) {
emit(separator);
}
emit(value);
first = false;
}
return this;
}
@SuppressWarnings("unchecked")
private Rendering emit(final Object value) {
if (value instanceof String) {
return emit((String) value);
} else if (value instanceof QueryModelNode) {
emit((QueryModelNode) value);
} else if (value instanceof BNode) {
emit((BNode) value);
} else if (value instanceof URI) {
emit((URI) value);
} else if (value instanceof Literal) {
emit((Literal) value);
} else if (value instanceof List<?>) {
emit((List<StatementPattern>) value);
} else if (value instanceof Query) {
emit((Query) value);
}
return this;
}
private Rendering emit(final String string) {
this.builder.append(string);
return this;
}
private Rendering emit(final Literal literal) {
if (XMLSchema.INTEGER.equals(literal.getDatatype())) {
this.builder.append(literal.getLabel());
} else {
this.builder.append("\"");
escape(literal.getLabel(), this.builder);
this.builder.append("\"");
if (literal.getDatatype() != null) {
this.builder.append("^^");
emit(literal.getDatatype());
} else if (literal.getLanguage() != null) {
this.builder.append("@").append(literal.getLanguage());
}
}
return this;
}
private Rendering emit(final BNode bnode) {
this.builder.append("_:").append(bnode.getID());
return this;
}
private Rendering emit(final URI uri) {
if (uri.getNamespace().equals("http://www.openlinksw.com/schema/sparql/extensions#")) {
this.builder.append("bif:").append(uri.getLocalName()); // for Virtuoso builtins
} else {
final String prefix = SPARQLRenderer.this.prefixes.get(uri.getNamespace());
if (prefix != null) {
if (this.namespaces != null) {
this.namespaces.add(uri.getNamespace());
}
this.builder.append(prefix).append(':').append(uri.getLocalName());
} else {
this.builder.append("<");
escape(uri.toString(), this.builder);
this.builder.append(">");
}
}
return this;
}
private Rendering emit(final List<StatementPattern> bgp) {
if (bgp.isEmpty()) {
return this;
}
final Var c = bgp.get(0).getContextVar();
if (c != null) {
emit("GRAPH ").emit(c).emit(" ").openBrace();
}
StatementPattern l = null;
for (final StatementPattern n : bgp) {
final Var s = n.getSubjectVar();
final Var p = n.getPredicateVar();
final Var o = n.getObjectVar();
if (l == null) {
emit(s).emit(" ").emit(p).emit(" ").emit(o); // s p o
} else if (!l.getSubjectVar().equals(n.getSubjectVar())) {
emit(" .").newline().emit(s).emit(" ").emit(p).emit(" ").emit(o); // .\n s p o
} else if (!l.getPredicateVar().equals(n.getPredicateVar())) {
emit(" ;").newline().emit("\t").emit(p).emit(" ").emit(o); // ;\n\t p o
} else if (!l.getObjectVar().equals(n.getObjectVar())) {
emit(" , ").emit(o); // , o
}
l = n;
}
emit(" .");
if (c != null) {
closeBrace();
}
return this;
}
private Rendering emit(final Query query) {
if (query.root != this.root) {
openBrace();
}
if (query.form == Form.ASK) {
emit("ASK");
} else if (query.form == Form.CONSTRUCT) {
emit("CONSTRUCT ").openBrace().emit(query.construct).closeBrace();
} else if (query.form == Form.DESCRIBE) {
emit("DESCRIBE");
for (final ProjectionElem p : query.select) {
final ExtensionElem e = p.getSourceExpression();
emit(" ").emit(
e != null && e.getExpr() instanceof ValueConstant ? e.getExpr() : p);
}
} else if (query.form == Form.SELECT) {
emit("SELECT");
if (query.modifier != null) {
emit(" ").emit(query.modifier.toString().toUpperCase());
}
if (query.select.isEmpty()) {
int count = 0;
for (final String var : query.where.getBindingNames()) {
final ValueExpr expr = getVarExpr(query.where, var);
if (!var.startsWith("-")) {
if (expr == null) {
emit(" ?").emit(var);
} else {
emit(" (").emit(expr).emit(" AS ?").emit(var).emit(")");
}
++count;
}
}
if (count == 0) {
emit(" *");
}
} else {
emit(" ").emit(query.select, " ");
}
}
if (query.from != null) {
for (final URI uri : query.from.getDefaultGraphs()) {
newline().emit("FROM ").emit(uri);
}
for (final URI uri : query.from.getNamedGraphs()) {
newline().emit("FROM NAMED ").emit(uri);
}
}
if (query.form != Form.DESCRIBE || !(query.where instanceof SingletonSet)) {
newline().emit("WHERE ").openBrace().emit(query.where).closeBrace();
}
if (!query.groupBy.isEmpty()) {
newline().emit("GROUP BY");
for (final ProjectionElem n : query.groupBy) {
emit(" ?").emit(n.getTargetName());
}
}
if (query.having != null) {
newline().emit("HAVING (").emit(query.having).emit(")");
}
if (!query.orderBy.isEmpty()) {
newline().emit("ORDER BY ").emit(query.orderBy, " ");
}
if (query.form != Form.ASK) {
if (query.offset != null) {
newline().emit("OFFSET " + query.offset);
}
if (query.limit != null) {
newline().emit("LIMIT " + query.limit);
// newline().emit("LIMIT " + (query.limit + 1)); // TODO Virtuoso fix :-(
}
}
if (query.root != this.root) {
closeBrace();
}
return this;
}
private Rendering emit(final QueryModelNode n) {
final QueryModelNode p = n.getParentNode();
final boolean braces = n instanceof TupleExpr && p != null
&& !(p instanceof TupleExpr);
if (braces) {
openBrace();
}
n.visit(this);
if (braces) {
closeBrace();
}
return this;
}
private Rendering emit(final QueryModelNode node, final boolean parenthesis) { // TODO
if (parenthesis) {
if (node instanceof TupleExpr) {
openBrace();
} else {
emit("(");
}
}
emit(node);
if (parenthesis) {
if (node instanceof TupleExpr) {
closeBrace();
} else {
emit(")");
}
}
return this;
}
private Rendering openBrace() {
emit("{");
++this.indent;
newline();
return this;
}
private Rendering closeBrace() {
--this.indent;
newline();
emit("}");
return this;
}
private Rendering newline() {
emit("\n");
for (int i = 0; i < this.indent; ++i) {
emit("\t");
}
return this;
}
private Rendering fail(final String message, final QueryModelNode node) {
throw new IllegalArgumentException("SPARQL rendering failed. " + message
+ (node == null ? "null" : node.getClass().getSimpleName() + "\n" + node));
}
// TupleExpr: root query nodes
@Override
public void meet(final OrderElem n) {
emit(n.isAscending() ? "ASC(" : "DESC(").emit(n.getExpr()).emit(")");
}
@Override
public void meet(final ProjectionElemList node) {
emit(node.getElements(), " ");
}
@Override
public void meet(final ProjectionElem n) {
final String source = n.getSourceName();
final String target = n.getTargetName();
final ValueExpr expr = n.getSourceExpression() == null ? null : n
.getSourceExpression().getExpr();
if (target.startsWith("-")) {
if (expr != null) {
emit("(").emit(expr).emit(" AS ?").emit(sanitize(target)).emit(")");
}
} else if (expr != null) {
emit("(").emit(expr).emit(" AS ?").emit(target).emit(")");
} else if (!equalOrNull(source, target)) {
emit("(?").emit(source).emit(" AS ?").emit(target).emit(")");
} else {
emit("?").emit(target);
}
}
@Override
public void meet(final GroupElem n) {
final ProjectionElem e = new ProjectionElem();
e.setTargetName(n.getName());
e.setSourceName(n.getName());
e.setSourceExpression(new ExtensionElem(n.getOperator(), n.getName()));
meet(e);
}
@Override
public void meet(final DescribeOperator n) {
emit(Query.create(n, null, SPARQLRenderer.this.forceSelect));
}
@Override
public void meet(final QueryRoot n) {
emit(Query.create(n, null, SPARQLRenderer.this.forceSelect));
}
@Override
public void meet(final Projection n) {
emit(Query.create(n, null, SPARQLRenderer.this.forceSelect));
}
@Override
public void meet(final MultiProjection n) {
emit(Query.create(n, null, SPARQLRenderer.this.forceSelect));
}
@Override
public void meet(final Distinct n) {
emit(Query.create(n, null, SPARQLRenderer.this.forceSelect));
}
@Override
public void meet(final Reduced n) {
emit(Query.create(n, null, SPARQLRenderer.this.forceSelect));
}
@Override
public void meet(final Group n) {
emit(Query.create(n, null, SPARQLRenderer.this.forceSelect));
}
@Override
public void meet(final Order n) {
emit(Query.create(n, null, SPARQLRenderer.this.forceSelect));
}
@Override
public void meet(final Slice n) {
emit(Query.create(n, null, SPARQLRenderer.this.forceSelect));
}
// TupleExpr: leaf nodes
@Override
public void meet(final EmptySet n) {
final QueryModelNode p = n.getParentNode();
if (p.getParentNode() != null && !(p.getParentNode() instanceof QueryRoot)) {
throw new IllegalArgumentException(
"Cannot translate EmptySet inside the body of a query / update operation");
}
emit("CONSTRUCT {} WHERE {}");
}
@Override
public void meet(final SingletonSet n) {
// nothing to do: braces, if necessary, are emitted by parent
}
@Override
public void meet(final BindingSetAssignment n) {
final Set<String> names = n.getBindingNames();
if (names.isEmpty()) {
emit("VALUES {}");
} else if (names.size() == 1) {
final String name = names.iterator().next();
emit("VALUES ?").emit(name).emit(" ").openBrace();
boolean first = true;
for (final BindingSet bindings : n.getBindingSets()) {
emitIf(!first, " ").emit(defaultIfNull(bindings.getValue(name), "UNDEF"));
first = false;
}
closeBrace();
} else {
emit("VALUES (?").emit(names, " ?").emit(") ").openBrace();
boolean firstBinding = true;
for (final BindingSet bindings : n.getBindingSets()) {
if (!firstBinding) {
newline();
}
emit("(");
boolean first = true;
for (final String name : names) {
emitIf(!first, " ").emit(defaultIfNull(bindings.getValue(name), "UNDEF"));
first = false;
}
emit(")");
firstBinding = false;
}
closeBrace();
}
}
@Override
public void meet(final StatementPattern n) {
emit(getBGP(n));
}
// TupleExpr: unary
@Override
public void meet(final Extension n) {
emit(n.getArg());
if (!(n.getArg() instanceof SingletonSet)) {
newline();
}
boolean first = true;
for (final ExtensionElem e : n.getElements()) {
final ValueExpr expr = e.getExpr();
if (!(expr instanceof Var) || !((Var) expr).getName().equals(e.getName())) {
if (!first) {
newline();
}
emit("BIND (").emit(expr).emit(" AS ?").emit(e.getName()).emit(")");
first = false;
}
}
}
@Override
public void meet(final ExtensionElem n) {
throw new Error("Should not be directly called");
}
@Override
public void meet(final Filter n) {
final ValueExpr cond = n.getCondition();
final boolean nopar = cond instanceof Exists || cond instanceof Not
&& ((Not) cond).getArg() instanceof Exists;
emit(n.getArg());
if (!(n.getArg() instanceof SingletonSet)) {
newline();
}
emit("FILTER ").emit(cond, !nopar);
}
@Override
public void meet(final Service n) {
newline().emit("SERVICE ").emitIf(n.isSilent(), "SILENT ").openBrace()
.emit(n.getServiceExpr()).closeBrace().emit(" ").emit(n.getServiceRef());
}
// TupleExpr: binary
@Override
public void meet(final Join n) {
final List<StatementPattern> bgp = getBGP(n);
if (bgp != null) {
emit(bgp);
} else {
final TupleExpr l = n.getLeftArg();
final TupleExpr r = n.getRightArg();
final boolean norpar = r instanceof Join || r instanceof StatementPattern
|| r instanceof SingletonSet || r instanceof Service || r instanceof Union
|| r instanceof BindingSetAssignment || r instanceof ArbitraryLengthPath;
emit(l).newline().emit(r, !norpar);
}
}
@Override
public void meet(final LeftJoin n) {
final TupleExpr l = n.getLeftArg();
final TupleExpr r = n.getCondition() == null ? n.getRightArg() : //
new Filter(n.getRightArg(), n.getCondition());
emit(l);
if (!(l instanceof SingletonSet)) {
newline();
}
emit("OPTIONAL ").emit(r, true);
}
@Override
public void meet(final Union n) {
final TupleExpr l = n.getLeftArg();
final TupleExpr r = n.getRightArg();
final ZeroLengthPath p = l instanceof ZeroLengthPath ? (ZeroLengthPath) l
: r instanceof ZeroLengthPath ? (ZeroLengthPath) r : null;
if (p == null) {
emit(l, !(l instanceof Union)).emit(" UNION ").emit(r, !(r instanceof Union));
} else {
final Var s = p.getSubjectVar();
final Var o = p.getObjectVar();
final Var c = p.getContextVar();
if (c != null) {
emit("GRAPH ").emit(c).emit(" ").openBrace();
}
emit(s).emit(" ").emitPropertyPath(n, s, o).emit(" ").emit(o);
if (c != null) {
closeBrace();
}
}
}
@Override
public void meet(final Difference n) {
final TupleExpr l = n.getLeftArg();
final TupleExpr r = n.getRightArg();
emit(l, true).emit(" MINUS ").emit(r, true);
}
// TupleExpr: paths
@Override
public void meet(final ArbitraryLengthPath n) {
final Var s = n.getSubjectVar();
final Var o = n.getObjectVar();
final Var c = n.getContextVar();
if (c != null) {
emit("GRAPH ").emit(c).openBrace();
}
emit(s).emit(" ").emitPropertyPath(n, s, o).emit(" ").emit(o).emit(" .");
if (c != null) {
closeBrace();
}
}
@Override
public void meet(final ZeroLengthPath node) {
throw new Error("Should not be directly called");
}
private Rendering emitPropertyPath(final TupleExpr node, final Var start, final Var end) {
// Note: elt1 / elt2 and ^(complex exp) do not occur in Sesame algebra
final boolean parenthesis = !(node instanceof StatementPattern)
&& (node.getParentNode() instanceof ArbitraryLengthPath || node
.getParentNode() instanceof Union);
emitIf(parenthesis, "(");
if (node instanceof StatementPattern) {
// handles iri, ^iri
final StatementPattern pattern = (StatementPattern) node;
final boolean inverse = isInversePath(pattern, start, end);
if (!pattern.getPredicateVar().hasValue()
|| !pattern.getPredicateVar().isAnonymous()) {
fail("Unsupported path expression. Check node: ", node);
}
emitIf(inverse, "^").emit(pattern.getPredicateVar().getValue());
} else if (node instanceof Join) {
final Join j = (Join) node;
final TupleExpr l = j.getLeftArg();
final TupleExpr r = j.getRightArg();
final StatementPattern s = l instanceof StatementPattern ? (StatementPattern) l
: r instanceof StatementPattern ? (StatementPattern) r : null;
if (s == null) {
return fail("Cannot process property path", node);
}
final Var m = s.getSubjectVar().equals(start) || s.getSubjectVar().equals(end) ? s
.getObjectVar() : s.getSubjectVar();
emitPropertyPath(l, start, m);
emit("/");
emitPropertyPath(r, m, end);
} else if (node instanceof ArbitraryLengthPath) {
// handles elt*, elt+
final ArbitraryLengthPath path = (ArbitraryLengthPath) node;
Preconditions.checkArgument(path.getMinLength() <= 1, "Invalid path length");
emitPropertyPath(path.getPathExpression(), start, end).emit(
path.getMinLength() == 0 ? "*" : "+");
} else if (node instanceof Union) {
// handles elt?, elt1|elt2|...
final Union union = (Union) node;
if (union.getLeftArg() instanceof ZeroLengthPath) {
emitPropertyPath(union.getRightArg(), start, end).emit("?");
} else if (union.getRightArg() instanceof ZeroLengthPath) {
emitPropertyPath(union.getLeftArg(), start, end).emit("?");
} else {
emitPropertyPath(union.getLeftArg(), start, end);
emit("|");
emitPropertyPath(union.getRightArg(), start, end);
}
} else if (node instanceof Filter) {
// handles !iri, !(iri1,iri2,...) with possibly inverse properties
final Filter filter = (Filter) node;
Preconditions.checkArgument(filter.getArg() instanceof StatementPattern);
final StatementPattern pattern = (StatementPattern) filter.getArg();
final boolean inverse = isInversePath(pattern, start, end);
Preconditions.checkArgument(!pattern.getPredicateVar().hasValue()
&& pattern.getPredicateVar().isAnonymous());
final Set<URI> negatedProperties = Sets.newLinkedHashSet();
extractNegatedProperties(filter.getCondition(), negatedProperties);
if (negatedProperties.size() == 1) {
emit("!").emitIf(inverse, "^").emit(negatedProperties.iterator().next());
} else {
emit("!(");
boolean first = true;
for (final URI negatedProperty : negatedProperties) {
emitIf(!first, "|").emitIf(inverse, "^").emit(negatedProperty);
first = false;
}
emit(")");
}
} else {
fail("Unsupported path expression node", node);
}
return emitIf(parenthesis, ")");
}
private void extractNegatedProperties(final ValueExpr condition,
final Set<URI> negatedProperties) {
if (condition instanceof And) {
final And and = (And) condition;
extractNegatedProperties(and.getLeftArg(), negatedProperties);
extractNegatedProperties(and.getRightArg(), negatedProperties);
} else if (condition instanceof Compare) {
final Compare compare = (Compare) condition;
Preconditions.checkArgument(compare.getOperator() == CompareOp.NE);
if (compare.getLeftArg() instanceof ValueConstant) {
Preconditions.checkArgument(compare.getRightArg() instanceof Var);
negatedProperties.add((URI) ((ValueConstant) compare.getLeftArg()).getValue());
} else if (compare.getRightArg() instanceof ValueConstant) {
Preconditions.checkArgument(compare.getLeftArg() instanceof Var);
negatedProperties
.add((URI) ((ValueConstant) compare.getRightArg()).getValue());
} else {
fail("Unsupported path expression. Check condition node: ", condition);
}
}
}
private boolean isInversePath(final StatementPattern node, final Var start, final Var end) {
if (node.getSubjectVar().equals(start)) {
Preconditions.checkArgument(node.getObjectVar().equals(end));
return false;
} else if (node.getObjectVar().equals(start)) {
Preconditions.checkArgument(node.getSubjectVar().equals(end));
return true;
} else {
fail("Unsupported path expression. Check node: ", node);
return false;
}
}
// TupleExpr: unsupported
@Override
public void meet(final Intersection n) {
fail("Not a SPARQL 1.1 node", n);
}
// === SPARQL UPDATE ===
@Override
public void meet(final Add add) {
throw new UnsupportedOperationException();
}
@Override
public void meet(final Clear clear) {
throw new UnsupportedOperationException();
}
@Override
public void meet(final Copy copy) {
throw new UnsupportedOperationException();
}
@Override
public void meet(final Create create) {
throw new UnsupportedOperationException();
}
@Override
public void meet(final DeleteData deleteData) {
throw new UnsupportedOperationException();
}
@Override
public void meet(final InsertData insertData) {
throw new UnsupportedOperationException();
}
@Override
public void meet(final Load load) {
throw new UnsupportedOperationException();
}
@Override
public void meet(final Modify modify) {
throw new UnsupportedOperationException();
}
@Override
public void meet(final Move move) {
throw new UnsupportedOperationException();
}
// === VALUE EXPR ===
// ValueExpr: variables and constants
@Override
public void meet(final ValueConstant n) {
emit(n.getValue());
}
@Override
public void meet(final Var n) {
final String name = n.getName();
if (n.getValue() != null) {
emit(n.getValue());
} else if (!n.isAnonymous()) {
emit("?" + n.getName());
} else {
final ValueExpr expr = getVarExpr(this.root, n.getName());
if (expr != null) {
emit(expr);
} else if (getVarRefs(this.root, n.getName()) <= 1) {
emit("[]");
} else {
emit("?").emit(sanitize(name));
}
}
}
// ValueExpr: comparison, math and boolean operators
@Override
public void meet(final Compare n) {
final QueryModelNode p = n.getParentNode();
final boolean par = p instanceof Not || p instanceof MathExpr;
emitIf(par, "(").emit(n.getLeftArg()).emit(" ").emit(n.getOperator().getSymbol())
.emit(" ").emit(n.getRightArg()).emitIf(par, ")");
}
@Override
public void meet(final ListMemberOperator n) {
final QueryModelNode p = n.getParentNode();
final boolean par = p instanceof Not || p instanceof MathExpr;
final List<ValueExpr> args = n.getArguments();
emitIf(par, "(").emit(args.get(0)).emit(" in (")
.emit(args.subList(1, args.size()), ", ").emit(")").emitIf(par, ")");
}
@Override
public void meet(final MathExpr n) {
final QueryModelNode p = n.getParentNode();
final MathOp op = n.getOperator();
final MathOp pop = p instanceof MathExpr ? ((MathExpr) p).getOperator() : null;
final boolean r = p instanceof BinaryValueOperator
&& n == ((BinaryValueOperator) p).getRightArg();
final boolean par = p instanceof Not //
|| (op == MathOp.PLUS || op == MathOp.MINUS)
&& (pop == MathOp.MINUS && r //
|| pop == MathOp.DIVIDE || pop == MathOp.MULTIPLY)
|| (op == MathOp.MULTIPLY || op == MathOp.DIVIDE) && pop == MathOp.DIVIDE && r;
emitIf(par, "(").emit(n.getLeftArg()).emit(" ").emit(op.getSymbol()).emit(" ")
.emit(n.getRightArg()).emitIf(par, ")");
}
@Override
public void meet(final And n) {
final QueryModelNode p = n.getParentNode();
final boolean needPar = p instanceof Not || p instanceof MathExpr
|| p instanceof ListMemberOperator || p instanceof Compare;
emitIf(needPar, "(").emit(n.getLeftArg()).emit(" && ").emit(n.getRightArg())
.emitIf(needPar, ")");
}
@Override
public void meet(final Or n) {
final QueryModelNode p = n.getParentNode();
final boolean needPar = p instanceof Not || p instanceof And || p instanceof MathExpr
|| p instanceof ListMemberOperator || p instanceof Compare;
emitIf(needPar, "(").emit(n.getLeftArg()).emit(" || ").emit(n.getRightArg())
.emitIf(needPar, ")");
}
@Override
public void meet(final Not n) {
final String op = n.getArg() instanceof Exists ? "NOT " : "!";
emit(op).emit(n.getArg());
}
// ValueExpr: aggregates
@Override
public void meet(final Count node) {
emit("COUNT(").emitIf(node.isDistinct(), "DISTINCT ")
.emit(defaultIfNull(node.getArg(), "*")).emit(")");
}
@Override
public void meet(final Sum node) {
emit("SUM(").emitIf(node.isDistinct(), "DISTINCT ").emit(node.getArg()).emit(")");
}
@Override
public void meet(final Min node) {
emit("MIN(").emitIf(node.isDistinct(), "DISTINCT ").emit(node.getArg()).emit(")");
}
@Override
public void meet(final Max node) {
emit("MAX(").emitIf(node.isDistinct(), "DISTINCT ").emit(node.getArg()).emit(")");
}
@Override
public void meet(final Avg node) {
emit("AVG(").emitIf(node.isDistinct(), "DISTINCT ").emit(node.getArg()).emit(")");
}
@Override
public void meet(final Sample node) {
emit("SAMPLE(").emitIf(node.isDistinct(), "DISTINCT ").emit(node.getArg()).emit(")");
}
@Override
public void meet(final GroupConcat node) {
emit("GROUP_CONCAT(").emitIf(node.isDistinct(), "DISTINCT ").emit(node.getArg());
if (node.getSeparator() != null) {
emit(" ; separator=").emit(node.getSeparator());
}
emit(")");
}
// ValueExpr: function calls
@Override
public void meet(final Str n) {
emit("STR(").emit(n.getArg()).emit(")");
}
@Override
public void meet(final Lang n) {
emit("LANG(").emit(n.getArg()).emit(")");
}
@Override
public void meet(final LangMatches n) {
emit("LANGMATCHES(").emit(n.getLeftArg()).emit(", ").emit(n.getRightArg()).emit(")");
}
@Override
public void meet(final Datatype n) {
emit("DATATYPE(").emit(n.getArg()).emit(")");
}
@Override
public void meet(final Bound n) {
emit("BOUND(").emit(n.getArg()).emit(")");
}
@Override
public void meet(final IRIFunction n) {
emit("IRI(").emit(n.getArg()).emit(")");
if (n.getBaseURI() != null) {
this.base = n.getBaseURI();
}
}
@Override
public void meet(final BNodeGenerator n) {
final ValueExpr expr = n.getNodeIdExpr();
emit("BNODE(").emitIf(expr != null, expr).emit(")");
}
@Override
public void meet(final FunctionCall n) {
final String uri = n.getURI();
String name = NAMES.get(uri);
if (name == null && NAMES.values().contains(uri.toUpperCase())) {
name = n.getURI().toUpperCase();
}
emit(name != null ? name : new URIImpl(uri)).emit("(").emit(n.getArgs(), ", ")
.emit(")");
}
@Override
public void meet(final Coalesce n) {
emit("COALESCE(").emit(n.getArguments(), ", ").emit(")");
}
@Override
public void meet(final If n) {
emit("IF(").emit(n.getCondition()).emit(", ").emit(n.getResult()).emit(", ")
.emit(n.getAlternative()).emit(")");
}
@Override
public void meet(final SameTerm n) {
emit("sameTerm(").emit(n.getLeftArg()).emit(", ").emit(n.getRightArg()).emit(")");
}
@Override
public void meet(final IsURI n) {
emit("isIRI(").emit(n.getArg()).emit(")");
}
@Override
public void meet(final IsBNode n) {
emit("isBLANK(").emit(n.getArg()).emit(")");
}
@Override
public void meet(final IsLiteral n) {
emit("isLITERAL(").emit(n.getArg()).emit(")");
}
@Override
public void meet(final IsNumeric n) {
emit("isNUMERIC(").emit(n.getArg()).emit(")");
}
@Override
public void meet(final Regex n) {
emit("REGEX(").emit(n.getArg()).emit(", ").emit(n.getPatternArg());
if (n.getFlagsArg() != null) {
emit(", ").emit(n.getFlagsArg());
}
emit(")");
}
@Override
public void meet(final Exists node) {
emit("EXISTS ").emit(node.getSubQuery());
}
// ValueExpr: unsupported nodes
@Override
public void meet(final IsResource n) {
fail("Not a SPARQL 1.1 node", n);
}
@Override
public void meet(final Label n) {
fail("Not a SPARQL 1.1 node", n);
}
@Override
public void meet(final Like n) {
fail("Not a SPARQL 1.1 node", n);
}
@Override
public void meet(final LocalName n) {
fail("Not a SPARQL 1.1 node", n);
}
@Override
public void meet(final Namespace n) {
fail("Not a SPARQL 1.1 node", n);
}
@Override
public void meet(final In n) {
fail("Not a SPARQL 1.1 node", n);
}
@Override
public void meet(final CompareAll n) {
fail("Not a SPARQL 1.1 node", n);
}
@Override
public void meet(final CompareAny n) {
fail("Not a SPARQL 1.1 node", n);
}
// OTHER
@Override
public void meetOther(final QueryModelNode n) {
fail("Unknown node", n);
}
}
private enum Form {
SELECT, CONSTRUCT, ASK, DESCRIBE
}
private enum Modifier {
DISTINCT, REDUCED
}
private static final class Query {
final QueryModelNode root;
final Form form;
@Nullable
final Modifier modifier;
final List<ProjectionElem> select;
@Nullable
final TupleExpr construct;
@Nullable
final Dataset from;
final TupleExpr where;
final List<ProjectionElem> groupBy;
@Nullable
final ValueExpr having;
final List<OrderElem> orderBy;
@Nullable
final Long offset;
@Nullable
final Long limit;
static Query create(final TupleExpr rootNode, @Nullable final Dataset dataset,
final boolean forceSelect) {
Preconditions.checkNotNull(rootNode);
// Handle special trivial case
if (rootNode instanceof EmptySet) {
return new Query(rootNode, Form.CONSTRUCT, null, null, rootNode, dataset,
rootNode, null, null, null, null, null);
}
// Local variables
Form form = null;
Modifier modifier = null;
final List<ProjectionElem> select = Lists.newArrayList();
TupleExpr construct = null;
TupleExpr where = null;
final List<ProjectionElem> groupBy = Lists.newArrayList();
ValueExpr having = null;
final List<OrderElem> orderBy = Lists.newArrayList();
Long offset = null;
Long limit = null;
final List<UnaryTupleOperator> nodes = extractQueryNodes(rootNode, false);
where = nodes.size() > 0 ? nodes.get(nodes.size() - 1).getArg() : rootNode;
for (final UnaryTupleOperator node : nodes) {
if (node instanceof DescribeOperator) {
form = Form.DESCRIBE;
} else if (node instanceof Distinct) {
modifier = Modifier.DISTINCT;
} else if (node instanceof Reduced) {
modifier = Modifier.REDUCED;
} else if (node instanceof Projection) {
final Map<String, ExtensionElem> extensions = extractExtensions(node);
final List<ProjectionElem> projections = ((Projection) node)
.getProjectionElemList().getElements();
final boolean isConstruct = projections.size() >= 3
&& "subject".equals(projections.get(0).getTargetName())
&& "predicate".equals(projections.get(1).getTargetName())
&& "object".equals(projections.get(2).getTargetName())
&& (projections.size() == 3 || projections.size() == 4
&& "context".equals(projections.get(3).getTargetName()));
if (isConstruct && !forceSelect) {
form = Form.CONSTRUCT;
construct = extractConstructExpression(extensions,
Collections.singleton(((Projection) node) //
.getProjectionElemList()));
} else {
form = form == null ? Form.SELECT : form;
for (final ProjectionElem projection : projections) {
final String variable = projection.getTargetName();
ExtensionElem extension = extensions.get(variable);
if (extension == null && projection.getSourceName() != null) {
extension = extensions.get(projection.getSourceName());
}
final ProjectionElem newProjection = new ProjectionElem();
newProjection.setTargetName(variable);
newProjection.setSourceExpression(extension);
newProjection.setSourceName(extension == null
|| !(extension.getExpr() instanceof Var) ? projection
.getSourceName() : ((Var) extension.getExpr()).getName());
select.add(newProjection);
}
}
} else if (node instanceof MultiProjection) {
form = Form.CONSTRUCT;
construct = extractConstructExpression(extractExtensions(node),
((MultiProjection) node).getProjections());
} else if (node instanceof Group) {
final Group group = (Group) node;
final Map<String, ExtensionElem> extensions = extractExtensions(group.getArg());
for (final String variableName : group.getGroupBindingNames()) {
final ExtensionElem extension = extensions.get(variableName);
final ProjectionElem projection = new ProjectionElem();
projection.setTargetName(variableName);
projection.setSourceExpression(extension);
projection.setSourceName(extension == null
|| !(extension.getExpr() instanceof Var) ? variableName
: ((Var) extension.getExpr()).getName());
groupBy.add(projection);
}
} else if (node instanceof Order) {
orderBy.addAll(((Order) node).getElements());
} else if (node instanceof Slice) {
final Slice slice = (Slice) node;
offset = slice.getOffset() < 0 ? null : slice.getOffset();
limit = slice.getLimit() < 0 ? null : slice.getLimit();
if (form == null && slice.getOffset() == 0 && slice.getLimit() == 1) {
if (forceSelect) {
form = Form.SELECT;
limit = 1L;
// limit = 2L; // TODO: workaround for Virtuoso
} else {
form = Form.ASK;
}
}
} else if (node instanceof Filter) {
having = ((Filter) node).getCondition();
}
}
form = defaultIfNull(form, Form.SELECT);
if (form == Form.CONSTRUCT && construct == null) {
construct = new SingletonSet();
}
return new Query(rootNode, form, modifier, select, construct, dataset, where, groupBy,
having, orderBy, offset, limit);
}
private static List<UnaryTupleOperator> extractQueryNodes(final TupleExpr rootNode,
final boolean haltOnGroup) {
final List<UnaryTupleOperator> nodes = Lists.newArrayList();
TupleExpr queryNode = rootNode;
while (queryNode instanceof UnaryTupleOperator) {
nodes.add((UnaryTupleOperator) queryNode);
queryNode = ((UnaryTupleOperator) queryNode).getArg();
}
boolean describeFound = false;
boolean modifierFound = false;
boolean projectionFound = false;
boolean groupFound = false;
boolean orderFound = false;
boolean sliceFound = false;
boolean extensionFound = false;
int index = 0;
while (index < nodes.size()) {
final UnaryTupleOperator node = nodes.get(index);
if (node instanceof DescribeOperator && !describeFound) {
describeFound = true;
} else if ((node instanceof Distinct || node instanceof Reduced) && !modifierFound
&& !projectionFound) {
modifierFound = true;
} else if ((node instanceof Projection || node instanceof MultiProjection)
&& !projectionFound) {
projectionFound = true;
} else if (node instanceof Group && !groupFound && !haltOnGroup) {
groupFound = true;
} else if (node instanceof Order && !orderFound) {
orderFound = true;
} else if (node instanceof Slice && !sliceFound) {
sliceFound = true;
} else if (node instanceof Filter && !groupFound && !haltOnGroup) {
int i = index + 1;
for (; i < nodes.size() && nodes.get(i) instanceof Extension;) {
++i;
}
if (i < nodes.size() && nodes.get(i) instanceof Group) {
groupFound = true;
index = i;
} else {
break;
}
} else if (node instanceof Extension && !extensionFound) {
extensionFound = true;
} else if (!(node instanceof QueryRoot) || index > 0) {
break;
}
++index;
}
return nodes.subList(0, index);
}
private static Map<String, ExtensionElem> extractExtensions(final TupleExpr rootNode) {
final Map<String, ExtensionElem> map = Maps.newHashMap();
for (final UnaryTupleOperator node : extractQueryNodes(rootNode, true)) {
if (node instanceof Extension) {
for (final ExtensionElem elem : ((Extension) node).getElements()) {
final String variable = elem.getName();
final ValueExpr expression = elem.getExpr();
if (!(expression instanceof Var)
|| !((Var) expression).getName().equals(variable)) {
map.put(variable, elem);
}
}
}
}
return map;
}
private static TupleExpr extractConstructExpression(
final Map<String, ExtensionElem> extensions,
final Iterable<? extends ProjectionElemList> multiProjections) {
TupleExpr expression = null;
for (final ProjectionElemList projections : multiProjections) {
final Var subj = extractConstructVar(extensions, projections.getElements().get(0));
final Var pred = extractConstructVar(extensions, projections.getElements().get(1));
final Var obj = extractConstructVar(extensions, projections.getElements().get(2));
final Var ctx = projections.getElements().size() < 4 ? null : extractConstructVar(
extensions, projections.getElements().get(3));
final StatementPattern pattern = new StatementPattern(
ctx == null ? Scope.DEFAULT_CONTEXTS : Scope.NAMED_CONTEXTS, subj, pred,
obj, ctx);
expression = expression == null ? pattern : new Join(expression, pattern);
}
return expression;
}
private static Var extractConstructVar(final Map<String, ExtensionElem> extensions,
final ProjectionElem projection) {
final ExtensionElem extension = extensions.get(projection.getSourceName());
String name = projection.getSourceName();
if (name.startsWith("-anon-")) {
name += "-construct";
}
if (extension == null || extension.getExpr() instanceof BNodeGenerator) {
final Var var = new Var(name);
var.setAnonymous(name.startsWith("-anon-"));
return var;
} else if (extension.getExpr() instanceof ValueConstant) {
final ValueConstant constant = (ValueConstant) extension.getExpr();
return new Var(name, constant.getValue());
} else {
throw new UnsupportedOperationException(
"Unsupported extension in construct query: " + extension);
}
}
private Query(//
final QueryModelNode root, //
final Form form, //
@Nullable final Modifier modifier, //
@Nullable final Iterable<? extends ProjectionElem> selectist, //
@Nullable final TupleExpr construct, //
@Nullable final Dataset from, //
final TupleExpr where, //
@Nullable final Iterable<? extends ProjectionElem> groupByt, //
@Nullable final ValueExpr having, //
@Nullable final Iterable<? extends OrderElem> orderBy, //
@Nullable final Long offset, //
@Nullable final Long limit) {
this.root = Preconditions.checkNotNull(root);
this.form = Preconditions.checkNotNull(form);
this.modifier = modifier;
this.select = selectist == null ? ImmutableList.<ProjectionElem>of() : ImmutableList
.copyOf(selectist);
this.construct = construct;
this.from = from;
this.where = Preconditions.checkNotNull(where);
this.groupBy = groupByt == null ? ImmutableList.<ProjectionElem>of() : ImmutableList
.copyOf(groupByt);
this.having = having;
this.orderBy = orderBy == null ? ImmutableList.<OrderElem>of() : ImmutableList
.copyOf(orderBy);
this.offset = offset;
this.limit = limit;
}
}
}