Add source_in_ring args for rings API (#528)

fixed: #523
Change-Id: If8c5cc4d8678f847ce4a5f0388e56aae465d3ea6
This commit is contained in:
zhoney 2019-07-01 22:01:24 +08:00 committed by Jermy Li
parent f7a619f971
commit 8d23665a40
6 changed files with 113 additions and 30 deletions

View File

@ -86,7 +86,7 @@
</addDefaultSpecificationEntries>
</manifest>
<manifestEntries>
<Implementation-Version>0.39.0.0</Implementation-Version>
<Implementation-Version>0.40.0.0</Implementation-Version>
</manifestEntries>
</archive>
</configuration>

View File

@ -72,8 +72,9 @@ public class RaysAPI extends API {
@DefaultValue(DEFAULT_PATHS_LIMIT) long limit) {
LOG.debug("Graph [{}] get rays paths from '{}' with " +
"direction '{}', edge label '{}', max depth '{}', " +
"max degree '{}' and limit '{}'",
graph, sourceV, direction, edgeLabel, depth, degree, limit);
"max degree '{}', capacity '{}' and limit '{}'",
graph, sourceV, direction, edgeLabel, depth, degree,
capacity, limit);
Id source = VertexAPI.checkAndParseVertexId(sourceV);
Directions dir = Directions.convert(EdgeAPI.parseDirection(direction));

View File

@ -64,6 +64,8 @@ public class RingsAPI extends API {
@QueryParam("direction") String direction,
@QueryParam("label") String edgeLabel,
@QueryParam("max_depth") int depth,
@QueryParam("source_in_ring")
@DefaultValue("true") boolean sourceInRing,
@QueryParam("max_degree")
@DefaultValue(DEFAULT_DEGREE) long degree,
@QueryParam("capacity")
@ -72,8 +74,10 @@ public class RingsAPI extends API {
@DefaultValue(DEFAULT_PATHS_LIMIT) long limit) {
LOG.debug("Graph [{}] get rings paths reachable from '{}' with " +
"direction '{}', edge label '{}', max depth '{}', " +
"max degree '{}' and limit '{}'",
graph, sourceV, direction, edgeLabel, depth, degree, limit);
"source in ring '{}', max degree '{}', capacity '{}' " +
"and limit '{}'",
graph, sourceV, direction, edgeLabel, depth, sourceInRing,
degree, capacity, limit);
Id source = VertexAPI.checkAndParseVertexId(sourceV);
Directions dir = Directions.convert(EdgeAPI.parseDirection(direction));
@ -82,8 +86,9 @@ public class RingsAPI extends API {
SubGraphTraverser traverser = new SubGraphTraverser(g);
List<HugeTraverser.Path> paths = traverser.rings(source, dir, edgeLabel,
depth, degree,
capacity, limit);
depth, sourceInRing,
degree, capacity,
limit);
return manager.serializer(g).writePaths("rings", paths, false);
}
}

View File

@ -87,10 +87,11 @@ public final class ApiVersion {
*
* version 0.10:
* [0.39] Issue-522: Add profile RESTful API
* [0.40] Issue-523: Add source_in_ring args for rings RESTful API
*/
// The second parameter of Version.of() is for IDE running without JAR
public static final Version VERSION = Version.of(ApiVersion.class, "0.39");
public static final Version VERSION = Version.of(ApiVersion.class, "0.40");
public static final void check() {
// Check version of hugegraph-core. Firstly do check from version 0.3

View File

@ -27,6 +27,7 @@ import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import javax.ws.rs.core.MultivaluedHashMap;
@ -379,7 +380,8 @@ public class HugeTraverser {
return false;
}
Node other = (Node) object;
return this.id.equals(other.id);
return Objects.equals(this.id, other.id) &&
Objects.equals(this.parent, other.parent);
}
}

View File

@ -20,6 +20,7 @@
package com.baidu.hugegraph.traversal.algorithm;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
@ -28,6 +29,7 @@ import java.util.Set;
import javax.ws.rs.core.MultivaluedMap;
import org.apache.tinkerpop.gremlin.structure.Edge;
import org.apache.tinkerpop.gremlin.util.iterator.IteratorUtils;
import com.baidu.hugegraph.HugeGraph;
import com.baidu.hugegraph.backend.id.Id;
@ -44,18 +46,20 @@ public class SubGraphTraverser extends HugeTraverser {
public List<Path> rays(Id sourceV, Directions dir, String label,
int depth, long degree, long capacity, long limit) {
return this.subGraphPaths(sourceV, dir, label, depth, degree,
capacity, limit, false);
capacity, limit, false, false);
}
public List<Path> rings(Id sourceV, Directions dir, String label,
int depth, long degree, long capacity, long limit) {
public List<Path> rings(Id sourceV, Directions dir, String label, int depth,
boolean sourceInRing, long degree, long capacity,
long limit) {
return this.subGraphPaths(sourceV, dir, label, depth, degree,
capacity, limit, true);
capacity, limit, true, sourceInRing);
}
private List<Path> subGraphPaths(Id sourceV, Directions dir, String label,
int depth, long degree, long capacity,
long limit, boolean rings) {
long limit, boolean rings,
boolean sourceInRing) {
E.checkNotNull(sourceV, "source vertex id");
E.checkNotNull(dir, "direction");
checkPositive(depth, "max depth");
@ -64,13 +68,13 @@ public class SubGraphTraverser extends HugeTraverser {
checkLimit(limit);
Id labelId = this.getEdgeLabelId(label);
Traverser traverser = new Traverser(sourceV, labelId, degree,
capacity, limit, rings);
Traverser traverser = new Traverser(sourceV, labelId, depth, degree,
capacity, limit, rings,
sourceInRing);
List<Path> paths = new ArrayList<>();
while (true) {
paths.addAll(traverser.forward(dir));
boolean reachDepth = rings ? --depth <= 0 : depth-- <= 0;
if (reachDepth || traverser.reachLimit() ||
if (--depth <= 0 || traverser.reachLimit() ||
traverser.finished()) {
break;
}
@ -78,27 +82,47 @@ public class SubGraphTraverser extends HugeTraverser {
return paths;
}
private static boolean hasMultiEdges(List<Edge> edges, Id target) {
int count = 0;
for (Edge edge : edges) {
if (((HugeEdge) edge).id().otherVertexId().equals(target)) {
if (++count > 1) {
return true;
}
}
}
assert count == 1;
return false;
}
private class Traverser {
private final Id source;
private MultivaluedMap<Id, Node> sources = newMultivalueMap();
private Set<Id> accessedVertices = newSet();
private final Id label;
private int depth;
private final long degree;
private final long capacity;
private final long limit;
private final boolean rings;
private final boolean sourceInRing;
private long pathCount;
public Traverser(Id sourceV, Id label, long degree,
long capacity, long limit, boolean rings) {
public Traverser(Id sourceV, Id label, int depth, long degree,
long capacity, long limit, boolean rings,
boolean sourceInRing) {
this.source = sourceV;
this.sources.add(sourceV, new Node(sourceV));
this.accessedVertices.add(sourceV);
this.label = label;
this.depth = depth;
this.degree = degree;
this.capacity = capacity;
this.limit = limit;
this.rings = rings;
this.sourceInRing = sourceInRing;
this.pathCount = 0L;
}
@ -112,7 +136,10 @@ public class SubGraphTraverser extends HugeTraverser {
// Traversal vertices of previous level
for (Map.Entry<Id, List<Node>> entry : this.sources.entrySet()) {
Id vid = entry.getKey();
edges = edgesOfVertex(vid, direction, this.label, this.degree);
// Record edgeList to determine if multiple edges exist
List<Edge> edgeList = IteratorUtils.list(edgesOfVertex(
vid, direction, this.label, this.degree));
edges = edgeList.iterator();
if (!edges.hasNext()) {
// Reach the end, rays found
@ -128,33 +155,80 @@ public class SubGraphTraverser extends HugeTraverser {
}
}
}
int neighborCount = 0;
Set<Id> currentNeighbors = new HashSet<>();
while (edges.hasNext()) {
neighborCount++;
HugeEdge edge = (HugeEdge) edges.next();
Id target = edge.id().otherVertexId();
// Avoid deduplicate path
if (currentNeighbors.contains(target)) {
continue;
}
currentNeighbors.add(target);
this.accessedVertices.add(target);
for (Node n : entry.getValue()) {
if (!n.contains(target)) {
for (Node node : entry.getValue()) {
// No ring, continue
if (!node.contains(target)) {
// Add node to next start-nodes
newVertices.add(target, new Node(target, n));
newVertices.add(target, new Node(target, node));
continue;
}
// Rings found and expect rings
if (this.rings) {
assert n.contains(target);
List<Id> prePath = n.path();
prePath.add(target);
paths.add(new Path(null, prePath));
// Rays found if it's fake ring like:
// path is pattern: A->B<-A && A is only neighbor of B
boolean uniqueEdge = neighborCount == 1 &&
!edges.hasNext();
boolean bothBack = target.equals(node.parent().id()) &&
direction == Directions.BOTH;
if (!this.rings && bothBack && uniqueEdge) {
paths.add(new Path(null, node.path()));
this.pathCount++;
if (reachLimit()) {
return paths;
}
}
// Actual rings found
if (this.rings) {
boolean ringsFound = false;
// 1. sourceInRing is false, or
// 2. sourceInRing is true and target == source
if (!sourceInRing || target.equals(this.source)) {
if (!target.equals(node.parent().id())) {
ringsFound = true;
} else if (direction != Directions.BOTH) {
ringsFound = true;
} else if (hasMultiEdges(edgeList, target)) {
ringsFound = true;
}
}
if (ringsFound) {
List<Id> path = node.path();
path.add(target);
paths.add(new Path(null, path));
this.pathCount++;
if (reachLimit()) {
return paths;
}
}
}
}
}
}
// Re-init sources
this.sources = newVertices;
if (!rings && --this.depth <= 0) {
for (List<Node> list : newVertices.values()) {
for (Node n : list) {
paths.add(new Path(null, n.path()));
}
}
}
return paths;
}