/**
Copyright (C) SYSTAP, LLC DBA Blazegraph 2006-2016. All rights reserved.
Contact:
SYSTAP, LLC DBA Blazegraph
2501 Calvert ST NW #106
Washington, DC 20008
licenses@blazegraph.com
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; version 2 of the License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package com.bigdata.blueprints;
import java.util.Arrays;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
import org.apache.log4j.Logger;
import org.openrdf.model.Literal;
import org.openrdf.model.URI;
import com.tinkerpop.blueprints.Edge;
import com.tinkerpop.blueprints.Predicate;
import com.tinkerpop.blueprints.GraphQuery;
import com.tinkerpop.blueprints.Vertex;
/**
* Translate a low-performance Blueprints GraphQuery into a high-performance
* SPARQL query.
*
* @author mikepersonick
*
*/
public class BigdataGraphQuery implements GraphQuery {
protected final static transient Logger log = Logger.getLogger(BigdataGraphQuery.class);
/**
* These are the only Predicate implementations we handle currently.
*/
protected static List<Class> knownPredicates = Arrays.asList(new Class[] {
BigdataPredicate.class,
com.tinkerpop.blueprints.Query.Compare.class,
com.tinkerpop.blueprints.Contains.class,
com.tinkerpop.blueprints.Compare.class
});
/**
* The graph.
*/
private final BigdataGraph graph;
/**
* URI used for typing elements.
*/
protected final URI TYPE;
/**
* URI used to represent a Vertex.
*/
protected final URI VERTEX;
/**
* URI used to represent a Edge.
*/
protected final URI EDGE;
/**
* URI used for labeling edges.
*/
protected final URI LABEL;
/**
* The list of criteria. Bigdata's query optimizer will re-order the
* criteria based on selectivity and execute for maximum performance and
* minimum IO.
*/
private final List<Has> criteria = new LinkedList<Has>();
/**
* Limit the number of results.
*/
private transient int limit = Integer.MAX_VALUE;
public BigdataGraphQuery(final BigdataGraph graph) {
this.graph = graph;
this.TYPE = graph.getValueFactory().getTypeURI();
this.VERTEX = graph.getValueFactory().getVertexURI();
this.EDGE = graph.getValueFactory().getEdgeURI();
this.LABEL = graph.getValueFactory().getLabelURI();
}
/**
* Filter out elements that do not have a property with provided key.
*
* ?s <key> ?value
*
* @param key the key of the property
* @return the modified query object
*/
@Override
public GraphQuery has(final String key) {
criteria.add(new Has(key));
return this;
}
/**
* Filter out elements that have a property with provided key.
*
* ?s ?p ?o .
* filter not exists { ?s <key> ?value } .
*
* @param key the key of the property
* @return the modified query object
*/
@Override
public GraphQuery hasNot(final String key) {
criteria.add(new HasNot(key));
return this;
}
/**
* Filter out elements that do not have a property value equal to provided value.
*
* ?s <key> <value> .
*
* @param key the key of the property
* @param value the value to check against
* @return the modified query object
*/
@Override
public GraphQuery has(final String key, final Object value) {
criteria.add(new Has(key, value));
return this;
}
/**
* Filter out elements that have a property value equal to provided value.
*
* ?s ?p ?o .
* filter not exists { ?s <key> <value> } .
*
* @param key the key of the property
* @param value the value to check against
* @return the modified query object
*/
@Override
public GraphQuery hasNot(final String key, final Object value) {
criteria.add(new HasNot(key, value));
return this;
}
/**
* Filter out the element if it does not have a property with a comparable value.
*
* @param key the key of the property
* @param predicate the comparator to use for comparison
* @param value the value to check against
* @return the modified query object
*/
@Override
public GraphQuery has(final String key, final Predicate predicate, final Object value) {
if (!knownPredicates.contains(predicate.getClass())) {
throw new IllegalArgumentException();
}
criteria.add(new Has(key, value, BigdataPredicate.toBigdataPredicate(predicate)));
return this;
}
/**
* Filter out the element if it does not have a property with a comparable value.
*
* @param key the key of the property
* @param value the value to check against
* @param compare the comparator to use for comparison
* @return the modified query object
*/
@Override
@Deprecated
public <T extends Comparable<T>> GraphQuery has(final String key, final T value,
final Compare compare) {
return has(key, compare, value);
}
/**
* Filter out the element of its property value is not within the provided interval.
*
* @param key the key of the property
* @param startValue the inclusive start value of the interval
* @param endValue the exclusive end value of the interval
* @return the modified query object
*/
@Override
public <T extends Comparable<?>> GraphQuery interval(final String key,
final T startValue, final T endValue) {
return has(key, BigdataPredicate.GTE, startValue)
.has(key, BigdataPredicate.LT, endValue);
}
/**
* Filter out the element if the take number of incident/adjacent elements to retrieve has already been reached.
*
* @param limit the take number of elements to return
* @return the modified query object
*/
@Override
public GraphQuery limit(final int limit) {
this.limit = limit;
return this;
}
/**
* Execute the query and return the matching edges.
*
* @return the unfiltered incident edges
*/
@Override
public Iterable<Edge> edges() {
try {
final String queryStr = toQueryStr(EDGE);
return graph.getEdges(queryStr);
} catch (RuntimeException ex) {
throw ex;
} catch (Exception ex) {
throw new RuntimeException(ex);
}
}
/**
* Execute the query and return the vertices on the other end of the matching edges.
*
* @return the unfiltered adjacent vertices
*/
@Override
public Iterable<Vertex> vertices() {
try {
final String queryStr = toQueryStr(VERTEX);
return graph.getVertices(queryStr, true);
} catch (RuntimeException ex) {
throw ex;
} catch (Exception ex) {
throw new RuntimeException(ex);
}
}
/**
* Generate the SPARQL query.
*/
protected String toQueryStr(final URI type) {
final StringBuilder sb = new StringBuilder();
if (type == VERTEX) {
sb.append("construct { ?x <"+TYPE+"> <"+type+"> . }\n");
sb.append("{\n select distinct ?x where {\n");
} else {
sb.append("construct { ?from ?x ?to . }\n");
sb.append("{\n select distinct ?from ?x ?to where {\n");
sb.append(" ?from ?x ?to .\n");
}
final BlueprintsValueFactory factory = graph.factory;
boolean hasHas = false;
int i = 1;
for (Has has : criteria) {
if (log.isTraceEnabled()) {
log.trace(has);
}
if (has instanceof HasNot) {
sb.append(" filter not exists { ");
sb.append("?x <").append(factory.toPropertyURI(has.key)).append("> ");
if (has.val != null) {
final String val = factory.toLiteral(has.val).toString();
sb.append(val).append(" .");
} else {
final String var = "?val"+i;
sb.append(var).append(" .");
}
sb.append("}\n");
} else {
hasHas = true;
sb.append(" ?x <").append(factory.toPropertyURI(has.key)).append("> ");
if (has.val != null &&
(has.pred == null || has.pred == BigdataPredicate.EQ)) {
final Literal val = factory.toLiteral(has.val);
sb.append(val).append(" .\n");
} else {
final String var = "?val"+i;
sb.append(var).append(" .\n");
if (has.pred != null) {
sb.append(toFilterStr(has.pred, var, has.val)).append("\n");
}
}
}
i++;
}
// need a statement pattern for the filter not exists
if (!hasHas) {
sb.append(" ?x <"+TYPE+"> <").append(type).append("> .\n");
}
// sb.setLength(sb.length()-1);
sb.append(" }");
if (limit < Integer.MAX_VALUE) {
sb.append(" limit " + factory.toLiteral(limit).getLabel());
}
sb.append("\n}");
if (log.isTraceEnabled()) {
log.trace("\n"+sb.toString());
}
return sb.toString();
}
/**
* Generate a SPARQL filter string for a particular Predicate.
*/
private String toFilterStr(final BigdataPredicate pred, final String var,
final Object val) {
final BlueprintsValueFactory factory = graph.factory;
final StringBuilder sb = new StringBuilder();
if (pred == BigdataPredicate.EQ) {
throw new IllegalArgumentException();
} else if (pred == BigdataPredicate.GT || pred == BigdataPredicate.GTE ||
pred == BigdataPredicate.LT || pred == BigdataPredicate.LTE ||
pred == BigdataPredicate.NE) {
final Literal l = factory.toLiteral(val);
sb.append(" filter(").append(var);
switch(pred) {
case GT:
sb.append(" > "); break;
case GTE:
sb.append(" >= "); break;
case LT:
sb.append(" < "); break;
case LTE:
sb.append(" <= "); break;
case NE:
sb.append(" != "); break;
default:
break;
}
sb.append(l).append(") .");
} else if (pred == BigdataPredicate.IN || pred == BigdataPredicate.NIN) {
sb.append(" filter(");
if (pred == BigdataPredicate.NIN) {
sb.append("!(");
}
sb.append(var).append(" in (");
final Collection<?> c = (Collection<?>) val;
for (Object o : c) {
final Literal l = factory.toLiteral(o);
sb.append(l).append(", ");
}
sb.setLength(sb.length()-2);
if (pred == BigdataPredicate.NIN) {
sb.append(")");
}
sb.append(")) .");
}
return sb.toString();
}
/**
* Standard criterion for filtering by the existence of a property and
* optional value.
*
* @author mikepersonick
*
*/
private class Has {
private String key;
private Object val;
private BigdataPredicate pred;
public Has(final String key) {
this(key, null, null);
}
public Has(final String key, final Object val) {
this(key, val, null);
}
public Has(final String key, final Object val,
final BigdataPredicate pred) {
if (pred == BigdataPredicate.IN || pred == BigdataPredicate.NIN) {
if (!(val instanceof Collection)) {
throw new IllegalArgumentException();
}
if (((Collection<?>) val).size() == 0) {
throw new IllegalArgumentException();
}
}
this.key = key;
if (pred == BigdataPredicate.IN && ((Collection<?>) val).size() == 1) {
/*
* Simple optimization to replace a single value IN with
* a simple EQ.
*/
this.val = ((Collection<?>) val).iterator().next();
this.pred = null;
} else {
this.val = val;
this.pred = pred;
}
}
public String toString() {
return "key: " + key + ", val: " + val + ", pred: " + pred;
}
}
/**
* Criterion for filtering by the non-existence of a property and
* optional value. Uses SPARQL filter not exists {}.
*
* @author mikepersonick
*
*/
private class HasNot extends Has {
public HasNot(final String key) {
super(key);
}
public HasNot(final String key, final Object val) {
super(key, val);
}
}
}