// Copyright 2017 JanusGraph Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package org.janusgraph.graphdb.query.graph; import com.google.common.base.Function; import com.google.common.base.Preconditions; import com.google.common.base.Predicate; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import org.janusgraph.core.*; import org.janusgraph.core.schema.Parameter; import org.janusgraph.diskstorage.indexing.RawQuery; import org.janusgraph.graphdb.database.IndexSerializer; import org.janusgraph.graphdb.internal.ElementCategory; import org.janusgraph.graphdb.query.BaseQuery; import org.janusgraph.graphdb.transaction.StandardJanusGraphTx; import org.apache.tinkerpop.gremlin.structure.Element; import org.apache.commons.lang.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.annotation.Nullable; import java.util.List; /** * Implementation of {@link JanusGraphIndexQuery} for string based queries that are issued directly against the specified * indexing backend. It is assumed that the given string conforms to the query language of the indexing backend. * This class does not understand or verify the provided query. However, it will introspect the query and replace * any reference to `v.SOME_KEY`, `e.SOME_KEY` or `p.SOME_KEY` with the respective key reference. This replacement * is 'dumb' in the sense that it relies on simple string replacements to accomplish this. If the key contains special characters * (in particular space) then it must be encapsulated in quotation marks. * </p> * In addition to the query string, a number of parameters can be specified which will be passed verbatim to the indexing * backend during query execution. * </p> * This class essentially just acts as a builder, uses the {@link IndexSerializer} to execute the query, and then post-processes * the result set to return to the user. * * @author Matthias Broecheler (me@matthiasb.com) */ public class IndexQueryBuilder extends BaseQuery implements JanusGraphIndexQuery { private static final Logger log = LoggerFactory.getLogger(IndexQueryBuilder.class); private static final String VERTEX_PREFIX = "v."; private static final String EDGE_PREFIX = "e."; private static final String PROPERTY_PREFIX = "p."; private final StandardJanusGraphTx tx; private final IndexSerializer serializer; /** * The name of the indexing backend this query is directed at */ private String indexName; /** * Query string conforming to the query language supported by the indexing backend. */ private String query; /** * Parameters passed to the indexing backend during query execution to modify the execution behavior. */ private final List<Parameter> parameters; /** * Prefix to be used to identify vertex, edge or property references and trigger key parsing and conversion. * In most cases this will be one of the above defined static prefixes, but in some special cases, the user may * define this. */ private String prefix; /** * Name to use for unknown keys, i.e. key references that could not be resolved to an actual type in the database. */ private final String unkownKeyName; /** * In addition to limit, this type of query supports offsets. */ private int offset; public IndexQueryBuilder(StandardJanusGraphTx tx, IndexSerializer serializer) { Preconditions.checkNotNull(tx); Preconditions.checkNotNull(serializer); this.tx = tx; this.serializer = serializer; parameters = Lists.newArrayList(); unkownKeyName = tx.getGraph().getConfiguration().getUnknownIndexKeyName(); this.offset=0; } //################################################ // Inspection Methods //################################################ public String getIndex() { return indexName; } public Parameter[] getParameters() { return parameters.toArray(new Parameter[parameters.size()]); } public String getQuery() { return query; } public int getOffset() { return offset; } public String getPrefix() { return prefix; } @Override public IndexQueryBuilder setElementIdentifier(String identifier) { Preconditions.checkArgument(StringUtils.isNotBlank(identifier),"Prefix may not be a blank string"); this.prefix=identifier; return this; } public String getUnknownKeyName() { return unkownKeyName; } //################################################ // Builder Methods //################################################ public IndexQueryBuilder setIndex(String indexName) { Preconditions.checkArgument(StringUtils.isNotBlank(indexName)); this.indexName=indexName; return this; } public IndexQueryBuilder setQuery(String query) { Preconditions.checkArgument(StringUtils.isNotBlank(query)); this.query=query; return this; } @Override public IndexQueryBuilder offset(int offset) { Preconditions.checkArgument(offset>=0,"Invalid offset provided: %s",offset); this.offset=offset; return this; } @Override public IndexQueryBuilder limit(int limit) { super.setLimit(limit); return this; } @Override public IndexQueryBuilder addParameter(Parameter para) { parameters.add(para); return this; } @Override public IndexQueryBuilder addParameters(Iterable<Parameter> paras) { Iterables.addAll(parameters, paras); return this; } @Override public IndexQueryBuilder addParameters(Parameter... paras) { for (Parameter para: paras) addParameter(para); return this; } private Iterable<Result<JanusGraphElement>> execute(ElementCategory resultType) { Preconditions.checkNotNull(indexName); Preconditions.checkNotNull(query); if (tx.hasModifications()) log.warn("Modifications in this transaction might not be accurately reflected in this index query: {}",query); Iterable<RawQuery.Result> result = serializer.executeQuery(this,resultType,tx.getTxHandle(),tx); final Function<Object, ? extends JanusGraphElement> conversionFct = tx.getConversionFunction(resultType); return Iterables.filter(Iterables.transform(result, new Function<RawQuery.Result, Result<JanusGraphElement>>() { @Nullable @Override public Result<JanusGraphElement> apply(@Nullable RawQuery.Result result) { return new ResultImpl<JanusGraphElement>(conversionFct.apply(result.getResult()),result.getScore()); } }),new Predicate<Result<JanusGraphElement>>() { @Override public boolean apply(@Nullable Result<JanusGraphElement> r) { return !r.getElement().isRemoved(); } }); } @Override public Iterable<Result<JanusGraphVertex>> vertices() { setPrefixInternal(VERTEX_PREFIX); return (Iterable)execute(ElementCategory.VERTEX); } @Override public Iterable<Result<JanusGraphEdge>> edges() { setPrefixInternal(EDGE_PREFIX); return (Iterable)execute(ElementCategory.EDGE); } @Override public Iterable<Result<JanusGraphVertexProperty>> properties() { setPrefixInternal(PROPERTY_PREFIX); return (Iterable)execute(ElementCategory.PROPERTY); } private void setPrefixInternal(String prefix) { Preconditions.checkArgument(StringUtils.isNotBlank(prefix)); if (this.prefix==null) this.prefix=prefix; } private static class ResultImpl<V extends Element> implements Result<V> { private final V element; private final double score; private ResultImpl(V element, double score) { this.element = element; this.score = score; } @Override public V getElement() { return element; } @Override public double getScore() { return score; } } }