Skip to content

Commit

Permalink
Optimize in() traversal with indexes in pattern matching
Browse files Browse the repository at this point in the history
  • Loading branch information
luigidellaquila committed Oct 13, 2015
1 parent 9bfea13 commit d52a803
Show file tree
Hide file tree
Showing 5 changed files with 137 additions and 33 deletions.
Expand Up @@ -103,8 +103,16 @@ protected Iterable<OIdentifiable> executeTraversal(OMatchStatement.MatchContext


protected Iterable<OIdentifiable> traversePatternEdge(OMatchStatement.MatchContext matchContext, OIdentifiable startingPoint, protected Iterable<OIdentifiable> traversePatternEdge(OMatchStatement.MatchContext matchContext, OIdentifiable startingPoint,
OCommandContext iCommandContext) { OCommandContext iCommandContext) {
Iterable possibleResults = matchContext.candidates == null || filter == null ? null : matchContext.candidates.get(filter
.getAlias()); Iterable possibleResults = null;
if(filter!=null) {
OIdentifiable matchedNode = matchContext.matched.get(filter.getAlias());
if (matchedNode != null) {
possibleResults = Collections.singleton(matchedNode);
} else {
possibleResults = matchContext.candidates == null ? null : matchContext.candidates.get(filter.getAlias());
}
}


Object qR = this.method.execute(startingPoint, possibleResults, iCommandContext); Object qR = this.method.execute(startingPoint, possibleResults, iCommandContext);
return (qR instanceof Iterable) ? (Iterable) qR : Collections.singleton(qR); return (qR instanceof Iterable) ? (Iterable) qR : Collections.singleton(qR);
Expand Down
Expand Up @@ -105,6 +105,19 @@ public Object jjtAccept(OrientSqlVisitor visitor, Object data) {
return visitor.visit(this, data); return visitor.visit(this, data);
} }


// ------------------------------------------------------------------
// query parsing and optimization
// ------------------------------------------------------------------

/**
* this method parses the statement
*
* @param iRequest
* Command request implementation.
*
* @param <RET>
* @return
*/
@Override @Override
public <RET extends OCommandExecutor> RET parse(OCommandRequest iRequest) { public <RET extends OCommandExecutor> RET parse(OCommandRequest iRequest) {
final OCommandRequestText textRequest = (OCommandRequestText) iRequest; final OCommandRequestText textRequest = (OCommandRequestText) iRequest;
Expand Down Expand Up @@ -138,7 +151,6 @@ public <RET extends OCommandExecutor> RET parse(OCommandRequest iRequest) {
for (OMatchExpression expr : this.matchExpressions) { for (OMatchExpression expr : this.matchExpressions) {
pattern.addExpression(expr); pattern.addExpression(expr);
} }
validateReturn();


Map<String, OWhereClause> aliasFilters = new LinkedHashMap<String, OWhereClause>(); Map<String, OWhereClause> aliasFilters = new LinkedHashMap<String, OWhereClause>();
Map<String, String> aliasClasses = new LinkedHashMap<String, String>(); Map<String, String> aliasClasses = new LinkedHashMap<String, String>();
Expand All @@ -154,6 +166,11 @@ public <RET extends OCommandExecutor> RET parse(OCommandRequest iRequest) {
return (RET) this; return (RET) this;
} }


/**
* rebinds filter (where) conditions to alias nodes after optimization
*
* @param aliasFilters
*/
private void rebindFilters(Map<String, OWhereClause> aliasFilters) { private void rebindFilters(Map<String, OWhereClause> aliasFilters) {
for (OMatchExpression expression : matchExpressions) { for (OMatchExpression expression : matchExpressions) {
OWhereClause newFilter = aliasFilters.get(expression.origin.getAlias()); OWhereClause newFilter = aliasFilters.get(expression.origin.getAlias());
Expand All @@ -166,25 +183,12 @@ private void rebindFilters(Map<String, OWhereClause> aliasFilters) {
} }
} }


private void validateReturn() { /**
// Set<String> aliases = pattern.aliasToNode.keySet(); * assigns default aliases to pattern nodes that do not have an explicit alias
// if (returnItems.size() == 1) { *
// String returnAlias = returnItems.iterator().next().toString(); * @param matchExpressions
// if (!returnAlias.equalsIgnoreCase("$matches") && !returnAlias.equalsIgnoreCase("$paths") && !aliases.contains(returnAlias)) { */
// throw new OCommandSQLParsingException("Invalid return alias: " + returnAlias);
// }
// return;
// }
// for (OExpression s : this.returnItems) {
// String returnAlias = s.toString();
// if (!aliases.contains(returnAlias)) {
// throw new OCommandSQLParsingException("Invalid return alias: " + returnAlias);
// }
// }
}

private void assignDefaultAliases(List<OMatchExpression> matchExpressions) { private void assignDefaultAliases(List<OMatchExpression> matchExpressions) {

int counter = 0; int counter = 0;
for (OMatchExpression expression : matchExpressions) { for (OMatchExpression expression : matchExpressions) {
if (expression.origin.getAlias() == null) { if (expression.origin.getAlias() == null) {
Expand All @@ -202,12 +206,33 @@ private void assignDefaultAliases(List<OMatchExpression> matchExpressions) {
} }
} }


// ------------------------------------------------------------------
// query execution
// ------------------------------------------------------------------

/**
* this method works statefully, using request and context variables from current Match statement.
* This method will be deprecated in next releases
*
* @param iArgs
* Optional variable arguments to pass to the command.
*
* @return
*/
@Override @Override
public Object execute(Map<Object, Object> iArgs) { public Object execute(Map<Object, Object> iArgs) {
this.context.setInputParameters(iArgs); this.context.setInputParameters(iArgs);
return execute(this.request, this.context); return execute(this.request, this.context);
} }


/**
* executes the match statement. This is the preferred execute() method and it has to be used as the default one in the future.
* This method works in stateless mode
*
* @param request
* @param context
* @return
*/
public Object execute(OSQLAsynchQuery<ODocument> request, OCommandContext context) { public Object execute(OSQLAsynchQuery<ODocument> request, OCommandContext context) {
Map<Object, Object> iArgs = context.getInputParameters(); Map<Object, Object> iArgs = context.getInputParameters();
try { try {
Expand All @@ -221,7 +246,8 @@ public Object execute(OSQLAsynchQuery<ODocument> request, OCommandContext contex
MatchExecutionPlan executionPlan = new MatchExecutionPlan(); MatchExecutionPlan executionPlan = new MatchExecutionPlan();
executionPlan.sortedEdges = sortedEdges; executionPlan.sortedEdges = sortedEdges;


calculateMatch(estimatedRootEntries, new MatchContext(), aliasClasses, aliasFilters, context, request, executionPlan); calculateMatch(pattern, estimatedRootEntries, new MatchContext(), aliasClasses, aliasFilters, context, request, executionPlan);

return getResult(request); return getResult(request);
} finally { } finally {
if (request.getResultListener() != null) { if (request.getResultListener() != null) {
Expand All @@ -231,7 +257,7 @@ public Object execute(OSQLAsynchQuery<ODocument> request, OCommandContext contex


} }


/* /**
* sort edges in the order they will be matched * sort edges in the order they will be matched
*/ */
private List<EdgeTraversal> sortEdges(Map<String, Long> estimatedRootEntries, Pattern pattern) { private List<EdgeTraversal> sortEdges(Map<String, Long> estimatedRootEntries, Pattern pattern) {
Expand Down Expand Up @@ -291,14 +317,6 @@ protected Object getResult(OSQLAsynchQuery<ODocument> request) {
return null; return null;
} }


private boolean calculateMatch(Map<String, Long> estimatedRootEntries, MatchContext matchContext,
Map<String, String> aliasClasses, Map<String, OWhereClause> aliasFilters, OCommandContext iCommandContext,
OSQLAsynchQuery<ODocument> request, MatchExecutionPlan executionPlan) {
return calculateMatch(pattern, estimatedRootEntries, matchContext, aliasClasses, aliasFilters, iCommandContext, request,
executionPlan);

}

private boolean calculateMatch(Pattern pattern, Map<String, Long> estimatedRootEntries, MatchContext matchContext, private boolean calculateMatch(Pattern pattern, Map<String, Long> estimatedRootEntries, MatchContext matchContext,
Map<String, String> aliasClasses, Map<String, OWhereClause> aliasFilters, OCommandContext iCommandContext, Map<String, String> aliasClasses, Map<String, OWhereClause> aliasFilters, OCommandContext iCommandContext,
OSQLAsynchQuery<ODocument> request, MatchExecutionPlan executionPlan) { OSQLAsynchQuery<ODocument> request, MatchExecutionPlan executionPlan) {
Expand Down Expand Up @@ -331,8 +349,10 @@ private boolean calculateMatch(Pattern pattern, Map<String, Long> estimatedRootE
executionPlan.preFetchedAliases.put(nextAlias, estimatedRootEntries.get(nextAlias)); executionPlan.preFetchedAliases.put(nextAlias, estimatedRootEntries.get(nextAlias));
} }


//pick first edge (as sorted before)
EdgeTraversal firstEdge = executionPlan.sortedEdges.size() == 0 ? null : executionPlan.sortedEdges.get(0); EdgeTraversal firstEdge = executionPlan.sortedEdges.size() == 0 ? null : executionPlan.sortedEdges.get(0);
String smallestAlias = null; String smallestAlias = null;
//and choose the most convenient starting point (the most convenient traversal direction)
if (firstEdge != null) { if (firstEdge != null) {
smallestAlias = firstEdge.out ? firstEdge.edge.out.alias : firstEdge.edge.in.alias; smallestAlias = firstEdge.out ? firstEdge.edge.out.alias : firstEdge.edge.in.alias;
} else { } else {
Expand Down
Expand Up @@ -19,17 +19,29 @@
*/ */
package com.orientechnologies.orient.graph.sql.functions; package com.orientechnologies.orient.graph.sql.functions;


import com.orientechnologies.common.collection.OMultiCollectionIterator;
import com.orientechnologies.common.util.OSizeable;
import com.orientechnologies.orient.core.db.record.OIdentifiable; import com.orientechnologies.orient.core.db.record.OIdentifiable;
import com.orientechnologies.orient.core.index.OCompositeKey;
import com.orientechnologies.orient.core.index.OIndex;
import com.orientechnologies.orient.core.metadata.schema.OClass;
import com.orientechnologies.orient.core.record.impl.ODocument;
import com.tinkerpop.blueprints.Direction; import com.tinkerpop.blueprints.Direction;
import com.tinkerpop.blueprints.impls.orient.OrientBaseGraph; import com.tinkerpop.blueprints.impls.orient.OrientBaseGraph;
import com.tinkerpop.blueprints.impls.orient.OrientEdge;
import com.tinkerpop.blueprints.impls.orient.OrientVertex;

import java.util.Collections;
import java.util.HashSet;
import java.util.Set;


/** /**
* Gets the incoming Vertices of current Vertex. * Gets the incoming Vertices of current Vertex.
* *
* @author Luca Garulli (l.garulli--at--orientechnologies.com) * @author Luca Garulli (l.garulli--at--orientechnologies.com)
* *
*/ */
public class OSQLFunctionIn extends OSQLFunctionMove { public class OSQLFunctionIn extends OSQLFunctionMoveFiltered {
public static final String NAME = "in"; public static final String NAME = "in";


public OSQLFunctionIn() { public OSQLFunctionIn() {
Expand All @@ -40,4 +52,66 @@ public OSQLFunctionIn() {
protected Object move(final OrientBaseGraph graph, final OIdentifiable iRecord, final String[] iLabels) { protected Object move(final OrientBaseGraph graph, final OIdentifiable iRecord, final String[] iLabels) {
return v2v(graph, iRecord, Direction.IN, iLabels); return v2v(graph, iRecord, Direction.IN, iLabels);
} }

protected Object move(final OrientBaseGraph graph, final OIdentifiable iRecord, final String[] iLabels,
Iterable<OIdentifiable> iPossibleResults) {
if (iPossibleResults == null) {
return v2v(graph, iRecord, Direction.IN, iLabels);
}

if (!iPossibleResults.iterator().hasNext()) {
return Collections.emptyList();
}

Object edges = v2e(graph, iRecord, Direction.IN, iLabels);
if (edges instanceof OSizeable) {
int size = ((OSizeable) edges).size();
if (size > supernodeThreshold) {
Object result = fetchFromIndex(graph, iRecord, iPossibleResults, iLabels);
if (result != null) {
return result;
}
}

}

return v2v(graph, iRecord, Direction.IN, iLabels);
}

private Object fetchFromIndex(OrientBaseGraph graph, OIdentifiable iFrom, Iterable<OIdentifiable> iTo, String[] iEdgeTypes) {
String edgeClassName = null;
if (iEdgeTypes == null) {
edgeClassName = "E";
} else if (iEdgeTypes.length == 1) {
edgeClassName = iEdgeTypes[0];
} else {
return null;
}
OClass edgeClass = graph.getRawGraph().getMetadata().getSchema().getClass(edgeClassName);
if (edgeClass == null) {
return null;
}
Set<OIndex<?>> indexes = edgeClass.getInvolvedIndexes("in", "out");
if (indexes == null || indexes.size() == 0) {
return null;
}
OIndex index = indexes.iterator().next();

OMultiCollectionIterator<OrientVertex> result = new OMultiCollectionIterator<OrientVertex>();
for (OIdentifiable to : iTo) {
OCompositeKey key = new OCompositeKey(iFrom, to);
Object indexResult = index.get(key);
if (indexResult instanceof OIdentifiable) {
indexResult = Collections.singleton(indexResult);
}
Set<OIdentifiable> identities = new HashSet<OIdentifiable>();
for (OIdentifiable edge : ((Iterable<OrientEdge>) indexResult)) {
identities.add((OIdentifiable) ((ODocument) edge.getRecord()).rawField("in"));
}
result.add(identities);
}

return result;
}

} }
Expand Up @@ -37,6 +37,8 @@
*/ */
public abstract class OSQLFunctionMoveFiltered extends OSQLFunctionMove implements OSQLFunctionFiltered{ public abstract class OSQLFunctionMoveFiltered extends OSQLFunctionMove implements OSQLFunctionFiltered{


protected static int supernodeThreshold = 1000; //move to some configuration

public OSQLFunctionMoveFiltered() { public OSQLFunctionMoveFiltered() {
super(NAME, 1, 2); super(NAME, 1, 2);
} }
Expand Down
Expand Up @@ -43,7 +43,7 @@
*/ */
public class OSQLFunctionOut extends OSQLFunctionMoveFiltered { public class OSQLFunctionOut extends OSQLFunctionMoveFiltered {
public static final String NAME = "out"; public static final String NAME = "out";
int supernodeThreshold = 1000;


public OSQLFunctionOut() { public OSQLFunctionOut() {
super(NAME, 0, -1); super(NAME, 0, -1);
Expand Down

0 comments on commit d52a803

Please sign in to comment.