diff --git a/hugegraph-api/pom.xml b/hugegraph-api/pom.xml index fc27f55e2c..ee328812d7 100644 --- a/hugegraph-api/pom.xml +++ b/hugegraph-api/pom.xml @@ -86,7 +86,7 @@ - 0.39.0.0 + 0.40.0.0 diff --git a/hugegraph-api/src/main/java/com/baidu/hugegraph/api/traversers/RaysAPI.java b/hugegraph-api/src/main/java/com/baidu/hugegraph/api/traversers/RaysAPI.java index edeccbe34e..c4996a79dd 100644 --- a/hugegraph-api/src/main/java/com/baidu/hugegraph/api/traversers/RaysAPI.java +++ b/hugegraph-api/src/main/java/com/baidu/hugegraph/api/traversers/RaysAPI.java @@ -72,8 +72,9 @@ public String get(@Context GraphManager manager, @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)); diff --git a/hugegraph-api/src/main/java/com/baidu/hugegraph/api/traversers/RingsAPI.java b/hugegraph-api/src/main/java/com/baidu/hugegraph/api/traversers/RingsAPI.java index 3921f3b4e1..814cdbdd76 100644 --- a/hugegraph-api/src/main/java/com/baidu/hugegraph/api/traversers/RingsAPI.java +++ b/hugegraph-api/src/main/java/com/baidu/hugegraph/api/traversers/RingsAPI.java @@ -64,6 +64,8 @@ public String get(@Context GraphManager manager, @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 String get(@Context GraphManager manager, @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 String get(@Context GraphManager manager, SubGraphTraverser traverser = new SubGraphTraverser(g); List paths = traverser.rings(source, dir, edgeLabel, - depth, degree, - capacity, limit); + depth, sourceInRing, + degree, capacity, + limit); return manager.serializer(g).writePaths("rings", paths, false); } } diff --git a/hugegraph-api/src/main/java/com/baidu/hugegraph/version/ApiVersion.java b/hugegraph-api/src/main/java/com/baidu/hugegraph/version/ApiVersion.java index 80656b1552..427048464f 100644 --- a/hugegraph-api/src/main/java/com/baidu/hugegraph/version/ApiVersion.java +++ b/hugegraph-api/src/main/java/com/baidu/hugegraph/version/ApiVersion.java @@ -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 diff --git a/hugegraph-core/src/main/java/com/baidu/hugegraph/traversal/algorithm/HugeTraverser.java b/hugegraph-core/src/main/java/com/baidu/hugegraph/traversal/algorithm/HugeTraverser.java index 279064fcaa..912471d657 100644 --- a/hugegraph-core/src/main/java/com/baidu/hugegraph/traversal/algorithm/HugeTraverser.java +++ b/hugegraph-core/src/main/java/com/baidu/hugegraph/traversal/algorithm/HugeTraverser.java @@ -27,6 +27,7 @@ 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 boolean equals(Object object) { 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); } } diff --git a/hugegraph-core/src/main/java/com/baidu/hugegraph/traversal/algorithm/SubGraphTraverser.java b/hugegraph-core/src/main/java/com/baidu/hugegraph/traversal/algorithm/SubGraphTraverser.java index 79d8e18a77..1a7d63911c 100644 --- a/hugegraph-core/src/main/java/com/baidu/hugegraph/traversal/algorithm/SubGraphTraverser.java +++ b/hugegraph-core/src/main/java/com/baidu/hugegraph/traversal/algorithm/SubGraphTraverser.java @@ -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 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 SubGraphTraverser(HugeGraph graph) { public List 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 rings(Id sourceV, Directions dir, String label, - int depth, long degree, long capacity, long limit) { + public List 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 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 @@ private List subGraphPaths(Id sourceV, Directions dir, String label, 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 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 @@ private List subGraphPaths(Id sourceV, Directions dir, String label, return paths; } + private static boolean hasMultiEdges(List 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 sources = newMultivalueMap(); private Set 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 List forward(Directions direction) { // Traversal vertices of previous level for (Map.Entry> 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 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 List forward(Directions direction) { } } } + + int neighborCount = 0; + Set 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 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 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 list : newVertices.values()) { + for (Node n : list) { + paths.add(new Path(null, n.path())); + } + } + } + return paths; }