/*
*
* * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com)
* *
* * 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.
* *
* * For more information: http://www.orientechnologies.com
*
*/
package com.tinkerpop.blueprints.impls.orient;
import com.orientechnologies.orient.core.db.record.OIdentifiable;
import com.orientechnologies.orient.core.metadata.schema.OClass;
import com.orientechnologies.orient.core.sql.query.OSQLSynchQuery;
import com.orientechnologies.orient.core.tx.OTransaction;
import com.tinkerpop.blueprints.*;
import com.tinkerpop.blueprints.util.DefaultGraphQuery;
import java.util.*;
/**
* OrientDB implementation for Graph query.
*
* @author Luca Garulli (http://www.orientechnologies.com)
*/
public class OrientGraphQuery extends DefaultGraphQuery {
protected static final char SPACE = ' ';
protected static final String OPERATOR_DIFFERENT = "<>";
protected static final String OPERATOR_NOT = "not ";
protected static final String OPERATOR_IS_NOT = "is not";
protected static final String OPERATOR_LET = "<=";
protected static final char OPERATOR_LT = '<';
protected static final String OPERATOR_GTE = ">=";
protected static final char OPERATOR_GT = '>';
protected static final String OPERATOR_EQUALS = "=";
protected static final String OPERATOR_IS = "is";
protected static final String OPERATOR_IN = " in ";
protected static final String OPERATOR_LIKE = " like ";
protected static final String QUERY_FILTER_AND = " and ";
protected static final String QUERY_FILTER_OR = " or ";
protected static final char QUERY_STRING = '\'';
protected static final char QUERY_SEPARATOR = ',';
protected static final char COLLECTION_BEGIN = '[';
protected static final char COLLECTION_END = ']';
protected static final char PARENTHESIS_BEGIN = '(';
protected static final char PARENTHESIS_END = ')';
protected static final String QUERY_LABEL_BEGIN = " label in [";
protected static final String QUERY_LABEL_END = "]";
protected static final String QUERY_WHERE = " where ";
protected static final String QUERY_SELECT_FROM = "select from ";
protected static final String SKIP = " SKIP ";
protected static final String LIMIT = " LIMIT ";
protected static final String ORDERBY = " ORDER BY ";
public int skip = 0;
public String orderBy = "";
public String orderByDir = "desc";
protected String fetchPlan;
public class OrientGraphQueryIterable<T extends Element> extends DefaultGraphQueryIterable<T> {
public OrientGraphQueryIterable(final boolean forVertex, final String[] labels) {
super(forVertex);
if (labels != null && labels.length > 0)
// TREAT CLASS AS LABEL
has("_class", Contains.IN, Arrays.asList(labels));
}
protected Set<String> getIndexedKeys(final Class<? extends Element> elementClass) {
return ((OrientBaseGraph) graph).getIndexedKeys(elementClass, true);
}
}
protected OrientGraphQuery(final Graph iGraph) {
super(iGraph);
}
/**
* (Blueprints Extension) Sets the labels to filter. Labels are bound to Class names by default.
*
* @param labels
* String vararg of labels
* @return Current Query Object to allow calls in chain.
*/
public Query labels(final String... labels) {
this.labels = labels;
return this;
}
/**
* Skips first iSkip items from the result set.
*
* @param iSkip
* Number of items to skip on result set
* @return Current Query Object to allow calls in chain.
*/
public Query skip(final int iSkip) {
this.skip = iSkip;
return this;
}
/**
* (Blueprints Extension) Sets the order of results by a field in ascending (asc) order. This is translated on ORDER BY in the
* underlying SQL query.
*
* @param props
* Field to order by
* @return Current Query Object to allow calls in chain.
*/
public Query order(final String props) {
this.order(props, orderByDir);
return this;
}
/**
* (Blueprints Extension) Sets the order of results by a field in ascending (asc) or descending (desc) order based on dir
* parameter. This is translated on ORDER BY in the underlying SQL query.
*
* @param props
* Field to order by
* @param dir
* Direction. Use "asc" for ascending and "desc" for descending
* @return Current Query Object to allow calls in chain.
*/
public Query order(final String props, final String dir) {
this.orderBy = props;
this.orderByDir = dir;
return this;
}
/**
* Returns the result set of the query as iterable vertices.
*/
@Override
public Iterable<Vertex> vertices() {
if (limit == 0)
return Collections.emptyList();
OTransaction transaction = ((OrientBaseGraph) graph).getRawGraph().getTransaction();
if (transaction.isActive() && transaction.getEntryCount() > 0 || hasCustomPredicate()) {
// INSIDE TRANSACTION QUERY DOESN'T SEE IN MEMORY CHANGES, UNTIL
// SUPPORTED USED THE BASIC IMPL
String[] classes = allSubClassesLabels();
return new OrientGraphQueryIterable<Vertex>(true, classes);
}
final StringBuilder text = new StringBuilder(512);
// GO DIRECTLY AGAINST E CLASS AND SUB-CLASSES
text.append(QUERY_SELECT_FROM);
if (((OrientBaseGraph) graph).isUseClassForVertexLabel() && labels != null && labels.length > 0) {
// FILTER PER CLASS SAVING CHECKING OF LABEL PROPERTY
if (labels.length == 1)
// USE THE CLASS NAME
text.append(OrientBaseGraph.encodeClassName(labels[0]));
else {
// MULTIPLE CLASSES NOT SUPPORTED DIRECTLY: CREATE A SUB-QUERY
String[] classes = allSubClassesLabels();
return new OrientGraphQueryIterable<Vertex>(true, classes);
}
} else
text.append(OrientVertexType.CLASS_NAME);
final boolean usedWhere = manageFilters(text);
if (!((OrientBaseGraph) graph).isUseClassForVertexLabel())
manageLabels(usedWhere, text);
if (orderBy.length() > 1) {
text.append(ORDERBY);
text.append(orderBy);
text.append(" ").append(orderByDir).append(" ");
}
if (skip > 0 && skip < Integer.MAX_VALUE) {
text.append(SKIP);
text.append(skip);
}
if (limit > 0 && limit < Integer.MAX_VALUE) {
text.append(LIMIT);
text.append(limit);
}
final OSQLSynchQuery<OIdentifiable> query = new OSQLSynchQuery<OIdentifiable>(text.toString());
if (fetchPlan != null)
query.setFetchPlan(fetchPlan);
return new OrientElementIterable<Vertex>(((OrientBaseGraph) graph), ((OrientBaseGraph) graph).getRawGraph().query(query));
}
private String[] allSubClassesLabels() {
String[] classes = null;
if (labels != null && labels.length > 0) {
List<String> tmpClasses = new ArrayList<String>();
for (String label : labels) {
OrientVertexType vertexType = ((OrientBaseGraph) graph).getVertexType(label);
tmpClasses.add(vertexType.getName());
Collection<OClass> allSubclasses = vertexType.getAllSubclasses();
for (OClass klass : allSubclasses) {
tmpClasses.add(klass.getName());
}
}
classes = tmpClasses.toArray(new String[tmpClasses.size()]);
}
return classes;
}
/**
*
* Returns the result set of the query as iterable edges.
*/
@Override
public Iterable<Edge> edges() {
if (limit == 0)
return Collections.emptyList();
if (((OrientBaseGraph) graph).getRawGraph().getTransaction().isActive() || hasCustomPredicate())
// INSIDE TRANSACTION QUERY DOESN'T SEE IN MEMORY CHANGES, UNTIL
// SUPPORTED USED THE BASIC IMPL
return new OrientGraphQueryIterable<Edge>(false, labels);
if (((OrientBaseGraph) graph).isUseLightweightEdges())
return new OrientGraphQueryIterable<Edge>(false, labels);
final StringBuilder text = new StringBuilder(512);
// GO DIRECTLY AGAINST E CLASS AND SUB-CLASSES
text.append(QUERY_SELECT_FROM);
if (((OrientBaseGraph) graph).isUseClassForEdgeLabel() && labels != null && labels.length > 0) {
// FILTER PER CLASS SAVING CHECKING OF LABEL PROPERTY
if (labels.length == 1)
// USE THE CLASS NAME
text.append(OrientBaseGraph.encodeClassName(labels[0]));
else {
// MULTIPLE CLASSES NOT SUPPORTED DIRECTLY: CREATE A SUB-QUERY
return new OrientGraphQueryIterable<Edge>(false, labels);
}
} else
text.append(OrientEdgeType.CLASS_NAME);
final boolean usedWhere = manageFilters(text);
if (!((OrientBaseGraph) graph).isUseClassForEdgeLabel())
manageLabels(usedWhere, text);
final OSQLSynchQuery<OIdentifiable> query = new OSQLSynchQuery<OIdentifiable>(text.toString());
if (fetchPlan != null)
query.setFetchPlan(fetchPlan);
if (limit > 0 && limit < Integer.MAX_VALUE)
query.setLimit(limit);
return new OrientElementIterable<Edge>(((OrientBaseGraph) graph), ((OrientBaseGraph) graph).getRawGraph().query(query));
}
/**
* (Blueprints Extension) Returns the fetch plan used.
*/
public String getFetchPlan() {
return fetchPlan;
}
/**
* (Blueprints Extension) Sets the fetch plan to use on returning result set.
*/
public void setFetchPlan(final String fetchPlan) {
this.fetchPlan = fetchPlan;
}
protected void manageLabels(final boolean usedWhere, final StringBuilder text) {
if (labels != null && labels.length > 0) {
if (!usedWhere) {
// APPEND WHERE
text.append(QUERY_WHERE);
} else
text.append(QUERY_FILTER_AND);
text.append(QUERY_LABEL_BEGIN);
for (int i = 0; i < labels.length; ++i) {
if (i > 0)
text.append(QUERY_SEPARATOR);
text.append(QUERY_STRING);
text.append(labels[i]);
text.append(QUERY_STRING);
}
text.append(QUERY_LABEL_END);
}
}
protected boolean hasCustomPredicate() {
for (HasContainer has : hasContainers) {
if (!(has.predicate instanceof Contains) && !(has.predicate instanceof com.tinkerpop.blueprints.Compare))
return true;
}
return false;
}
@SuppressWarnings("unchecked")
protected boolean manageFilters(final StringBuilder text) {
boolean firstPredicate = true;
for (HasContainer has : hasContainers) {
if (!firstPredicate)
text.append(QUERY_FILTER_AND);
else {
text.append(QUERY_WHERE);
firstPredicate = false;
}
if (has.predicate instanceof Contains) {
// IN AND NOT_IN
if (has.predicate == Contains.NOT_IN) {
text.append(OPERATOR_NOT);
text.append(PARENTHESIS_BEGIN);
}
text.append('`').append(has.key).append('`');
if (has.value instanceof String) {
text.append(OPERATOR_LIKE);
generateFilterValue(text, has.value);
} else {
text.append(OPERATOR_IN);
text.append(COLLECTION_BEGIN);
boolean firstItem = true;
for (Object o : (Collection<Object>) has.value) {
if (!firstItem)
text.append(QUERY_SEPARATOR);
else
firstItem = false;
generateFilterValue(text, o);
}
text.append(COLLECTION_END);
}
if (has.predicate == Contains.NOT_IN)
text.append(PARENTHESIS_END);
} else {
// ANY OTHER OPERATORS
text.append('`').append(has.key).append('`');
text.append(SPACE);
if (has.predicate instanceof com.tinkerpop.blueprints.Compare) {
final com.tinkerpop.blueprints.Compare compare = (com.tinkerpop.blueprints.Compare) has.predicate;
switch (compare) {
case EQUAL:
if (has.value == null)
// IS
text.append(OPERATOR_IS);
else
// EQUALS
text.append(OPERATOR_EQUALS);
break;
case GREATER_THAN:
text.append(OPERATOR_GT);
break;
case GREATER_THAN_EQUAL:
text.append(OPERATOR_GTE);
break;
case LESS_THAN:
text.append(OPERATOR_LT);
break;
case LESS_THAN_EQUAL:
text.append(OPERATOR_LET);
break;
case NOT_EQUAL:
if (has.value == null)
text.append(OPERATOR_IS_NOT);
else
text.append(OPERATOR_DIFFERENT);
break;
}
text.append(SPACE);
generateFilterValue(text, has.value);
}
if (has.value instanceof Collection<?>)
text.append(PARENTHESIS_END);
}
}
return !firstPredicate;
}
protected void generateFilterValue(final StringBuilder text, final Object iValue) {
if (iValue instanceof String)
text.append(QUERY_STRING);
final Object value;
if (iValue instanceof Date)
value = ((Date) iValue).getTime();
else if (iValue != null)
value = iValue.toString().replace("'", "\\'");
else
value = null;
text.append(value);
if (iValue instanceof String)
text.append(QUERY_STRING);
}
}