// 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.vertex;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import org.janusgraph.core.BaseVertexQuery;
import org.janusgraph.core.Cardinality;
import org.janusgraph.core.PropertyKey;
import org.janusgraph.core.RelationType;
import org.janusgraph.core.JanusGraphRelation;
import org.janusgraph.core.JanusGraphVertex;
import org.janusgraph.core.attribute.Cmp;
import org.janusgraph.core.schema.SchemaInspector;
import org.janusgraph.graphdb.internal.Order;
import org.janusgraph.graphdb.internal.OrderList;
import org.janusgraph.graphdb.internal.RelationCategory;
import org.janusgraph.graphdb.query.Query;
import org.janusgraph.graphdb.query.JanusGraphPredicate;
import org.janusgraph.graphdb.query.condition.PredicateCondition;
import org.janusgraph.graphdb.relations.RelationIdentifier;
import org.janusgraph.graphdb.tinkerpop.ElementUtils;
import org.janusgraph.graphdb.types.system.ImplicitKey;
import org.janusgraph.graphdb.types.system.SystemRelationType;
import org.apache.commons.lang.StringUtils;
import org.apache.tinkerpop.gremlin.structure.Direction;
import org.apache.tinkerpop.gremlin.structure.Vertex;
import java.util.ArrayList;
import java.util.List;
/**
* Builds a {@link org.janusgraph.core.BaseVertexQuery}, optimizes the query and compiles the result into a {@link BaseVertexCentricQuery} which
* is then executed by one of the extending classes.
*
* @author Matthias Broecheler (me@matthiasb.com)
*/
public abstract class BaseVertexCentricQueryBuilder<Q extends BaseVertexQuery<Q>> implements BaseVertexQuery<Q> {
private static final String[] NO_TYPES = new String[0];
private static final List<PredicateCondition<String, JanusGraphRelation>> NO_CONSTRAINTS = ImmutableList.of();
/**
* The direction of this query. BOTH by default
*/
protected Direction dir = Direction.BOTH;
/**
* The relation types (labels or keys) to query for. None by default which means query for any relation type.
*/
protected String[] types = NO_TYPES;
/**
* The constraints added to this query. None by default.
*/
protected List<PredicateCondition<String, JanusGraphRelation>> constraints = NO_CONSTRAINTS;
/**
* The vertex to be used for the adjacent vertex constraint. If null, that means no such constraint. Null by default.
*/
protected JanusGraphVertex adjacentVertex = null;
/**
* The order in which the relations should be returned. None by default.
*/
protected OrderList orders = new OrderList();
/**
* The limit of this query. No limit by default.
*/
protected int limit = Query.NO_LIMIT;
private final SchemaInspector schemaInspector;
protected BaseVertexCentricQueryBuilder(SchemaInspector schemaInspector) {
this.schemaInspector = schemaInspector;
}
protected abstract Q getThis();
protected abstract JanusGraphVertex getVertex(long vertexid);
/* ---------------------------------------------------------------
* Query Construction
* ---------------------------------------------------------------
*/
@Override
public Q adjacent(Vertex vertex) {
Preconditions.checkArgument(vertex != null && (vertex instanceof JanusGraphVertex), "Not a valid vertex provided for adjacency constraint");
this.adjacentVertex = (JanusGraphVertex) vertex;
return getThis();
}
private Q addConstraint(String type, JanusGraphPredicate rel, Object value) {
Preconditions.checkArgument(type != null && StringUtils.isNotBlank(type) && rel != null);
//Treat special cases
if (type.equals(ImplicitKey.ADJACENT_ID.name())) {
Preconditions.checkArgument(rel == Cmp.EQUAL, "Only equality constraints are supported for %s", type);
long vertexId = ElementUtils.getVertexId(value);
Preconditions.checkArgument(vertexId > 0, "Expected valid vertex id: %s", value);
return adjacent(getVertex(vertexId));
} else if (type.equals(ImplicitKey.ID.name())) {
RelationIdentifier rid = ElementUtils.getEdgeId(value);
Preconditions.checkArgument(rid != null, "Expected valid relation id: %s", value);
return addConstraint(ImplicitKey.JANUSGRAPHID.name(), rel, rid.getRelationId());
} else {
Preconditions.checkArgument(rel.isValidCondition(value), "Invalid condition provided: " + value);
}
if (constraints == NO_CONSTRAINTS) constraints = new ArrayList<PredicateCondition<String, JanusGraphRelation>>(5);
constraints.add(new PredicateCondition<String, JanusGraphRelation>(type, rel, value));
return getThis();
}
@Override
public Q has(String type, Object value) {
return addConstraint(type, Cmp.EQUAL, value);
}
@Override
public Q hasNot(String key, Object value) {
return has(key, Cmp.NOT_EQUAL, value);
}
@Override
public Q has(String key) {
return has(key, Cmp.NOT_EQUAL, (Object) null);
}
@Override
public Q hasNot(String key) {
return has(key, Cmp.EQUAL, (Object) null);
}
@Override
public Q has(String key, JanusGraphPredicate predicate, Object value) {
return addConstraint(key, predicate, value);
}
@Override
public <T extends Comparable<?>> Q interval(String key, T start, T end) {
addConstraint(key, Cmp.GREATER_THAN_EQUAL, start);
return addConstraint(key, Cmp.LESS_THAN, end);
}
@Override
public Q types(RelationType... types) {
String[] ts = new String[types.length];
for (int i = 0; i < types.length; i++) {
ts[i] = types[i].name();
}
return types(ts);
}
@Override
public Q labels(String... labels) {
return types(labels);
}
@Override
public Q keys(String... keys) {
return types(keys);
}
public Q type(RelationType type) {
return types(type.name());
}
@Override
public Q types(String... types) {
if (types == null) types = NO_TYPES;
for (String type : types) Preconditions.checkArgument(StringUtils.isNotBlank(type), "Invalid type: %s", type);
this.types = types;
return getThis();
}
@Override
public Q direction(Direction d) {
Preconditions.checkNotNull(d);
dir = d;
return getThis();
}
@Override
public Q limit(int limit) {
Preconditions.checkArgument(limit >= 0);
this.limit = limit;
return getThis();
}
@Override
public Q orderBy(String keyName, org.apache.tinkerpop.gremlin.process.traversal.Order order) {
Preconditions.checkArgument(schemaInspector.containsPropertyKey(keyName), "Provided key does not exist: %s", keyName);
PropertyKey key = schemaInspector.getPropertyKey(keyName);
Preconditions.checkArgument(key != null && order != null, "Need to specify and key and an order");
Preconditions.checkArgument(Comparable.class.isAssignableFrom(key.dataType()),
"Can only order on keys with comparable data type. [%s] has datatype [%s]", key.name(), key.dataType());
Preconditions.checkArgument(key.cardinality() == Cardinality.SINGLE, "Ordering is undefined on multi-valued key [%s]", key.name());
Preconditions.checkArgument(!(key instanceof SystemRelationType), "Cannot use system types in ordering: %s", key);
Preconditions.checkArgument(!orders.containsKey(key));
Preconditions.checkArgument(orders.isEmpty(), "Only a single sort order is supported on vertex queries");
orders.add(key, Order.convert(order));
return getThis();
}
/* ---------------------------------------------------------------
* Inspection Methods
* ---------------------------------------------------------------
*/
protected final boolean hasTypes() {
return types.length > 0;
}
protected final boolean hasSingleType() {
return types.length == 1 && schemaInspector.getRelationType(types[0]) != null;
}
protected final RelationType getSingleType() {
Preconditions.checkArgument(hasSingleType());
return schemaInspector.getRelationType(types[0]);
}
/**
* Whether this query is asking for the value of an {@link org.janusgraph.graphdb.types.system.ImplicitKey}.
* </p>
* Handling of implicit keys is completely distinct from "normal" query execution and handled extra
* for completeness reasons.
*
* @param returnType
* @return
*/
protected final boolean isImplicitKeyQuery(RelationCategory returnType) {
if (returnType == RelationCategory.EDGE || types.length != 1 || !constraints.isEmpty()) return false;
return schemaInspector.getRelationType(types[0]) instanceof ImplicitKey;
}
}