From d12f5734e64a56fd45c9fe09c1f277acf83af2e2 Mon Sep 17 00:00:00 2001 From: DanGuge <77946882+DanGuge@users.noreply.github.com> Date: Sat, 19 Aug 2023 13:41:08 +0800 Subject: [PATCH 1/3] feat(api&core): in oltp apis, add statistics info and support full info about vertices and edges (#2262) * chore: improve gitignore file * feat: add ApiMeasure to collect runtime data ApiMeasure will count the number of vertices and edges traversed at runtime, and the time the api takes to execute * feat: Add ApiMeasure to JsonSerializer and Modify the Serializer interface * JsonSerializer: return measure information in api response * Serializer: fit the feature that returns complete information about vertices and edges * refactor: format code based on hugegraph-style.xml * feat: Add statistics information in all oltp restful apis response and Support full information about vertices and edges Statistics information: * add vertexIterCounter and edgeIterCounter in HugeTraverser.java to track traversed vertices and edges at run time * modify all oltp restful apis to add statistics information in response Full information about vertices and edges: * add 'with_vertex' and 'with_edge' parameter option in apis * modify oltp apis to support vertex and edge information in api response * add EdgeRecord in HugeTraverser.java to record edges at run time and generate the edge information returned in api response * modify Path and PathSet in HugeTraverser.java to support full edge information storage * modify all traversers to support track of edge information at run time * fix: numeric cast * fix: Jaccard Similarity api test * fix: adjust the code style and naming convention * Empty commit * Empty commit * fix: 1. change System.currentTimeMillis() to System.nanoTime(); 2. modify addCount() * fix: rollback change in .gitignore * fix: rollback ServerOptions.java code style * fix: rollback API.java code style and add exception in else branch * fix: fix code style * fix: name style & code style * rename edgeRecord to edgeResults * fix Request class code style in SameNeighborsAPI.java --- .../java/org/apache/hugegraph/api/API.java | 89 ++- .../api/traversers/AllShortestPathsAPI.java | 73 ++- .../api/traversers/CrosspointsAPI.java | 30 +- .../traversers/CustomizedCrosspointsAPI.java | 125 ++-- .../api/traversers/CustomizedPathsAPI.java | 103 ++-- .../hugegraph/api/traversers/EdgesAPI.java | 28 +- .../api/traversers/FusiformSimilarityAPI.java | 66 ++- .../api/traversers/JaccardSimilarityAPI.java | 55 +- .../api/traversers/KneighborAPI.java | 78 ++- .../hugegraph/api/traversers/KoutAPI.java | 89 ++- .../traversers/MultiNodeShortestPathAPI.java | 82 +-- .../hugegraph/api/traversers/PathsAPI.java | 102 ++-- .../hugegraph/api/traversers/RaysAPI.java | 67 ++- .../hugegraph/api/traversers/RingsAPI.java | 74 ++- .../api/traversers/SameNeighborsAPI.java | 108 +++- .../api/traversers/ShortestPathAPI.java | 67 ++- .../SingleSourceShortestPathAPI.java | 79 ++- .../api/traversers/TemplatePathsAPI.java | 104 ++-- .../hugegraph/api/traversers/VerticesAPI.java | 28 +- .../traversers/WeightedShortestPathAPI.java | 84 ++- .../hugegraph/config/ServerOptions.java | 2 +- .../apache/hugegraph/core/GraphManager.java | 4 + .../hugegraph/serializer/JsonSerializer.java | 153 +++-- .../hugegraph/serializer/Serializer.java | 24 +- .../comm/TriangleCountAlgorithm.java | 38 +- .../algorithm/CollectionPathsTraverser.java | 58 +- .../algorithm/CustomizePathsTraverser.java | 82 +-- .../CustomizedCrosspointsTraverser.java | 133 +++-- .../FusiformSimilarityTraverser.java | 67 ++- .../traversal/algorithm/HugeTraverser.java | 541 +++++++++++------- .../algorithm/JaccardSimilarTraverser.java | 49 +- .../algorithm/KneighborTraverser.java | 16 +- .../traversal/algorithm/KoutTraverser.java | 24 +- .../MultiNodeShortestPathTraverser.java | 117 ++-- .../traversal/algorithm/PathTraverser.java | 25 +- .../traversal/algorithm/PathsTraverser.java | 23 +- .../algorithm/SameNeighborTraverser.java | 57 +- .../algorithm/ShortestPathTraverser.java | 73 ++- .../SingleSourceShortestPathTraverser.java | 206 ++++--- .../algorithm/SubGraphTraverser.java | 175 +++--- .../algorithm/TemplatePathsTraverser.java | 58 +- .../records/ShortestPathRecords.java | 4 +- .../records/SingleWayMultiPathsRecords.java | 18 +- .../traversers/JaccardSimilarityApiTest.java | 12 +- 44 files changed, 2268 insertions(+), 1222 deletions(-) diff --git a/hugegraph-api/src/main/java/org/apache/hugegraph/api/API.java b/hugegraph-api/src/main/java/org/apache/hugegraph/api/API.java index afaba499b3..99fe67e5ba 100644 --- a/hugegraph-api/src/main/java/org/apache/hugegraph/api/API.java +++ b/hugegraph-api/src/main/java/org/apache/hugegraph/api/API.java @@ -22,41 +22,39 @@ import java.util.concurrent.Callable; import java.util.function.Consumer; -import jakarta.ws.rs.ForbiddenException; -import jakarta.ws.rs.NotFoundException; -import jakarta.ws.rs.NotSupportedException; -import jakarta.ws.rs.core.MediaType; - +import org.apache.commons.lang.mutable.MutableLong; +import org.apache.hugegraph.HugeException; +import org.apache.hugegraph.HugeGraph; import org.apache.hugegraph.core.GraphManager; import org.apache.hugegraph.define.Checkable; import org.apache.hugegraph.metrics.MetricsUtil; -import org.slf4j.Logger; - -import org.apache.hugegraph.HugeException; -import org.apache.hugegraph.HugeGraph; import org.apache.hugegraph.util.E; +import org.apache.hugegraph.util.InsertionOrderUtil; import org.apache.hugegraph.util.JsonUtil; import org.apache.hugegraph.util.Log; +import org.slf4j.Logger; + import com.codahale.metrics.Meter; import com.google.common.collect.ImmutableMap; -public class API { +import jakarta.ws.rs.ForbiddenException; +import jakarta.ws.rs.NotFoundException; +import jakarta.ws.rs.NotSupportedException; +import jakarta.ws.rs.core.MediaType; - protected static final Logger LOG = Log.logger(API.class); +public class API { public static final String CHARSET = "UTF-8"; - public static final String TEXT_PLAIN = MediaType.TEXT_PLAIN; public static final String APPLICATION_JSON = MediaType.APPLICATION_JSON; public static final String APPLICATION_JSON_WITH_CHARSET = APPLICATION_JSON + ";charset=" + CHARSET; public static final String JSON = MediaType.APPLICATION_JSON_TYPE .getSubtype(); - public static final String ACTION_APPEND = "append"; public static final String ACTION_ELIMINATE = "eliminate"; public static final String ACTION_CLEAR = "clear"; - + protected static final Logger LOG = Log.logger(API.class); private static final Meter SUCCEED_METER = MetricsUtil.registerMeter(API.class, "commit-succeed"); private static final Meter ILLEGAL_ARG_ERROR_METER = @@ -69,8 +67,7 @@ public class API { public static HugeGraph graph(GraphManager manager, String graph) { HugeGraph g = manager.graph(graph); if (g == null) { - throw new NotFoundException(String.format( - "Graph '%s' does not exist", graph)); + throw new NotFoundException(String.format("Graph '%s' does not exist", graph)); } return g; } @@ -140,8 +137,7 @@ protected static void checkUpdatingBody(Checkable body) { body.checkUpdate(); } - protected static void checkCreatingBody( - Collection bodies) { + protected static void checkCreatingBody(Collection bodies) { E.checkArgumentNotNull(bodies, "The request body can't be empty"); for (Checkable body : bodies) { E.checkArgument(body != null, @@ -150,8 +146,7 @@ protected static void checkCreatingBody( } } - protected static void checkUpdatingBody( - Collection bodies) { + protected static void checkUpdatingBody(Collection bodies) { E.checkArgumentNotNull(bodies, "The request body can't be empty"); for (Checkable body : bodies) { E.checkArgumentNotNull(body, @@ -186,8 +181,58 @@ public static boolean checkAndParseAction(String action) { } else if (action.equals(ACTION_ELIMINATE)) { return false; } else { - throw new NotSupportedException( - String.format("Not support action '%s'", action)); + throw new NotSupportedException(String.format("Not support action '%s'", action)); + } + } + + public static class ApiMeasurer { + + public static final String EDGE_ITER = "edge_iterations"; + public static final String VERTICE_ITER = "vertice_iterations"; + public static final String COST = "cost(ns)"; + private final long timeStart; + private final Map measures; + + public ApiMeasurer() { + this.timeStart = System.nanoTime(); + this.measures = InsertionOrderUtil.newMap(); + } + + public Map measures() { + measures.put(COST, System.nanoTime() - timeStart); + return measures; + } + + public void put(String key, String value) { + this.measures.put(key, value); + } + + public void put(String key, long value) { + this.measures.put(key, value); + } + + public void put(String key, int value) { + this.measures.put(key, value); + } + + protected void addCount(String key, long value) { + Object current = measures.get(key); + if (current == null) { + measures.put(key, new MutableLong(value)); + } else if (current instanceof MutableLong) { + ((MutableLong) measures.computeIfAbsent(key, MutableLong::new)).add(value); + } else if (current instanceof Long) { + Long currentLong = (Long) current; + measures.put(key, new MutableLong(currentLong + value)); + } else { + throw new NotSupportedException("addCount() method's 'value' datatype must be " + + "Long or MutableLong"); + } + } + + public void addIterCount(long verticeIters, long edgeIters) { + this.addCount(EDGE_ITER, edgeIters); + this.addCount(VERTICE_ITER, verticeIters); } } } diff --git a/hugegraph-api/src/main/java/org/apache/hugegraph/api/traversers/AllShortestPathsAPI.java b/hugegraph-api/src/main/java/org/apache/hugegraph/api/traversers/AllShortestPathsAPI.java index 030c4e8cc1..e432f81ea7 100644 --- a/hugegraph-api/src/main/java/org/apache/hugegraph/api/traversers/AllShortestPathsAPI.java +++ b/hugegraph-api/src/main/java/org/apache/hugegraph/api/traversers/AllShortestPathsAPI.java @@ -20,19 +20,10 @@ import static org.apache.hugegraph.traversal.algorithm.HugeTraverser.DEFAULT_CAPACITY; import static org.apache.hugegraph.traversal.algorithm.HugeTraverser.DEFAULT_MAX_DEGREE; +import java.util.HashSet; +import java.util.Iterator; import java.util.List; - -import io.swagger.v3.oas.annotations.tags.Tag; -import jakarta.inject.Singleton; -import jakarta.ws.rs.DefaultValue; -import jakarta.ws.rs.GET; -import jakarta.ws.rs.Path; -import jakarta.ws.rs.PathParam; -import jakarta.ws.rs.Produces; -import jakarta.ws.rs.QueryParam; -import jakarta.ws.rs.core.Context; - -import org.slf4j.Logger; +import java.util.Set; import org.apache.hugegraph.HugeGraph; import org.apache.hugegraph.api.API; @@ -44,9 +35,22 @@ import org.apache.hugegraph.traversal.algorithm.ShortestPathTraverser; import org.apache.hugegraph.type.define.Directions; import org.apache.hugegraph.util.Log; +import org.apache.tinkerpop.gremlin.structure.Edge; +import org.slf4j.Logger; + import com.codahale.metrics.annotation.Timed; import com.google.common.collect.ImmutableList; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.inject.Singleton; +import jakarta.ws.rs.DefaultValue; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.PathParam; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.QueryParam; +import jakarta.ws.rs.core.Context; + @Path("graphs/{graph}/traversers/allshortestpaths") @Singleton @Tag(name = "AllShortestPathsAPI") @@ -68,13 +72,20 @@ public String get(@Context GraphManager manager, @DefaultValue(DEFAULT_MAX_DEGREE) long maxDegree, @QueryParam("skip_degree") @DefaultValue("0") long skipDegree, + @QueryParam("with_vertex") + @DefaultValue("false") boolean withVertex, + @QueryParam("with_edge") + @DefaultValue("false") boolean withEdge, @QueryParam("capacity") @DefaultValue(DEFAULT_CAPACITY) long capacity) { LOG.debug("Graph [{}] get shortest path from '{}', to '{}' with " + "direction {}, edge label {}, max depth '{}', " + - "max degree '{}', skipped degree '{}' and capacity '{}'", + "max degree '{}', skipped degree '{}', capacity '{}', " + + "with_vertex '{}' and with_edge '{}'", graph, source, target, direction, edgeLabel, depth, - maxDegree, skipDegree, capacity); + maxDegree, skipDegree, capacity, withVertex, withEdge); + + ApiMeasurer measure = new ApiMeasurer(); Id sourceId = VertexAPI.checkAndParseVertexId(source); Id targetId = VertexAPI.checkAndParseVertexId(target); @@ -85,9 +96,35 @@ public String get(@Context GraphManager manager, ShortestPathTraverser traverser = new ShortestPathTraverser(g); List edgeLabels = edgeLabel == null ? ImmutableList.of() : ImmutableList.of(edgeLabel); - HugeTraverser.PathSet paths = traverser.allShortestPaths( - sourceId, targetId, dir, edgeLabels, - depth, maxDegree, skipDegree, capacity); - return manager.serializer(g).writePaths("paths", paths, false); + HugeTraverser.PathSet paths = traverser.allShortestPaths(sourceId, targetId, dir, + edgeLabels, depth, maxDegree, + skipDegree, capacity); + + measure.addIterCount(traverser.vertexIterCounter.get(), + traverser.edgeIterCounter.get()); + + Iterator iterVertex; + Set vertexIds = new HashSet<>(); + for (HugeTraverser.Path path : paths) { + vertexIds.addAll(path.vertices()); + } + if (withVertex && !vertexIds.isEmpty()) { + iterVertex = g.vertices(vertexIds.toArray()); + measure.addIterCount(vertexIds.size(), 0L); + } else { + iterVertex = vertexIds.iterator(); + } + + Iterator iterEdge; + Set edges = paths.getEdges(); + if (withEdge && !edges.isEmpty()) { + iterEdge = edges.iterator(); + } else { + iterEdge = HugeTraverser.EdgeRecord.getEdgeIds(edges).iterator(); + } + + return manager.serializer(g, measure.measures()) + .writePaths("paths", paths, false, + iterVertex, iterEdge); } } diff --git a/hugegraph-api/src/main/java/org/apache/hugegraph/api/traversers/CrosspointsAPI.java b/hugegraph-api/src/main/java/org/apache/hugegraph/api/traversers/CrosspointsAPI.java index 39de473b8f..eda042511c 100644 --- a/hugegraph-api/src/main/java/org/apache/hugegraph/api/traversers/CrosspointsAPI.java +++ b/hugegraph-api/src/main/java/org/apache/hugegraph/api/traversers/CrosspointsAPI.java @@ -21,18 +21,6 @@ import static org.apache.hugegraph.traversal.algorithm.HugeTraverser.DEFAULT_MAX_DEGREE; import static org.apache.hugegraph.traversal.algorithm.HugeTraverser.DEFAULT_PATHS_LIMIT; -import io.swagger.v3.oas.annotations.tags.Tag; -import jakarta.inject.Singleton; -import jakarta.ws.rs.DefaultValue; -import jakarta.ws.rs.GET; -import jakarta.ws.rs.Path; -import jakarta.ws.rs.PathParam; -import jakarta.ws.rs.Produces; -import jakarta.ws.rs.QueryParam; -import jakarta.ws.rs.core.Context; - -import org.slf4j.Logger; - import org.apache.hugegraph.HugeGraph; import org.apache.hugegraph.api.API; import org.apache.hugegraph.api.graph.EdgeAPI; @@ -43,8 +31,20 @@ import org.apache.hugegraph.traversal.algorithm.PathsTraverser; import org.apache.hugegraph.type.define.Directions; import org.apache.hugegraph.util.Log; +import org.slf4j.Logger; + import com.codahale.metrics.annotation.Timed; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.inject.Singleton; +import jakarta.ws.rs.DefaultValue; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.PathParam; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.QueryParam; +import jakarta.ws.rs.core.Context; + @Path("graphs/{graph}/traversers/crosspoints") @Singleton @Tag(name = "CrosspointsAPI") @@ -74,6 +74,7 @@ public String get(@Context GraphManager manager, graph, source, target, direction, edgeLabel, depth, maxDegree, capacity, limit); + ApiMeasurer measure = new ApiMeasurer(); Id sourceId = VertexAPI.checkAndParseVertexId(source); Id targetId = VertexAPI.checkAndParseVertexId(target); Directions dir = Directions.convert(EdgeAPI.parseDirection(direction)); @@ -84,6 +85,9 @@ public String get(@Context GraphManager manager, dir, edgeLabel, depth, maxDegree, capacity, limit); - return manager.serializer(g).writePaths("crosspoints", paths, true); + measure.addIterCount(traverser.vertexIterCounter.get(), + traverser.edgeIterCounter.get()); + return manager.serializer(g, measure.measures()) + .writePaths("crosspoints", paths, true); } } diff --git a/hugegraph-api/src/main/java/org/apache/hugegraph/api/traversers/CustomizedCrosspointsAPI.java b/hugegraph-api/src/main/java/org/apache/hugegraph/api/traversers/CustomizedCrosspointsAPI.java index da35f7325f..cadbd2ce00 100644 --- a/hugegraph-api/src/main/java/org/apache/hugegraph/api/traversers/CustomizedCrosspointsAPI.java +++ b/hugegraph-api/src/main/java/org/apache/hugegraph/api/traversers/CustomizedCrosspointsAPI.java @@ -22,38 +22,39 @@ import static org.apache.hugegraph.traversal.algorithm.HugeTraverser.DEFAULT_PATHS_LIMIT; import java.util.ArrayList; +import java.util.Collections; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; -import io.swagger.v3.oas.annotations.tags.Tag; -import jakarta.inject.Singleton; -import jakarta.ws.rs.Consumes; -import jakarta.ws.rs.POST; -import jakarta.ws.rs.Path; -import jakarta.ws.rs.PathParam; -import jakarta.ws.rs.Produces; -import jakarta.ws.rs.core.Context; - -import org.apache.tinkerpop.gremlin.structure.Vertex; -import org.apache.hugegraph.core.GraphManager; -import org.slf4j.Logger; - import org.apache.hugegraph.HugeGraph; import org.apache.hugegraph.api.API; import org.apache.hugegraph.backend.id.Id; -import org.apache.hugegraph.backend.query.QueryResults; +import org.apache.hugegraph.core.GraphManager; import org.apache.hugegraph.traversal.algorithm.CustomizedCrosspointsTraverser; import org.apache.hugegraph.traversal.algorithm.HugeTraverser; import org.apache.hugegraph.type.define.Directions; import org.apache.hugegraph.util.E; import org.apache.hugegraph.util.Log; +import org.apache.tinkerpop.gremlin.structure.Edge; +import org.apache.tinkerpop.gremlin.structure.Vertex; +import org.slf4j.Logger; + import com.codahale.metrics.annotation.Timed; import com.fasterxml.jackson.annotation.JsonAlias; import com.fasterxml.jackson.annotation.JsonProperty; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.inject.Singleton; +import jakarta.ws.rs.Consumes; +import jakarta.ws.rs.POST; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.PathParam; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.core.Context; + @Path("graphs/{graph}/traversers/customizedcrosspoints") @Singleton @Tag(name = "CustomizedCrosspointsAPI") @@ -61,6 +62,21 @@ public class CustomizedCrosspointsAPI extends API { private static final Logger LOG = Log.logger(CustomizedCrosspointsAPI.class); + private static List pathPatterns( + HugeGraph graph, CrosspointsRequest request) { + int stepSize = request.pathPatterns.size(); + List pathPatterns = new ArrayList<>(stepSize); + for (PathPattern pattern : request.pathPatterns) { + CustomizedCrosspointsTraverser.PathPattern pathPattern = + new CustomizedCrosspointsTraverser.PathPattern(); + for (Step step : pattern.steps) { + pathPattern.add(step.jsonToStep(graph)); + } + pathPatterns.add(pathPattern); + } + return pathPatterns; + } + @POST @Timed @Consumes(APPLICATION_JSON) @@ -78,55 +94,56 @@ public String post(@Context GraphManager manager, "The steps of crosspoints request can't be empty"); LOG.debug("Graph [{}] get customized crosspoints from source vertex " + - "'{}', with path_pattern '{}', with_path '{}', with_vertex " + - "'{}', capacity '{}' and limit '{}'", graph, request.sources, - request.pathPatterns, request.withPath, request.withVertex, - request.capacity, request.limit); + "'{}', with path_pattern '{}', with path '{}', with_vertex " + + "'{}', capacity '{}', limit '{}' and with_edge '{}'", + graph, request.sources, request.pathPatterns, request.withPath, + request.withVertex, request.capacity, request.limit, request.withEdge); + + ApiMeasurer measure = new ApiMeasurer(); HugeGraph g = graph(manager, graph); Iterator sources = request.sources.vertices(g); - List patterns; - patterns = pathPatterns(g, request); CustomizedCrosspointsTraverser traverser = - new CustomizedCrosspointsTraverser(g); - CustomizedCrosspointsTraverser.CrosspointsPaths paths; - paths = traverser.crosspointsPaths(sources, patterns, request.capacity, - request.limit); - Iterator iter = QueryResults.emptyIterator(); - if (!request.withVertex) { - return manager.serializer(g).writeCrosspoints(paths, iter, - request.withPath); - } - Set ids = new HashSet<>(); + new CustomizedCrosspointsTraverser(g); + + List patterns = pathPatterns(g, request); + CustomizedCrosspointsTraverser.CrosspointsPaths paths = + traverser.crosspointsPaths(sources, patterns, request.capacity, request.limit); + + measure.addIterCount(traverser.vertexIterCounter.get(), + traverser.edgeIterCounter.get()); + + + Iterator iterVertex; + Set vertexIds = new HashSet<>(); if (request.withPath) { - for (HugeTraverser.Path p : paths.paths()) { - ids.addAll(p.vertices()); + for (HugeTraverser.Path path : paths.paths()) { + vertexIds.addAll(path.vertices()); } } else { - ids = paths.crosspoints(); + vertexIds = paths.crosspoints(); } - if (!ids.isEmpty()) { - iter = g.vertices(ids.toArray()); + if (request.withVertex && !vertexIds.isEmpty()) { + iterVertex = g.vertices(vertexIds.toArray()); + measure.addIterCount(vertexIds.size(), 0L); + } else { + iterVertex = vertexIds.iterator(); } - return manager.serializer(g).writeCrosspoints(paths, iter, - request.withPath); - } - private static List - pathPatterns(HugeGraph graph, CrosspointsRequest request) { - int stepSize = request.pathPatterns.size(); - List pathPatterns; - pathPatterns = new ArrayList<>(stepSize); - for (PathPattern pattern : request.pathPatterns) { - CustomizedCrosspointsTraverser.PathPattern pathPattern; - pathPattern = new CustomizedCrosspointsTraverser.PathPattern(); - for (Step step : pattern.steps) { - pathPattern.add(step.jsonToStep(graph)); + Iterator iterEdge = Collections.emptyIterator(); + if (request.withPath) { + Set edges = traverser.edgeResults().getEdges(paths.paths()); + if (request.withEdge) { + iterEdge = edges.iterator(); + } else { + iterEdge = HugeTraverser.EdgeRecord.getEdgeIds(edges).iterator(); } - pathPatterns.add(pathPattern); } - return pathPatterns; + + return manager.serializer(g, measure.measures()) + .writeCrosspoints(paths, iterVertex, + iterEdge, request.withPath); } private static class CrosspointsRequest { @@ -143,14 +160,16 @@ private static class CrosspointsRequest { public boolean withPath = false; @JsonProperty("with_vertex") public boolean withVertex = false; + @JsonProperty("with_edge") + public boolean withEdge = false; @Override public String toString() { return String.format("CrosspointsRequest{sourceVertex=%s," + "pathPatterns=%s,withPath=%s,withVertex=%s," + - "capacity=%s,limit=%s}", this.sources, - this.pathPatterns, this.withPath, - this.withVertex, this.capacity, this.limit); + "capacity=%s,limit=%s,withEdge=%s}", this.sources, + this.pathPatterns, this.withPath, this.withVertex, + this.capacity, this.limit, this.withEdge); } } diff --git a/hugegraph-api/src/main/java/org/apache/hugegraph/api/traversers/CustomizedPathsAPI.java b/hugegraph-api/src/main/java/org/apache/hugegraph/api/traversers/CustomizedPathsAPI.java index 272009ea24..5641e31193 100644 --- a/hugegraph-api/src/main/java/org/apache/hugegraph/api/traversers/CustomizedPathsAPI.java +++ b/hugegraph-api/src/main/java/org/apache/hugegraph/api/traversers/CustomizedPathsAPI.java @@ -30,33 +30,33 @@ import java.util.Map; import java.util.Set; -import io.swagger.v3.oas.annotations.tags.Tag; -import jakarta.inject.Singleton; -import jakarta.ws.rs.Consumes; -import jakarta.ws.rs.POST; -import jakarta.ws.rs.Path; -import jakarta.ws.rs.PathParam; -import jakarta.ws.rs.Produces; -import jakarta.ws.rs.core.Context; - -import org.apache.tinkerpop.gremlin.structure.Vertex; -import org.apache.hugegraph.core.GraphManager; -import org.slf4j.Logger; - import org.apache.hugegraph.HugeGraph; import org.apache.hugegraph.api.API; import org.apache.hugegraph.backend.id.Id; -import org.apache.hugegraph.backend.query.QueryResults; +import org.apache.hugegraph.core.GraphManager; import org.apache.hugegraph.traversal.algorithm.CustomizePathsTraverser; import org.apache.hugegraph.traversal.algorithm.HugeTraverser; import org.apache.hugegraph.traversal.algorithm.steps.WeightedEdgeStep; import org.apache.hugegraph.type.define.Directions; import org.apache.hugegraph.util.E; import org.apache.hugegraph.util.Log; +import org.apache.tinkerpop.gremlin.structure.Edge; +import org.apache.tinkerpop.gremlin.structure.Vertex; +import org.slf4j.Logger; + import com.codahale.metrics.annotation.Timed; import com.fasterxml.jackson.annotation.JsonAlias; import com.fasterxml.jackson.annotation.JsonProperty; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.inject.Singleton; +import jakarta.ws.rs.Consumes; +import jakarta.ws.rs.POST; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.PathParam; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.core.Context; + @Path("graphs/{graph}/traversers/customizedpaths") @Singleton @Tag(name = "CustomizedPathsAPI") @@ -64,6 +64,16 @@ public class CustomizedPathsAPI extends API { private static final Logger LOG = Log.logger(CustomizedPathsAPI.class); + private static List step(HugeGraph graph, + PathRequest request) { + int stepSize = request.steps.size(); + List steps = new ArrayList<>(stepSize); + for (Step step : request.steps) { + steps.add(step.jsonToStep(graph)); + } + return steps; + } + @POST @Timed @Consumes(APPLICATION_JSON) @@ -81,10 +91,12 @@ public String post(@Context GraphManager manager, } LOG.debug("Graph [{}] get customized paths from source vertex '{}', " + - "with steps '{}', sort by '{}', capacity '{}', limit '{}' " + - "and with_vertex '{}'", graph, request.sources, request.steps, + "with steps '{}', sort by '{}', capacity '{}', limit '{}', " + + "with_vertex '{}' and with_edge '{}'", graph, request.sources, request.steps, request.sortBy, request.capacity, request.limit, - request.withVertex); + request.withVertex, request.withEdge); + + ApiMeasurer measure = new ApiMeasurer(); HugeGraph g = graph(manager, graph); Iterator sources = request.sources.vertices(g); @@ -95,6 +107,8 @@ public String post(@Context GraphManager manager, List paths; paths = traverser.customizedPaths(sources, steps, sorted, request.capacity, request.limit); + measure.addIterCount(traverser.vertexIterCounter.get(), + traverser.edgeIterCounter.get()); if (sorted) { boolean incr = request.sortBy == SortBy.INCR; @@ -102,29 +116,35 @@ public String post(@Context GraphManager manager, request.limit); } - if (!request.withVertex) { - return manager.serializer(g).writePaths("paths", paths, false); + Iterator iterVertex; + Set vertexIds = new HashSet<>(); + for (HugeTraverser.Path path : paths) { + vertexIds.addAll(path.vertices()); } - - Set ids = new HashSet<>(); - for (HugeTraverser.Path p : paths) { - ids.addAll(p.vertices()); + if (request.withVertex && !vertexIds.isEmpty()) { + iterVertex = g.vertices(vertexIds.toArray()); + measure.addIterCount(vertexIds.size(), 0L); + } else { + iterVertex = vertexIds.iterator(); } - Iterator iter = QueryResults.emptyIterator(); - if (!ids.isEmpty()) { - iter = g.vertices(ids.toArray()); + + Iterator iterEdge; + Set edges = traverser.edgeResults().getEdges(paths); + if (request.withEdge && !edges.isEmpty()) { + iterEdge = edges.iterator(); + } else { + iterEdge = HugeTraverser.EdgeRecord.getEdgeIds(edges).iterator(); } - return manager.serializer(g).writePaths("paths", paths, false, iter); + + return manager.serializer(g, measure.measures()) + .writePaths("paths", paths, false, + iterVertex, iterEdge); } - private static List step(HugeGraph graph, - PathRequest req) { - int stepSize = req.steps.size(); - List steps = new ArrayList<>(stepSize); - for (Step step : req.steps) { - steps.add(step.jsonToStep(graph)); - } - return steps; + private enum SortBy { + INCR, + DECR, + NONE } private static class PathRequest { @@ -142,13 +162,16 @@ private static class PathRequest { @JsonProperty("with_vertex") public boolean withVertex = false; + @JsonProperty("with_edge") + public boolean withEdge = false; + @Override public String toString() { return String.format("PathRequest{sourceVertex=%s,steps=%s," + "sortBy=%s,capacity=%s,limit=%s," + - "withVertex=%s}", this.sources, this.steps, + "withVertex=%s,withEdge=%s}", this.sources, this.steps, this.sortBy, this.capacity, this.limit, - this.withVertex); + this.withVertex, this.withEdge); } } @@ -190,10 +213,4 @@ private WeightedEdgeStep jsonToStep(HugeGraph g) { this.defaultWeight, this.sample); } } - - private enum SortBy { - INCR, - DECR, - NONE - } } diff --git a/hugegraph-api/src/main/java/org/apache/hugegraph/api/traversers/EdgesAPI.java b/hugegraph-api/src/main/java/org/apache/hugegraph/api/traversers/EdgesAPI.java index ca4909a552..da9dfe1779 100644 --- a/hugegraph-api/src/main/java/org/apache/hugegraph/api/traversers/EdgesAPI.java +++ b/hugegraph-api/src/main/java/org/apache/hugegraph/api/traversers/EdgesAPI.java @@ -22,32 +22,32 @@ import java.util.Iterator; import java.util.List; -import io.swagger.v3.oas.annotations.tags.Tag; -import jakarta.inject.Singleton; -import jakarta.ws.rs.DefaultValue; -import jakarta.ws.rs.GET; -import jakarta.ws.rs.Path; -import jakarta.ws.rs.PathParam; -import jakarta.ws.rs.Produces; -import jakarta.ws.rs.QueryParam; -import jakarta.ws.rs.core.Context; - -import org.apache.tinkerpop.gremlin.structure.Edge; -import org.apache.hugegraph.core.GraphManager; -import org.slf4j.Logger; - import org.apache.hugegraph.HugeGraph; import org.apache.hugegraph.api.API; import org.apache.hugegraph.api.filter.CompressInterceptor.Compress; import org.apache.hugegraph.backend.id.Id; import org.apache.hugegraph.backend.query.ConditionQuery; import org.apache.hugegraph.backend.store.Shard; +import org.apache.hugegraph.core.GraphManager; import org.apache.hugegraph.structure.HugeEdge; import org.apache.hugegraph.type.HugeType; import org.apache.hugegraph.util.E; import org.apache.hugegraph.util.Log; +import org.apache.tinkerpop.gremlin.structure.Edge; +import org.slf4j.Logger; + import com.codahale.metrics.annotation.Timed; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.inject.Singleton; +import jakarta.ws.rs.DefaultValue; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.PathParam; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.QueryParam; +import jakarta.ws.rs.core.Context; + @Path("graphs/{graph}/traversers/edges") @Singleton @Tag(name = "EdgesAPI") diff --git a/hugegraph-api/src/main/java/org/apache/hugegraph/api/traversers/FusiformSimilarityAPI.java b/hugegraph-api/src/main/java/org/apache/hugegraph/api/traversers/FusiformSimilarityAPI.java index fbb330ae12..1b2273dc4a 100644 --- a/hugegraph-api/src/main/java/org/apache/hugegraph/api/traversers/FusiformSimilarityAPI.java +++ b/hugegraph-api/src/main/java/org/apache/hugegraph/api/traversers/FusiformSimilarityAPI.java @@ -23,32 +23,33 @@ import static org.apache.hugegraph.traversal.algorithm.HugeTraverser.NO_LIMIT; import java.util.Iterator; - -import io.swagger.v3.oas.annotations.tags.Tag; -import jakarta.inject.Singleton; -import jakarta.ws.rs.Consumes; -import jakarta.ws.rs.POST; -import jakarta.ws.rs.Path; -import jakarta.ws.rs.PathParam; -import jakarta.ws.rs.Produces; -import jakarta.ws.rs.core.Context; - -import org.apache.tinkerpop.gremlin.structure.Vertex; -import org.apache.tinkerpop.gremlin.structure.util.CloseableIterator; -import org.apache.hugegraph.core.GraphManager; -import org.slf4j.Logger; +import java.util.Set; import org.apache.hugegraph.HugeGraph; import org.apache.hugegraph.api.API; -import org.apache.hugegraph.backend.query.QueryResults; +import org.apache.hugegraph.backend.id.Id; +import org.apache.hugegraph.core.GraphManager; import org.apache.hugegraph.traversal.algorithm.FusiformSimilarityTraverser; import org.apache.hugegraph.traversal.algorithm.FusiformSimilarityTraverser.SimilarsMap; import org.apache.hugegraph.type.define.Directions; import org.apache.hugegraph.util.E; import org.apache.hugegraph.util.Log; +import org.apache.tinkerpop.gremlin.structure.Vertex; +import org.apache.tinkerpop.gremlin.structure.util.CloseableIterator; +import org.slf4j.Logger; + import com.codahale.metrics.annotation.Timed; import com.fasterxml.jackson.annotation.JsonProperty; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.inject.Singleton; +import jakarta.ws.rs.Consumes; +import jakarta.ws.rs.POST; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.PathParam; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.core.Context; + @Path("graphs/{graph}/traversers/fusiformsimilarity") @Singleton @Tag(name = "FusiformSimilarityAPI") @@ -64,7 +65,7 @@ public String post(@Context GraphManager manager, @PathParam("graph") String graph, FusiformSimilarityRequest request) { E.checkArgumentNotNull(request, "The fusiform similarity " + - "request body can't be null"); + "request body can't be null"); E.checkArgumentNotNull(request.sources, "The sources of fusiform similarity " + "request can't be null"); @@ -94,28 +95,37 @@ public String post(@Context GraphManager manager, request.minNeighbors, request.alpha, request.minSimilars, request.groupProperty, request.minGroups); + ApiMeasurer measure = new ApiMeasurer(); HugeGraph g = graph(manager, graph); Iterator sources = request.sources.vertices(g); E.checkArgument(sources != null && sources.hasNext(), "The source vertices can't be empty"); - FusiformSimilarityTraverser traverser = - new FusiformSimilarityTraverser(g); + FusiformSimilarityTraverser traverser = new FusiformSimilarityTraverser(g); SimilarsMap result = traverser.fusiformSimilarity( - sources, request.direction, request.label, - request.minNeighbors, request.alpha, - request.minSimilars, request.top, - request.groupProperty, request.minGroups, - request.maxDegree, request.capacity, - request.limit, request.withIntermediary); + sources, request.direction, request.label, + request.minNeighbors, request.alpha, + request.minSimilars, request.top, + request.groupProperty, request.minGroups, + request.maxDegree, request.capacity, + request.limit, request.withIntermediary); CloseableIterator.closeIterator(sources); - Iterator iterator = QueryResults.emptyIterator(); - if (request.withVertex && !result.isEmpty()) { - iterator = g.vertices(result.vertices().toArray()); + measure.addIterCount(traverser.vertexIterCounter.get(), + traverser.edgeIterCounter.get()); + + Iterator iterVertex; + Set vertexIds = result.vertices(); + if (request.withVertex && !vertexIds.isEmpty()) { + iterVertex = g.vertices(vertexIds.toArray()); + measure.addIterCount(vertexIds.size(), 0); + } else { + iterVertex = vertexIds.iterator(); } - return manager.serializer(g).writeSimilars(result, iterator); + + return manager.serializer(g, measure.measures()) + .writeSimilars(result, iterVertex); } private static class FusiformSimilarityRequest { diff --git a/hugegraph-api/src/main/java/org/apache/hugegraph/api/traversers/JaccardSimilarityAPI.java b/hugegraph-api/src/main/java/org/apache/hugegraph/api/traversers/JaccardSimilarityAPI.java index ff187a5918..d5de80351f 100644 --- a/hugegraph-api/src/main/java/org/apache/hugegraph/api/traversers/JaccardSimilarityAPI.java +++ b/hugegraph-api/src/main/java/org/apache/hugegraph/api/traversers/JaccardSimilarityAPI.java @@ -18,41 +18,40 @@ package org.apache.hugegraph.api.traversers; import static org.apache.hugegraph.traversal.algorithm.HugeTraverser.DEFAULT_CAPACITY; -import static org.apache.hugegraph.traversal.algorithm.HugeTraverser.DEFAULT_MAX_DEGREE; import static org.apache.hugegraph.traversal.algorithm.HugeTraverser.DEFAULT_LIMIT; +import static org.apache.hugegraph.traversal.algorithm.HugeTraverser.DEFAULT_MAX_DEGREE; import java.util.Map; -import io.swagger.v3.oas.annotations.tags.Tag; -import jakarta.inject.Singleton; -import jakarta.ws.rs.Consumes; -import jakarta.ws.rs.DefaultValue; -import jakarta.ws.rs.GET; -import jakarta.ws.rs.POST; -import jakarta.ws.rs.Path; -import jakarta.ws.rs.PathParam; -import jakarta.ws.rs.Produces; -import jakarta.ws.rs.QueryParam; -import jakarta.ws.rs.core.Context; - -import org.apache.hugegraph.core.GraphManager; -import org.slf4j.Logger; - import org.apache.hugegraph.HugeGraph; import org.apache.hugegraph.api.graph.EdgeAPI; import org.apache.hugegraph.api.graph.VertexAPI; import org.apache.hugegraph.backend.id.Id; +import org.apache.hugegraph.core.GraphManager; import org.apache.hugegraph.structure.HugeVertex; -import org.apache.hugegraph.traversal.algorithm.steps.EdgeStep; import org.apache.hugegraph.traversal.algorithm.JaccardSimilarTraverser; +import org.apache.hugegraph.traversal.algorithm.steps.EdgeStep; import org.apache.hugegraph.type.define.Directions; import org.apache.hugegraph.util.E; -import org.apache.hugegraph.util.JsonUtil; import org.apache.hugegraph.util.Log; +import org.slf4j.Logger; + import com.codahale.metrics.annotation.Timed; import com.fasterxml.jackson.annotation.JsonProperty; import com.google.common.collect.ImmutableMap; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.inject.Singleton; +import jakarta.ws.rs.Consumes; +import jakarta.ws.rs.DefaultValue; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.POST; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.PathParam; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.QueryParam; +import jakarta.ws.rs.core.Context; + @Path("graphs/{graph}/traversers/jaccardsimilarity") @Singleton @Tag(name = "JaccardSimilarityAPI") @@ -75,6 +74,8 @@ public String get(@Context GraphManager manager, "with direction {}, edge label {} and max degree '{}'", graph, vertex, other, direction, edgeLabel, maxDegree); + ApiMeasurer measure = new ApiMeasurer(); + Id sourceId = VertexAPI.checkAndParseVertexId(vertex); Id targetId = VertexAPI.checkAndParseVertexId(other); Directions dir = Directions.convert(EdgeAPI.parseDirection(direction)); @@ -82,12 +83,15 @@ public String get(@Context GraphManager manager, HugeGraph g = graph(manager, graph); double similarity; try (JaccardSimilarTraverser traverser = - new JaccardSimilarTraverser(g)) { + new JaccardSimilarTraverser(g)) { similarity = traverser.jaccardSimilarity(sourceId, targetId, dir, edgeLabel, maxDegree); + measure.addIterCount(traverser.vertexIterCounter.get(), + traverser.edgeIterCounter.get()); } - return JsonUtil.toJson(ImmutableMap.of("jaccard_similarity", - similarity)); + + return manager.serializer(g, measure.measures()) + .writeMap(ImmutableMap.of("jaccard_similarity", similarity)); } @POST @@ -110,6 +114,8 @@ public String post(@Context GraphManager manager, graph, request.vertex, request.step, request.top, request.capacity); + ApiMeasurer measure = new ApiMeasurer(); + HugeGraph g = graph(manager, graph); Id sourceId = HugeVertex.getIdValue(request.vertex); @@ -117,11 +123,14 @@ public String post(@Context GraphManager manager, Map results; try (JaccardSimilarTraverser traverser = - new JaccardSimilarTraverser(g)) { + new JaccardSimilarTraverser(g)) { results = traverser.jaccardSimilars(sourceId, step, request.top, request.capacity); + measure.addIterCount(traverser.vertexIterCounter.get(), + traverser.edgeIterCounter.get()); } - return manager.serializer(g).writeMap(results); + return manager.serializer(g, measure.measures()) + .writeMap(ImmutableMap.of("jaccard_similarity", results)); } private static class Request { diff --git a/hugegraph-api/src/main/java/org/apache/hugegraph/api/traversers/KneighborAPI.java b/hugegraph-api/src/main/java/org/apache/hugegraph/api/traversers/KneighborAPI.java index 4a7c0a9515..a0e7d0c4ee 100644 --- a/hugegraph-api/src/main/java/org/apache/hugegraph/api/traversers/KneighborAPI.java +++ b/hugegraph-api/src/main/java/org/apache/hugegraph/api/traversers/KneighborAPI.java @@ -21,6 +21,7 @@ import static org.apache.hugegraph.traversal.algorithm.HugeTraverser.DEFAULT_MAX_DEGREE; import static org.apache.hugegraph.traversal.algorithm.HugeTraverser.NO_LIMIT; +import java.util.Collections; import java.util.HashSet; import java.util.Iterator; import java.util.List; @@ -40,12 +41,13 @@ import org.apache.hugegraph.type.define.Directions; import org.apache.hugegraph.util.E; import org.apache.hugegraph.util.Log; -import org.apache.tinkerpop.gremlin.structure.Vertex; +import org.apache.tinkerpop.gremlin.structure.Edge; import org.slf4j.Logger; import com.codahale.metrics.annotation.Timed; import com.fasterxml.jackson.annotation.JsonProperty; import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.inject.Singleton; @@ -75,6 +77,8 @@ public String get(@Context GraphManager manager, @QueryParam("direction") String direction, @QueryParam("label") String edgeLabel, @QueryParam("max_depth") int depth, + @QueryParam("count_only") + @DefaultValue("false") boolean countOnly, @QueryParam("max_degree") @DefaultValue(DEFAULT_MAX_DEGREE) long maxDegree, @QueryParam("limit") @@ -85,6 +89,8 @@ public String get(@Context GraphManager manager, graph, sourceV, direction, edgeLabel, depth, maxDegree, limit); + ApiMeasurer measure = new ApiMeasurer(); + Id source = VertexAPI.checkAndParseVertexId(sourceV); Directions dir = Directions.convert(EdgeAPI.parseDirection(direction)); @@ -94,8 +100,14 @@ public String get(@Context GraphManager manager, try (KneighborTraverser traverser = new KneighborTraverser(g)) { ids = traverser.kneighbor(source, dir, edgeLabel, depth, maxDegree, limit); + measure.addIterCount(traverser.vertexIterCounter.get(), + traverser.edgeIterCounter.get()); + } + if (countOnly) { + return manager.serializer(g, measure.measures()) + .writeMap(ImmutableMap.of("vertices_size", ids.size())); } - return manager.serializer(g).writeList("vertices", ids); + return manager.serializer(g, measure.measures()).writeList("vertices", ids); } @POST @@ -111,15 +123,18 @@ public String post(@Context GraphManager manager, E.checkArgument(request.step != null, "The steps of request can't be null"); if (request.countOnly) { - E.checkArgument(!request.withVertex && !request.withPath, - "Can't return vertex or path when count only"); + E.checkArgument(!request.withVertex && !request.withPath && !request.withEdge, + "Can't return vertex, edge or path when count only"); } LOG.debug("Graph [{}] get customized kneighbor from source vertex " + "'{}', with step '{}', limit '{}', count_only '{}', " + - "with_vertex '{}' and with_path '{}'", + "with_vertex '{}', with_path '{}' and with_edge '{}'", graph, request.source, request.step, request.limit, - request.countOnly, request.withVertex, request.withPath); + request.countOnly, request.withVertex, request.withPath, + request.withEdge); + + ApiMeasurer measure = new ApiMeasurer(); HugeGraph g = graph(manager, graph); Id sourceId = HugeVertex.getIdValue(request.source); @@ -131,6 +146,8 @@ public String post(@Context GraphManager manager, results = traverser.customizedKneighbor(sourceId, step, request.maxDepth, request.limit); + measure.addIterCount(traverser.vertexIterCounter.get(), + traverser.edgeIterCounter.get()); } long size = results.size(); @@ -144,20 +161,41 @@ public String post(@Context GraphManager manager, if (request.withPath) { paths.addAll(results.paths(request.limit)); } - Iterator iter = QueryResults.emptyIterator(); - if (request.withVertex && !request.countOnly) { - Set ids = new HashSet<>(neighbors); - if (request.withPath) { - for (HugeTraverser.Path p : paths) { - ids.addAll(p.vertices()); - } + + if (request.countOnly) { + return manager.serializer(g, measure.measures()) + .writeNodesWithPath("kneighbor", neighbors, size, paths, + QueryResults.emptyIterator(), + QueryResults.emptyIterator()); + } + + Iterator iterVertex; + Set vertexIds = new HashSet<>(neighbors); + if (request.withPath) { + for (HugeTraverser.Path p : paths) { + vertexIds.addAll(p.vertices()); } - if (!ids.isEmpty()) { - iter = g.vertices(ids.toArray()); + } + if (request.withVertex && !vertexIds.isEmpty()) { + iterVertex = g.vertices(vertexIds.toArray()); + measure.addIterCount(vertexIds.size(), 0L); + } else { + iterVertex = vertexIds.iterator(); + } + + Iterator iterEdge = Collections.emptyIterator(); + if (request.withPath) { + Set edges = results.edgeResults().getEdges(paths); + if (request.withEdge) { + iterEdge = edges.iterator(); + } else { + iterEdge = HugeTraverser.EdgeRecord.getEdgeIds(edges).iterator(); } } - return manager.serializer(g).writeNodesWithPath("kneighbor", neighbors, - size, paths, iter); + + return manager.serializer(g, measure.measures()) + .writeNodesWithPath("kneighbor", neighbors, + size, paths, iterVertex, iterEdge); } private static class Request { @@ -176,14 +214,16 @@ private static class Request { public boolean withVertex = false; @JsonProperty("with_path") public boolean withPath = false; + @JsonProperty("with_edge") + public boolean withEdge = false; @Override public String toString() { return String.format("PathRequest{source=%s,step=%s,maxDepth=%s" + "limit=%s,countOnly=%s,withVertex=%s," + - "withPath=%s}", this.source, this.step, + "withPath=%s,withEdge=%s}", this.source, this.step, this.maxDepth, this.limit, this.countOnly, - this.withVertex, this.withPath); + this.withVertex, this.withPath, this.withEdge); } } } diff --git a/hugegraph-api/src/main/java/org/apache/hugegraph/api/traversers/KoutAPI.java b/hugegraph-api/src/main/java/org/apache/hugegraph/api/traversers/KoutAPI.java index 30282be9d6..1adf2be5eb 100644 --- a/hugegraph-api/src/main/java/org/apache/hugegraph/api/traversers/KoutAPI.java +++ b/hugegraph-api/src/main/java/org/apache/hugegraph/api/traversers/KoutAPI.java @@ -22,6 +22,7 @@ import static org.apache.hugegraph.traversal.algorithm.HugeTraverser.DEFAULT_MAX_DEGREE; import static org.apache.hugegraph.traversal.algorithm.HugeTraverser.NO_LIMIT; +import java.util.Collections; import java.util.HashSet; import java.util.Iterator; import java.util.List; @@ -41,12 +42,13 @@ import org.apache.hugegraph.type.define.Directions; import org.apache.hugegraph.util.E; import org.apache.hugegraph.util.Log; -import org.apache.tinkerpop.gremlin.structure.Vertex; +import org.apache.tinkerpop.gremlin.structure.Edge; import org.slf4j.Logger; import com.codahale.metrics.annotation.Timed; import com.fasterxml.jackson.annotation.JsonProperty; import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.inject.Singleton; @@ -78,6 +80,8 @@ public String get(@Context GraphManager manager, @QueryParam("max_depth") int depth, @QueryParam("nearest") @DefaultValue("true") boolean nearest, + @QueryParam("count_only") + @DefaultValue("false") boolean count_only, @QueryParam("max_degree") @DefaultValue(DEFAULT_MAX_DEGREE) long maxDegree, @QueryParam("capacity") @@ -87,8 +91,10 @@ public String get(@Context GraphManager manager, LOG.debug("Graph [{}] get k-out from '{}' with " + "direction '{}', edge label '{}', max depth '{}', nearest " + "'{}', max degree '{}', capacity '{}' and limit '{}'", - graph, source, direction, edgeLabel, depth, nearest, - maxDegree, capacity, limit); + graph, source, direction, edgeLabel, depth, + nearest, maxDegree, capacity, limit); + + ApiMeasurer measure = new ApiMeasurer(); Id sourceId = VertexAPI.checkAndParseVertexId(source); Directions dir = Directions.convert(EdgeAPI.parseDirection(direction)); @@ -99,8 +105,15 @@ public String get(@Context GraphManager manager, try (KoutTraverser traverser = new KoutTraverser(g)) { ids = traverser.kout(sourceId, dir, edgeLabel, depth, nearest, maxDegree, capacity, limit); + measure.addIterCount(traverser.vertexIterCounter.get(), + traverser.edgeIterCounter.get()); + } + + if (count_only) { + return manager.serializer(g, measure.measures()) + .writeMap(ImmutableMap.of("vertices_size", ids.size())); } - return manager.serializer(g).writeList("vertices", ids); + return manager.serializer(g, measure.measures()).writeList("vertices", ids); } @POST @@ -116,23 +129,25 @@ public String post(@Context GraphManager manager, E.checkArgument(request.step != null, "The steps of request can't be null"); if (request.countOnly) { - E.checkArgument(!request.withVertex && !request.withPath, - "Can't return vertex or path when count only"); + E.checkArgument(!request.withVertex && !request.withPath && !request.withEdge, + "Can't return vertex, edge or path when count only"); } LOG.debug("Graph [{}] get customized kout from source vertex '{}', " + "with step '{}', max_depth '{}', nearest '{}', " + "count_only '{}', capacity '{}', limit '{}', " + - "with_vertex '{}' and with_path '{}'", + "with_vertex '{}', with_path '{}' and with_edge '{}'", graph, request.source, request.step, request.maxDepth, request.nearest, request.countOnly, request.capacity, - request.limit, request.withVertex, request.withPath); + request.limit, request.withVertex, request.withPath, + request.withEdge); + + ApiMeasurer measure = new ApiMeasurer(); HugeGraph g = graph(manager, graph); Id sourceId = HugeVertex.getIdValue(request.source); EdgeStep step = step(g, request.step); - KoutRecords results; try (KoutTraverser traverser = new KoutTraverser(g)) { results = traverser.customizedKout(sourceId, step, @@ -140,8 +155,9 @@ public String post(@Context GraphManager manager, request.nearest, request.capacity, request.limit); + measure.addIterCount(traverser.vertexIterCounter.get(), + traverser.edgeIterCounter.get()); } - long size = results.size(); if (request.limit != NO_LIMIT && size > request.limit) { size = request.limit; @@ -154,20 +170,40 @@ public String post(@Context GraphManager manager, paths.addAll(results.paths(request.limit)); } - Iterator iter = QueryResults.emptyIterator(); - if (request.withVertex && !request.countOnly) { - Set ids = new HashSet<>(neighbors); - if (request.withPath) { - for (HugeTraverser.Path p : paths) { - ids.addAll(p.vertices()); - } + if (request.countOnly) { + return manager.serializer(g, measure.measures()) + .writeNodesWithPath("kneighbor", neighbors, size, paths, + QueryResults.emptyIterator(), + QueryResults.emptyIterator()); + } + + Iterator iterVertex; + Set vertexIds = new HashSet<>(neighbors); + if (request.withPath) { + for (HugeTraverser.Path p : results.paths(request.limit)) { + vertexIds.addAll(p.vertices()); } - if (!ids.isEmpty()) { - iter = g.vertices(ids.toArray()); + } + if (request.withVertex && !vertexIds.isEmpty()) { + iterVertex = g.vertices(vertexIds.toArray()); + measure.addIterCount(vertexIds.size(), 0L); + } else { + iterVertex = vertexIds.iterator(); + } + + Iterator iterEdge = Collections.emptyIterator(); + if (request.withPath) { + Set edges = results.edgeResults().getEdges(paths); + if (request.withEdge) { + iterEdge = edges.iterator(); + } else { + iterEdge = HugeTraverser.EdgeRecord.getEdgeIds(edges).iterator(); } } - return manager.serializer(g).writeNodesWithPath("kout", neighbors, - size, paths, iter); + + return manager.serializer(g, measure.measures()) + .writeNodesWithPath("kout", neighbors, size, paths, + iterVertex, iterEdge); } private static class Request { @@ -190,15 +226,18 @@ private static class Request { public boolean withVertex = false; @JsonProperty("with_path") public boolean withPath = false; + @JsonProperty("with_edge") + public boolean withEdge = false; @Override public String toString() { return String.format("KoutRequest{source=%s,step=%s,maxDepth=%s" + "nearest=%s,countOnly=%s,capacity=%s," + - "limit=%s,withVertex=%s,withPath=%s}", - this.source, this.step, this.maxDepth, - this.nearest, this.countOnly, this.capacity, - this.limit, this.withVertex, this.withPath); + "limit=%s,withVertex=%s,withPath=%s," + + "withEdge=%s}", this.source, this.step, + this.maxDepth, this.nearest, this.countOnly, + this.capacity, this.limit, this.withVertex, + this.withPath, this.withEdge); } } } diff --git a/hugegraph-api/src/main/java/org/apache/hugegraph/api/traversers/MultiNodeShortestPathAPI.java b/hugegraph-api/src/main/java/org/apache/hugegraph/api/traversers/MultiNodeShortestPathAPI.java index 81c38e65c9..588940abb7 100644 --- a/hugegraph-api/src/main/java/org/apache/hugegraph/api/traversers/MultiNodeShortestPathAPI.java +++ b/hugegraph-api/src/main/java/org/apache/hugegraph/api/traversers/MultiNodeShortestPathAPI.java @@ -24,30 +24,30 @@ import java.util.List; import java.util.Set; -import io.swagger.v3.oas.annotations.tags.Tag; -import jakarta.inject.Singleton; -import jakarta.ws.rs.Consumes; -import jakarta.ws.rs.POST; -import jakarta.ws.rs.Path; -import jakarta.ws.rs.PathParam; -import jakarta.ws.rs.Produces; -import jakarta.ws.rs.core.Context; - -import org.apache.tinkerpop.gremlin.structure.Vertex; -import org.apache.hugegraph.core.GraphManager; -import org.slf4j.Logger; - import org.apache.hugegraph.HugeGraph; import org.apache.hugegraph.backend.id.Id; -import org.apache.hugegraph.backend.query.QueryResults; -import org.apache.hugegraph.traversal.algorithm.steps.EdgeStep; +import org.apache.hugegraph.core.GraphManager; import org.apache.hugegraph.traversal.algorithm.HugeTraverser; import org.apache.hugegraph.traversal.algorithm.MultiNodeShortestPathTraverser; +import org.apache.hugegraph.traversal.algorithm.steps.EdgeStep; import org.apache.hugegraph.util.E; import org.apache.hugegraph.util.Log; +import org.apache.tinkerpop.gremlin.structure.Edge; +import org.apache.tinkerpop.gremlin.structure.Vertex; +import org.slf4j.Logger; + import com.codahale.metrics.annotation.Timed; import com.fasterxml.jackson.annotation.JsonProperty; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.inject.Singleton; +import jakarta.ws.rs.Consumes; +import jakarta.ws.rs.POST; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.PathParam; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.core.Context; + @Path("graphs/{graph}/traversers/multinodeshortestpath") @Singleton @Tag(name = "MultiNodeShortestPathAPI") @@ -74,32 +74,48 @@ public String post(@Context GraphManager manager, graph, request.vertices, request.step, request.maxDepth, request.capacity, request.withVertex); + ApiMeasurer measure = new ApiMeasurer(); + HugeGraph g = graph(manager, graph); Iterator vertices = request.vertices.vertices(g); EdgeStep step = step(g, request.step); - List paths; + MultiNodeShortestPathTraverser.WrappedListPath wrappedListPath; try (MultiNodeShortestPathTraverser traverser = - new MultiNodeShortestPathTraverser(g)) { - paths = traverser.multiNodeShortestPath(vertices, step, - request.maxDepth, - request.capacity); + new MultiNodeShortestPathTraverser(g)) { + wrappedListPath = traverser.multiNodeShortestPath(vertices, step, + request.maxDepth, + request.capacity); + measure.addIterCount(traverser.vertexIterCounter.get(), + traverser.edgeIterCounter.get()); } - if (!request.withVertex) { - return manager.serializer(g).writePaths("paths", paths, false); - } + List paths = wrappedListPath.paths(); - Set ids = new HashSet<>(); - for (HugeTraverser.Path p : paths) { - ids.addAll(p.vertices()); + Iterator iterVertex; + Set vertexIds = new HashSet<>(); + for (HugeTraverser.Path path : paths) { + vertexIds.addAll(path.vertices()); + } + if (request.withVertex && !vertexIds.isEmpty()) { + iterVertex = g.vertices(vertexIds.toArray()); + measure.addIterCount(vertexIds.size(), 0L); + } else { + iterVertex = vertexIds.iterator(); } - Iterator iter = QueryResults.emptyIterator(); - if (!ids.isEmpty()) { - iter = g.vertices(ids.toArray()); + + Iterator iterEdge; + Set edges = wrappedListPath.edges(); + if (request.withEdge && !edges.isEmpty()) { + iterEdge = wrappedListPath.edges().iterator(); + } else { + iterEdge = HugeTraverser.EdgeRecord.getEdgeIds(edges).iterator(); } - return manager.serializer(g).writePaths("paths", paths, false, iter); + + return manager.serializer(g, measure.measures()) + .writePaths("paths", paths, + false, iterVertex, iterEdge); } private static class Request { @@ -114,13 +130,15 @@ private static class Request { public long capacity = Long.parseLong(DEFAULT_CAPACITY); @JsonProperty("with_vertex") public boolean withVertex = false; + @JsonProperty("with_edge") + public boolean withEdge = false; @Override public String toString() { return String.format("Request{vertices=%s,step=%s,maxDepth=%s" + - "capacity=%s,withVertex=%s}", + "capacity=%s,withVertex=%s,withEdge=%s}", this.vertices, this.step, this.maxDepth, - this.capacity, this.withVertex); + this.capacity, this.withVertex, this.withEdge); } } } diff --git a/hugegraph-api/src/main/java/org/apache/hugegraph/api/traversers/PathsAPI.java b/hugegraph-api/src/main/java/org/apache/hugegraph/api/traversers/PathsAPI.java index 6e18c9a1c2..50bca7f75b 100644 --- a/hugegraph-api/src/main/java/org/apache/hugegraph/api/traversers/PathsAPI.java +++ b/hugegraph-api/src/main/java/org/apache/hugegraph/api/traversers/PathsAPI.java @@ -27,27 +27,11 @@ import java.util.Iterator; import java.util.Set; -import io.swagger.v3.oas.annotations.tags.Tag; -import jakarta.inject.Singleton; -import jakarta.ws.rs.Consumes; -import jakarta.ws.rs.DefaultValue; -import jakarta.ws.rs.GET; -import jakarta.ws.rs.POST; -import jakarta.ws.rs.Path; -import jakarta.ws.rs.PathParam; -import jakarta.ws.rs.Produces; -import jakarta.ws.rs.QueryParam; -import jakarta.ws.rs.core.Context; - -import org.apache.tinkerpop.gremlin.structure.Vertex; -import org.apache.hugegraph.core.GraphManager; -import org.slf4j.Logger; - import org.apache.hugegraph.HugeGraph; import org.apache.hugegraph.api.graph.EdgeAPI; import org.apache.hugegraph.api.graph.VertexAPI; import org.apache.hugegraph.backend.id.Id; -import org.apache.hugegraph.backend.query.QueryResults; +import org.apache.hugegraph.core.GraphManager; import org.apache.hugegraph.traversal.algorithm.CollectionPathsTraverser; import org.apache.hugegraph.traversal.algorithm.HugeTraverser; import org.apache.hugegraph.traversal.algorithm.PathsTraverser; @@ -55,9 +39,25 @@ import org.apache.hugegraph.type.define.Directions; import org.apache.hugegraph.util.E; import org.apache.hugegraph.util.Log; +import org.apache.tinkerpop.gremlin.structure.Edge; +import org.apache.tinkerpop.gremlin.structure.Vertex; +import org.slf4j.Logger; + import com.codahale.metrics.annotation.Timed; import com.fasterxml.jackson.annotation.JsonProperty; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.inject.Singleton; +import jakarta.ws.rs.Consumes; +import jakarta.ws.rs.DefaultValue; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.POST; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.PathParam; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.QueryParam; +import jakarta.ws.rs.core.Context; + @Path("graphs/{graph}/traversers/paths") @Singleton @Tag(name = "PathsAPI") @@ -87,6 +87,8 @@ public String get(@Context GraphManager manager, graph, source, target, direction, edgeLabel, depth, maxDegree, capacity, limit); + ApiMeasurer measure = new ApiMeasurer(); + Id sourceId = VertexAPI.checkAndParseVertexId(source); Id targetId = VertexAPI.checkAndParseVertexId(target); Directions dir = Directions.convert(EdgeAPI.parseDirection(direction)); @@ -97,7 +99,10 @@ public String get(@Context GraphManager manager, dir.opposite(), edgeLabel, depth, maxDegree, capacity, limit); - return manager.serializer(g).writePaths("paths", paths, false); + measure.addIterCount(traverser.vertexIterCounter.get(), + traverser.edgeIterCounter.get()); + return manager.serializer(g, measure.measures()) + .writePaths("paths", paths, false); } @POST @@ -120,10 +125,12 @@ public String post(@Context GraphManager manager, LOG.debug("Graph [{}] get paths from source vertices '{}', target " + "vertices '{}', with step '{}', max depth '{}', " + - "capacity '{}', limit '{}' and with_vertex '{}'", + "capacity '{}', limit '{}', with_vertex '{}' and with_edge '{}'", graph, request.sources, request.targets, request.step, request.depth, request.capacity, request.limit, - request.withVertex); + request.withVertex, request.withEdge); + + ApiMeasurer measure = new ApiMeasurer(); HugeGraph g = graph(manager, graph); Iterator sources = request.sources.vertices(g); @@ -131,24 +138,38 @@ public String post(@Context GraphManager manager, EdgeStep step = step(g, request.step); CollectionPathsTraverser traverser = new CollectionPathsTraverser(g); - Collection paths; - paths = traverser.paths(sources, targets, step, request.depth, - request.nearest, request.capacity, - request.limit); - - if (!request.withVertex) { - return manager.serializer(g).writePaths("paths", paths, false); + CollectionPathsTraverser.WrappedPathCollection + wrappedPathCollection = traverser.paths(sources, targets, + step, request.depth, + request.nearest, request.capacity, + request.limit); + Collection paths = wrappedPathCollection.paths(); + measure.addIterCount(traverser.vertexIterCounter.get(), + traverser.edgeIterCounter.get()); + + Iterator iterVertex; + Set vertexIds = new HashSet<>(); + for (HugeTraverser.Path path : paths) { + vertexIds.addAll(path.vertices()); } - - Set ids = new HashSet<>(); - for (HugeTraverser.Path p : paths) { - ids.addAll(p.vertices()); + if (request.withVertex && !vertexIds.isEmpty()) { + iterVertex = g.vertices(vertexIds.toArray()); + measure.addIterCount(vertexIds.size(), 0L); + } else { + iterVertex = vertexIds.iterator(); } - Iterator iter = QueryResults.emptyIterator(); - if (!ids.isEmpty()) { - iter = g.vertices(ids.toArray()); + + Iterator iterEdge; + Set edges = wrappedPathCollection.edges(); + if (request.withEdge && !edges.isEmpty()) { + iterEdge = edges.iterator(); + } else { + iterEdge = HugeTraverser.EdgeRecord.getEdgeIds(edges).iterator(); } - return manager.serializer(g).writePaths("paths", paths, false, iter); + + return manager.serializer(g, measure.measures()) + .writePaths("paths", paths, false, + iterVertex, iterEdge); } private static class Request { @@ -170,14 +191,17 @@ private static class Request { @JsonProperty("with_vertex") public boolean withVertex = false; + @JsonProperty("with_edge") + public boolean withEdge = false; + @Override public String toString() { return String.format("PathRequest{sources=%s,targets=%s,step=%s," + "maxDepth=%s,nearest=%s,capacity=%s," + - "limit=%s,withVertex=%s}", this.sources, - this.targets, this.step, this.depth, - this.nearest, this.capacity, - this.limit, this.withVertex); + "limit=%s,withVertex=%s,withEdge=%s}", + this.sources, this.targets, this.step, + this.depth, this.nearest, this.capacity, + this.limit, this.withVertex, this.withEdge); } } } diff --git a/hugegraph-api/src/main/java/org/apache/hugegraph/api/traversers/RaysAPI.java b/hugegraph-api/src/main/java/org/apache/hugegraph/api/traversers/RaysAPI.java index c841412cae..28ded20e60 100644 --- a/hugegraph-api/src/main/java/org/apache/hugegraph/api/traversers/RaysAPI.java +++ b/hugegraph-api/src/main/java/org/apache/hugegraph/api/traversers/RaysAPI.java @@ -21,30 +21,35 @@ import static org.apache.hugegraph.traversal.algorithm.HugeTraverser.DEFAULT_MAX_DEGREE; import static org.apache.hugegraph.traversal.algorithm.HugeTraverser.DEFAULT_PATHS_LIMIT; -import io.swagger.v3.oas.annotations.tags.Tag; -import jakarta.inject.Singleton; -import jakarta.ws.rs.DefaultValue; -import jakarta.ws.rs.GET; -import jakarta.ws.rs.Path; -import jakarta.ws.rs.PathParam; -import jakarta.ws.rs.Produces; -import jakarta.ws.rs.QueryParam; -import jakarta.ws.rs.core.Context; - -import org.apache.hugegraph.core.GraphManager; -import org.slf4j.Logger; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Set; import org.apache.hugegraph.HugeGraph; import org.apache.hugegraph.api.API; import org.apache.hugegraph.api.graph.EdgeAPI; import org.apache.hugegraph.api.graph.VertexAPI; import org.apache.hugegraph.backend.id.Id; +import org.apache.hugegraph.core.GraphManager; import org.apache.hugegraph.traversal.algorithm.HugeTraverser; import org.apache.hugegraph.traversal.algorithm.SubGraphTraverser; import org.apache.hugegraph.type.define.Directions; import org.apache.hugegraph.util.Log; +import org.apache.tinkerpop.gremlin.structure.Edge; +import org.slf4j.Logger; + import com.codahale.metrics.annotation.Timed; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.inject.Singleton; +import jakarta.ws.rs.DefaultValue; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.PathParam; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.QueryParam; +import jakarta.ws.rs.core.Context; + @Path("graphs/{graph}/traversers/rays") @Singleton @Tag(name = "RaysAPI") @@ -66,12 +71,17 @@ public String get(@Context GraphManager manager, @QueryParam("capacity") @DefaultValue(DEFAULT_CAPACITY) long capacity, @QueryParam("limit") - @DefaultValue(DEFAULT_PATHS_LIMIT) int limit) { + @DefaultValue(DEFAULT_PATHS_LIMIT) int limit, + @QueryParam("with_vertex") + @DefaultValue("false") boolean withVertex, + @QueryParam("with_edge") + @DefaultValue("false") boolean withEdge) { LOG.debug("Graph [{}] get rays paths from '{}' with " + "direction '{}', edge label '{}', max depth '{}', " + "max degree '{}', capacity '{}' and limit '{}'", graph, sourceV, direction, edgeLabel, depth, maxDegree, capacity, limit); + ApiMeasurer measure = new ApiMeasurer(); Id source = VertexAPI.checkAndParseVertexId(sourceV); Directions dir = Directions.convert(EdgeAPI.parseDirection(direction)); @@ -80,8 +90,33 @@ public String get(@Context GraphManager manager, SubGraphTraverser traverser = new SubGraphTraverser(g); HugeTraverser.PathSet paths = traverser.rays(source, dir, edgeLabel, - depth, maxDegree, - capacity, limit); - return manager.serializer(g).writePaths("rays", paths, false); + depth, maxDegree, capacity, + limit); + measure.addIterCount(traverser.vertexIterCounter.get(), + traverser.edgeIterCounter.get()); + + Iterator iterVertex; + Set vertexIds = new HashSet<>(); + for (HugeTraverser.Path path : paths) { + vertexIds.addAll(path.vertices()); + } + if (withVertex && !vertexIds.isEmpty()) { + iterVertex = g.vertices(vertexIds.toArray()); + measure.addIterCount(vertexIds.size(), 0L); + } else { + iterVertex = vertexIds.iterator(); + } + + Iterator iterEdge; + Set edges = paths.getEdges(); + if (withEdge && !edges.isEmpty()) { + iterEdge = edges.iterator(); + } else { + iterEdge = HugeTraverser.EdgeRecord.getEdgeIds(edges).iterator(); + } + + return manager.serializer(g, measure.measures()) + .writePaths("rays", paths, false, + iterVertex, iterEdge); } } diff --git a/hugegraph-api/src/main/java/org/apache/hugegraph/api/traversers/RingsAPI.java b/hugegraph-api/src/main/java/org/apache/hugegraph/api/traversers/RingsAPI.java index 67dfe7ab72..3a44fd85a1 100644 --- a/hugegraph-api/src/main/java/org/apache/hugegraph/api/traversers/RingsAPI.java +++ b/hugegraph-api/src/main/java/org/apache/hugegraph/api/traversers/RingsAPI.java @@ -21,30 +21,35 @@ import static org.apache.hugegraph.traversal.algorithm.HugeTraverser.DEFAULT_MAX_DEGREE; import static org.apache.hugegraph.traversal.algorithm.HugeTraverser.DEFAULT_PATHS_LIMIT; -import io.swagger.v3.oas.annotations.tags.Tag; -import jakarta.inject.Singleton; -import jakarta.ws.rs.DefaultValue; -import jakarta.ws.rs.GET; -import jakarta.ws.rs.Path; -import jakarta.ws.rs.PathParam; -import jakarta.ws.rs.Produces; -import jakarta.ws.rs.QueryParam; -import jakarta.ws.rs.core.Context; - -import org.apache.hugegraph.core.GraphManager; -import org.slf4j.Logger; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Set; import org.apache.hugegraph.HugeGraph; import org.apache.hugegraph.api.API; import org.apache.hugegraph.api.graph.EdgeAPI; import org.apache.hugegraph.api.graph.VertexAPI; import org.apache.hugegraph.backend.id.Id; +import org.apache.hugegraph.core.GraphManager; import org.apache.hugegraph.traversal.algorithm.HugeTraverser; import org.apache.hugegraph.traversal.algorithm.SubGraphTraverser; import org.apache.hugegraph.type.define.Directions; import org.apache.hugegraph.util.Log; +import org.apache.tinkerpop.gremlin.structure.Edge; +import org.slf4j.Logger; + import com.codahale.metrics.annotation.Timed; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.inject.Singleton; +import jakarta.ws.rs.DefaultValue; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.PathParam; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.QueryParam; +import jakarta.ws.rs.core.Context; + @Path("graphs/{graph}/traversers/rings") @Singleton @Tag(name = "RingsAPI") @@ -68,14 +73,19 @@ public String get(@Context GraphManager manager, @QueryParam("capacity") @DefaultValue(DEFAULT_CAPACITY) long capacity, @QueryParam("limit") - @DefaultValue(DEFAULT_PATHS_LIMIT) int limit) { + @DefaultValue(DEFAULT_PATHS_LIMIT) int limit, + @QueryParam("with_vertex") + @DefaultValue("false") boolean withVertex, + @QueryParam("with_edge") + @DefaultValue("false") boolean withEdge) { LOG.debug("Graph [{}] get rings paths reachable from '{}' with " + "direction '{}', edge label '{}', max depth '{}', " + - "source in ring '{}', max degree '{}', capacity '{}' " + - "and limit '{}'", + "source in ring '{}', max degree '{}', capacity '{}', " + + "limit '{}', with_vertex '{}' and with_edge '{}'", graph, sourceV, direction, edgeLabel, depth, sourceInRing, - maxDegree, capacity, limit); + maxDegree, capacity, limit, withVertex, withEdge); + ApiMeasurer measure = new ApiMeasurer(); Id source = VertexAPI.checkAndParseVertexId(sourceV); Directions dir = Directions.convert(EdgeAPI.parseDirection(direction)); @@ -83,8 +93,34 @@ public String get(@Context GraphManager manager, SubGraphTraverser traverser = new SubGraphTraverser(g); HugeTraverser.PathSet paths = traverser.rings(source, dir, edgeLabel, - depth, sourceInRing, - maxDegree, capacity, limit); - return manager.serializer(g).writePaths("rings", paths, false); + depth, sourceInRing, maxDegree, + capacity, limit); + + measure.addIterCount(traverser.vertexIterCounter.get(), + traverser.edgeIterCounter.get()); + + Iterator iterVertex; + Set vertexIds = new HashSet<>(); + for (HugeTraverser.Path path : paths) { + vertexIds.addAll(path.vertices()); + } + if (withVertex && !vertexIds.isEmpty()) { + iterVertex = g.vertices(vertexIds.toArray()); + measure.addIterCount(vertexIds.size(), 0L); + } else { + iterVertex = vertexIds.iterator(); + } + + Iterator iterEdge; + Set edges = paths.getEdges(); + if (withEdge && !edges.isEmpty()) { + iterEdge = edges.iterator(); + } else { + iterEdge = HugeTraverser.EdgeRecord.getEdgeIds(edges).iterator(); + } + + return manager.serializer(g, measure.measures()) + .writePaths("rings", paths, false, + iterVertex, iterEdge); } } diff --git a/hugegraph-api/src/main/java/org/apache/hugegraph/api/traversers/SameNeighborsAPI.java b/hugegraph-api/src/main/java/org/apache/hugegraph/api/traversers/SameNeighborsAPI.java index a7a1770fd0..489ca08054 100644 --- a/hugegraph-api/src/main/java/org/apache/hugegraph/api/traversers/SameNeighborsAPI.java +++ b/hugegraph-api/src/main/java/org/apache/hugegraph/api/traversers/SameNeighborsAPI.java @@ -20,30 +20,39 @@ import static org.apache.hugegraph.traversal.algorithm.HugeTraverser.DEFAULT_ELEMENTS_LIMIT; import static org.apache.hugegraph.traversal.algorithm.HugeTraverser.DEFAULT_MAX_DEGREE; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; import java.util.Set; -import io.swagger.v3.oas.annotations.tags.Tag; -import jakarta.inject.Singleton; -import jakarta.ws.rs.DefaultValue; -import jakarta.ws.rs.GET; -import jakarta.ws.rs.Path; -import jakarta.ws.rs.PathParam; -import jakarta.ws.rs.Produces; -import jakarta.ws.rs.QueryParam; -import jakarta.ws.rs.core.Context; - -import org.slf4j.Logger; - import org.apache.hugegraph.HugeGraph; import org.apache.hugegraph.api.API; import org.apache.hugegraph.api.graph.EdgeAPI; import org.apache.hugegraph.api.graph.VertexAPI; import org.apache.hugegraph.backend.id.Id; import org.apache.hugegraph.core.GraphManager; +import org.apache.hugegraph.structure.HugeVertex; import org.apache.hugegraph.traversal.algorithm.SameNeighborTraverser; import org.apache.hugegraph.type.define.Directions; +import org.apache.hugegraph.util.E; import org.apache.hugegraph.util.Log; +import org.slf4j.Logger; + import com.codahale.metrics.annotation.Timed; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.google.common.collect.ImmutableMap; + +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.inject.Singleton; +import jakarta.ws.rs.DefaultValue; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.POST; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.PathParam; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.QueryParam; +import jakarta.ws.rs.core.Context; @Path("graphs/{graph}/traversers/sameneighbors") @Singleton @@ -69,6 +78,8 @@ public String get(@Context GraphManager manager, "direction {}, edge label {}, max degree '{}' and limit '{}'", graph, vertex, other, direction, edgeLabel, maxDegree, limit); + ApiMeasurer measure = new ApiMeasurer(); + Id sourceId = VertexAPI.checkAndParseVertexId(vertex); Id targetId = VertexAPI.checkAndParseVertexId(other); Directions dir = Directions.convert(EdgeAPI.parseDirection(direction)); @@ -77,6 +88,77 @@ public String get(@Context GraphManager manager, SameNeighborTraverser traverser = new SameNeighborTraverser(g); Set neighbors = traverser.sameNeighbors(sourceId, targetId, dir, edgeLabel, maxDegree, limit); - return manager.serializer(g).writeList("same_neighbors", neighbors); + + measure.addIterCount(traverser.vertexIterCounter.get(), + traverser.edgeIterCounter.get()); + + return manager.serializer(g, measure.measures()) + .writeList("same_neighbors", neighbors); + } + + @POST + @Timed + @Produces(APPLICATION_JSON_WITH_CHARSET) + public String sameNeighbors(@Context GraphManager manager, + @PathParam("graph") String graph, + Request request) { + LOG.debug("Graph [{}] get same neighbors among batch, '{}'", graph, request.toString()); + + ApiMeasurer measure = new ApiMeasurer(); + + Directions dir = Directions.convert(EdgeAPI.parseDirection(request.direction)); + HugeGraph g = graph(manager, graph); + SameNeighborTraverser traverser = new SameNeighborTraverser(g); + + List vertexList = request.vertexList; + E.checkArgument(vertexList.size() >= 2, "vertex_list size can't " + + "be less than 2"); + + List vertexIds = new ArrayList<>(); + for (Object obj : vertexList) { + vertexIds.add(HugeVertex.getIdValue(obj)); + } + + Set neighbors = traverser.sameNeighbors(vertexIds, dir, request.labels, + request.maxDegree, request.limit); + measure.addIterCount(traverser.vertexIterCounter.get(), + traverser.edgeIterCounter.get()); + + Iterator iterVertex; + Set ids = new HashSet<>(neighbors); + ids.addAll(vertexIds); + if (request.withVertex && !ids.isEmpty()) { + iterVertex = g.vertices(ids.toArray()); + } else { + iterVertex = ids.iterator(); + } + return manager.serializer(g, measure.measures()) + .writeMap(ImmutableMap.of("same_neighbors", neighbors, + "vertices", iterVertex)); + } + + private static class Request { + + @JsonProperty("max_degree") + public long maxDegree = Long.parseLong(DEFAULT_MAX_DEGREE); + @JsonProperty("limit") + public int limit = Integer.parseInt(DEFAULT_ELEMENTS_LIMIT); + @JsonProperty("vertex_list") + private List vertexList; + @JsonProperty("direction") + private String direction; + @JsonProperty("labels") + private List labels; + @JsonProperty("with_vertex") + private boolean withVertex = false; + + @Override + public String toString() { + return String.format("SameNeighborsBatchRequest{vertex_list=%s," + + "direction=%s,label=%s,max_degree=%d," + + "limit=%d,with_vertex=%s", + this.vertexList, this.direction, this.labels, + this.maxDegree, this.limit, this.withVertex); + } } } diff --git a/hugegraph-api/src/main/java/org/apache/hugegraph/api/traversers/ShortestPathAPI.java b/hugegraph-api/src/main/java/org/apache/hugegraph/api/traversers/ShortestPathAPI.java index 08cbdf74cb..dcc8489ae1 100644 --- a/hugegraph-api/src/main/java/org/apache/hugegraph/api/traversers/ShortestPathAPI.java +++ b/hugegraph-api/src/main/java/org/apache/hugegraph/api/traversers/ShortestPathAPI.java @@ -20,32 +20,36 @@ import static org.apache.hugegraph.traversal.algorithm.HugeTraverser.DEFAULT_CAPACITY; import static org.apache.hugegraph.traversal.algorithm.HugeTraverser.DEFAULT_MAX_DEGREE; +import java.util.Iterator; import java.util.List; - -import io.swagger.v3.oas.annotations.tags.Tag; -import jakarta.inject.Singleton; -import jakarta.ws.rs.DefaultValue; -import jakarta.ws.rs.GET; -import jakarta.ws.rs.Path; -import jakarta.ws.rs.PathParam; -import jakarta.ws.rs.Produces; -import jakarta.ws.rs.QueryParam; -import jakarta.ws.rs.core.Context; - -import org.apache.hugegraph.core.GraphManager; -import org.slf4j.Logger; +import java.util.Set; import org.apache.hugegraph.HugeGraph; import org.apache.hugegraph.api.API; import org.apache.hugegraph.api.graph.EdgeAPI; import org.apache.hugegraph.api.graph.VertexAPI; import org.apache.hugegraph.backend.id.Id; +import org.apache.hugegraph.core.GraphManager; import org.apache.hugegraph.traversal.algorithm.HugeTraverser; import org.apache.hugegraph.traversal.algorithm.ShortestPathTraverser; import org.apache.hugegraph.type.define.Directions; import org.apache.hugegraph.util.Log; +import org.apache.tinkerpop.gremlin.structure.Edge; +import org.slf4j.Logger; + import com.codahale.metrics.annotation.Timed; import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; + +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.inject.Singleton; +import jakarta.ws.rs.DefaultValue; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.PathParam; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.QueryParam; +import jakarta.ws.rs.core.Context; @Path("graphs/{graph}/traversers/shortestpath") @Singleton @@ -68,13 +72,21 @@ public String get(@Context GraphManager manager, @DefaultValue(DEFAULT_MAX_DEGREE) long maxDegree, @QueryParam("skip_degree") @DefaultValue("0") long skipDegree, + @QueryParam("with_vertex") + @DefaultValue("false") boolean withVertex, + @QueryParam("with_edge") + @DefaultValue("false") boolean withEdge, @QueryParam("capacity") @DefaultValue(DEFAULT_CAPACITY) long capacity) { LOG.debug("Graph [{}] get shortest path from '{}', to '{}' with " + "direction {}, edge label {}, max depth '{}', " + - "max degree '{}', skipped maxDegree '{}' and capacity '{}'", + "max degree '{}', skipped maxDegree '{}', capacity '{}', " + + "with_vertex '{}' and with_edge '{}'", graph, source, target, direction, edgeLabel, depth, - maxDegree, skipDegree, capacity); + maxDegree, skipDegree, capacity, withVertex, withEdge); + + ApiMeasurer measure = new ApiMeasurer(); + Id sourceId = VertexAPI.checkAndParseVertexId(source); Id targetId = VertexAPI.checkAndParseVertexId(target); Directions dir = Directions.convert(EdgeAPI.parseDirection(direction)); @@ -89,6 +101,29 @@ public String get(@Context GraphManager manager, dir, edgeLabels, depth, maxDegree, skipDegree, capacity); - return manager.serializer(g).writeList("path", path.vertices()); + measure.addIterCount(traverser.vertexIterCounter.get(), + traverser.edgeIterCounter.get()); + + Iterator iterVertex; + List vertexIds = path.vertices(); + if (withVertex && !vertexIds.isEmpty()) { + iterVertex = g.vertices(vertexIds.toArray()); + measure.addIterCount(path.vertices().size(), 0L); + } else { + iterVertex = vertexIds.iterator(); + } + + Iterator iterEdge; + Set edges = path.getEdges(); + if (withEdge) { + iterEdge = edges.iterator(); + } else { + iterEdge = HugeTraverser.EdgeRecord.getEdgeIds(edges).iterator(); + } + + return manager.serializer(g, measure.measures()) + .writeMap(ImmutableMap.of("path", path.vertices(), + "vertices", iterVertex, + "edges", iterEdge)); } } diff --git a/hugegraph-api/src/main/java/org/apache/hugegraph/api/traversers/SingleSourceShortestPathAPI.java b/hugegraph-api/src/main/java/org/apache/hugegraph/api/traversers/SingleSourceShortestPathAPI.java index 8813399ca7..eab339d958 100644 --- a/hugegraph-api/src/main/java/org/apache/hugegraph/api/traversers/SingleSourceShortestPathAPI.java +++ b/hugegraph-api/src/main/java/org/apache/hugegraph/api/traversers/SingleSourceShortestPathAPI.java @@ -22,33 +22,33 @@ import static org.apache.hugegraph.traversal.algorithm.HugeTraverser.DEFAULT_PATHS_LIMIT; import java.util.Iterator; - -import io.swagger.v3.oas.annotations.tags.Tag; -import jakarta.inject.Singleton; -import jakarta.ws.rs.DefaultValue; -import jakarta.ws.rs.GET; -import jakarta.ws.rs.Path; -import jakarta.ws.rs.PathParam; -import jakarta.ws.rs.Produces; -import jakarta.ws.rs.QueryParam; -import jakarta.ws.rs.core.Context; - -import org.apache.tinkerpop.gremlin.structure.Vertex; -import org.apache.hugegraph.core.GraphManager; -import org.slf4j.Logger; +import java.util.Set; import org.apache.hugegraph.HugeGraph; import org.apache.hugegraph.api.API; import org.apache.hugegraph.api.graph.EdgeAPI; import org.apache.hugegraph.api.graph.VertexAPI; import org.apache.hugegraph.backend.id.Id; -import org.apache.hugegraph.backend.query.QueryResults; +import org.apache.hugegraph.core.GraphManager; +import org.apache.hugegraph.traversal.algorithm.HugeTraverser; import org.apache.hugegraph.traversal.algorithm.SingleSourceShortestPathTraverser; -import org.apache.hugegraph.traversal.algorithm.SingleSourceShortestPathTraverser.WeightedPaths; import org.apache.hugegraph.type.define.Directions; import org.apache.hugegraph.util.Log; +import org.apache.tinkerpop.gremlin.structure.Edge; +import org.slf4j.Logger; + import com.codahale.metrics.annotation.Timed; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.inject.Singleton; +import jakarta.ws.rs.DefaultValue; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.PathParam; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.QueryParam; +import jakarta.ws.rs.core.Context; + @Path("graphs/{graph}/traversers/singlesourceshortestpath") @Singleton @Tag(name = "SingleSourceShortestPathAPI") @@ -69,16 +69,22 @@ public String get(@Context GraphManager manager, @DefaultValue(DEFAULT_MAX_DEGREE) long maxDegree, @QueryParam("skip_degree") @DefaultValue("0") long skipDegree, + @QueryParam("with_vertex") + @DefaultValue("false") boolean withVertex, + @QueryParam("with_edge") + @DefaultValue("false") boolean withEdge, @QueryParam("capacity") @DefaultValue(DEFAULT_CAPACITY) long capacity, @QueryParam("limit") - @DefaultValue(DEFAULT_PATHS_LIMIT) int limit, - @QueryParam("with_vertex") boolean withVertex) { + @DefaultValue(DEFAULT_PATHS_LIMIT) int limit) { LOG.debug("Graph [{}] get single source shortest path from '{}' " + "with direction {}, edge label {}, weight property {}, " + - "max degree '{}', limit '{}' and with vertex '{}'", + "max degree '{}', capacity '{}', limit '{}', " + + "with_vertex '{}' and with_edge '{}'", graph, source, direction, edgeLabel, - weight, maxDegree, withVertex); + weight, maxDegree, capacity, limit, withVertex, withEdge); + + ApiMeasurer measure = new ApiMeasurer(); Id sourceId = VertexAPI.checkAndParseVertexId(source); Directions dir = Directions.convert(EdgeAPI.parseDirection(direction)); @@ -86,14 +92,31 @@ public String get(@Context GraphManager manager, HugeGraph g = graph(manager, graph); SingleSourceShortestPathTraverser traverser = new SingleSourceShortestPathTraverser(g); - WeightedPaths paths = traverser.singleSourceShortestPaths( - sourceId, dir, edgeLabel, weight, - maxDegree, skipDegree, capacity, limit); - Iterator iterator = QueryResults.emptyIterator(); - assert paths != null; - if (!paths.isEmpty() && withVertex) { - iterator = g.vertices(paths.vertices().toArray()); + SingleSourceShortestPathTraverser.WeightedPaths paths = + traverser.singleSourceShortestPaths( + sourceId, dir, edgeLabel, weight, + maxDegree, skipDegree, capacity, limit); + measure.addIterCount(traverser.vertexIterCounter.get(), + traverser.edgeIterCounter.get()); + + Iterator iterVertex; + Set vertexIds = paths.vertices(); + if (withVertex && !vertexIds.isEmpty()) { + iterVertex = g.vertices(vertexIds.toArray()); + measure.addIterCount(vertexIds.size(), 0L); + } else { + iterVertex = vertexIds.iterator(); } - return manager.serializer(g).writeWeightedPaths(paths, iterator); + + Iterator iterEdge; + Set edges = paths.getEdges(); + if (withEdge && !edges.isEmpty()) { + iterEdge = edges.iterator(); + } else { + iterEdge = HugeTraverser.EdgeRecord.getEdgeIds(edges).iterator(); + } + + return manager.serializer(g, measure.measures()) + .writeWeightedPaths(paths, iterVertex, iterEdge); } } diff --git a/hugegraph-api/src/main/java/org/apache/hugegraph/api/traversers/TemplatePathsAPI.java b/hugegraph-api/src/main/java/org/apache/hugegraph/api/traversers/TemplatePathsAPI.java index d566fae90d..9b3739acb2 100644 --- a/hugegraph-api/src/main/java/org/apache/hugegraph/api/traversers/TemplatePathsAPI.java +++ b/hugegraph-api/src/main/java/org/apache/hugegraph/api/traversers/TemplatePathsAPI.java @@ -26,30 +26,30 @@ import java.util.List; import java.util.Set; -import io.swagger.v3.oas.annotations.tags.Tag; -import jakarta.inject.Singleton; -import jakarta.ws.rs.Consumes; -import jakarta.ws.rs.POST; -import jakarta.ws.rs.Path; -import jakarta.ws.rs.PathParam; -import jakarta.ws.rs.Produces; -import jakarta.ws.rs.core.Context; - -import org.apache.tinkerpop.gremlin.structure.Vertex; -import org.apache.hugegraph.core.GraphManager; -import org.slf4j.Logger; - import org.apache.hugegraph.HugeGraph; import org.apache.hugegraph.backend.id.Id; -import org.apache.hugegraph.backend.query.QueryResults; +import org.apache.hugegraph.core.GraphManager; import org.apache.hugegraph.traversal.algorithm.HugeTraverser; import org.apache.hugegraph.traversal.algorithm.TemplatePathsTraverser; import org.apache.hugegraph.traversal.algorithm.steps.RepeatEdgeStep; import org.apache.hugegraph.util.E; import org.apache.hugegraph.util.Log; +import org.apache.tinkerpop.gremlin.structure.Edge; +import org.apache.tinkerpop.gremlin.structure.Vertex; +import org.slf4j.Logger; + import com.codahale.metrics.annotation.Timed; import com.fasterxml.jackson.annotation.JsonProperty; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.inject.Singleton; +import jakarta.ws.rs.Consumes; +import jakarta.ws.rs.POST; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.PathParam; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.core.Context; + @Path("graphs/{graph}/traversers/templatepaths") @Singleton @Tag(name = "TemplatePathsAPI") @@ -57,6 +57,22 @@ public class TemplatePathsAPI extends TraverserAPI { private static final Logger LOG = Log.logger(TemplatePathsAPI.class); + private static List steps(HugeGraph g, + List steps) { + List edgeSteps = new ArrayList<>(steps.size()); + for (TemplatePathStep step : steps) { + edgeSteps.add(repeatEdgeStep(g, step)); + } + return edgeSteps; + } + + private static RepeatEdgeStep repeatEdgeStep(HugeGraph graph, + TemplatePathStep step) { + return new RepeatEdgeStep(graph, step.direction, step.labels, + step.properties, step.maxDegree, + step.skipDegree, step.maxTimes); + } + @POST @Timed @Consumes(APPLICATION_JSON) @@ -74,9 +90,11 @@ public String post(@Context GraphManager manager, LOG.debug("Graph [{}] get template paths from source vertices '{}', " + "target vertices '{}', with steps '{}', " + - "capacity '{}', limit '{}' and with_vertex '{}'", + "capacity '{}', limit '{}', with_vertex '{}' and with_edge '{}'", graph, request.sources, request.targets, request.steps, - request.capacity, request.limit, request.withVertex); + request.capacity, request.limit, request.withVertex, request.withEdge); + + ApiMeasurer measure = new ApiMeasurer(); HugeGraph g = graph(manager, graph); Iterator sources = request.sources.vertices(g); @@ -84,40 +102,38 @@ public String post(@Context GraphManager manager, List steps = steps(g, request.steps); TemplatePathsTraverser traverser = new TemplatePathsTraverser(g); - Set paths; - paths = traverser.templatePaths(sources, targets, steps, + TemplatePathsTraverser.WrappedPathSet wrappedPathSet = + traverser.templatePaths(sources, targets, steps, request.withRing, request.capacity, request.limit); + measure.addIterCount(traverser.vertexIterCounter.get(), + traverser.edgeIterCounter.get()); - if (!request.withVertex) { - return manager.serializer(g).writePaths("paths", paths, false); - } + Set paths = wrappedPathSet.paths(); - Set ids = new HashSet<>(); - for (HugeTraverser.Path p : paths) { - ids.addAll(p.vertices()); + Iterator iterVertex; + Set vertexIds = new HashSet<>(); + for (HugeTraverser.Path path : paths) { + vertexIds.addAll(path.vertices()); } - Iterator iter = QueryResults.emptyIterator(); - if (!ids.isEmpty()) { - iter = g.vertices(ids.toArray()); + if (request.withVertex && !vertexIds.isEmpty()) { + iterVertex = g.vertices(vertexIds.toArray()); + measure.addIterCount(vertexIds.size(), 0L); + } else { + iterVertex = vertexIds.iterator(); } - return manager.serializer(g).writePaths("paths", paths, false, iter); - } - private static List steps(HugeGraph g, - List steps) { - List edgeSteps = new ArrayList<>(steps.size()); - for (TemplatePathStep step : steps) { - edgeSteps.add(repeatEdgeStep(g, step)); + Iterator iterEdge; + Set edges = wrappedPathSet.edges(); + if (request.withEdge && !edges.isEmpty()) { + iterEdge = edges.iterator(); + } else { + iterEdge = HugeTraverser.EdgeRecord.getEdgeIds(edges).iterator(); } - return edgeSteps; - } - private static RepeatEdgeStep repeatEdgeStep(HugeGraph graph, - TemplatePathStep step) { - return new RepeatEdgeStep(graph, step.direction, step.labels, - step.properties, step.maxDegree, - step.skipDegree, step.maxTimes); + return manager.serializer(g, measure.measures()) + .writePaths("paths", paths, false, + iterVertex, iterEdge); } private static class Request { @@ -136,15 +152,17 @@ private static class Request { public int limit = Integer.parseInt(DEFAULT_PATHS_LIMIT); @JsonProperty("with_vertex") public boolean withVertex = false; + @JsonProperty("with_edge") + public boolean withEdge = false; @Override public String toString() { return String.format("TemplatePathsRequest{sources=%s,targets=%s," + "steps=%s,withRing=%s,capacity=%s,limit=%s," + - "withVertex=%s}", + "withVertex=%s,withEdge=%s}", this.sources, this.targets, this.steps, this.withRing, this.capacity, this.limit, - this.withVertex); + this.withVertex, this.withEdge); } } diff --git a/hugegraph-api/src/main/java/org/apache/hugegraph/api/traversers/VerticesAPI.java b/hugegraph-api/src/main/java/org/apache/hugegraph/api/traversers/VerticesAPI.java index 56c4889f81..86364a23bf 100644 --- a/hugegraph-api/src/main/java/org/apache/hugegraph/api/traversers/VerticesAPI.java +++ b/hugegraph-api/src/main/java/org/apache/hugegraph/api/traversers/VerticesAPI.java @@ -22,20 +22,6 @@ import java.util.Iterator; import java.util.List; -import io.swagger.v3.oas.annotations.tags.Tag; -import jakarta.inject.Singleton; -import jakarta.ws.rs.DefaultValue; -import jakarta.ws.rs.GET; -import jakarta.ws.rs.Path; -import jakarta.ws.rs.PathParam; -import jakarta.ws.rs.Produces; -import jakarta.ws.rs.QueryParam; -import jakarta.ws.rs.core.Context; - -import org.apache.tinkerpop.gremlin.structure.Vertex; -import org.apache.hugegraph.core.GraphManager; -import org.slf4j.Logger; - import org.apache.hugegraph.HugeGraph; import org.apache.hugegraph.api.API; import org.apache.hugegraph.api.filter.CompressInterceptor.Compress; @@ -43,11 +29,25 @@ import org.apache.hugegraph.backend.id.Id; import org.apache.hugegraph.backend.query.ConditionQuery; import org.apache.hugegraph.backend.store.Shard; +import org.apache.hugegraph.core.GraphManager; import org.apache.hugegraph.type.HugeType; import org.apache.hugegraph.util.E; import org.apache.hugegraph.util.Log; +import org.apache.tinkerpop.gremlin.structure.Vertex; +import org.slf4j.Logger; + import com.codahale.metrics.annotation.Timed; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.inject.Singleton; +import jakarta.ws.rs.DefaultValue; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.PathParam; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.QueryParam; +import jakarta.ws.rs.core.Context; + @Path("graphs/{graph}/traversers/vertices") @Singleton @Tag(name = "VerticesAPI") diff --git a/hugegraph-api/src/main/java/org/apache/hugegraph/api/traversers/WeightedShortestPathAPI.java b/hugegraph-api/src/main/java/org/apache/hugegraph/api/traversers/WeightedShortestPathAPI.java index b675f618bc..1c25661f15 100644 --- a/hugegraph-api/src/main/java/org/apache/hugegraph/api/traversers/WeightedShortestPathAPI.java +++ b/hugegraph-api/src/main/java/org/apache/hugegraph/api/traversers/WeightedShortestPathAPI.java @@ -21,20 +21,8 @@ import static org.apache.hugegraph.traversal.algorithm.HugeTraverser.DEFAULT_MAX_DEGREE; import java.util.Iterator; - -import io.swagger.v3.oas.annotations.tags.Tag; -import jakarta.inject.Singleton; -import jakarta.ws.rs.DefaultValue; -import jakarta.ws.rs.GET; -import jakarta.ws.rs.Path; -import jakarta.ws.rs.PathParam; -import jakarta.ws.rs.Produces; -import jakarta.ws.rs.QueryParam; -import jakarta.ws.rs.core.Context; - -import org.apache.tinkerpop.gremlin.structure.Vertex; -import org.apache.hugegraph.core.GraphManager; -import org.slf4j.Logger; +import java.util.List; +import java.util.Set; import org.apache.hugegraph.HugeGraph; import org.apache.hugegraph.api.API; @@ -42,13 +30,27 @@ import org.apache.hugegraph.api.graph.VertexAPI; import org.apache.hugegraph.backend.id.Id; import org.apache.hugegraph.backend.query.QueryResults; +import org.apache.hugegraph.core.GraphManager; +import org.apache.hugegraph.traversal.algorithm.HugeTraverser; import org.apache.hugegraph.traversal.algorithm.SingleSourceShortestPathTraverser; -import org.apache.hugegraph.traversal.algorithm.SingleSourceShortestPathTraverser.NodeWithWeight; import org.apache.hugegraph.type.define.Directions; import org.apache.hugegraph.util.E; import org.apache.hugegraph.util.Log; +import org.apache.tinkerpop.gremlin.structure.Edge; +import org.slf4j.Logger; + import com.codahale.metrics.annotation.Timed; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.inject.Singleton; +import jakarta.ws.rs.DefaultValue; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.PathParam; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.QueryParam; +import jakarta.ws.rs.core.Context; + @Path("graphs/{graph}/traversers/weightedshortestpath") @Singleton @Tag(name = "WeightedShortestPathAPI") @@ -70,16 +72,20 @@ public String get(@Context GraphManager manager, @DefaultValue(DEFAULT_MAX_DEGREE) long maxDegree, @QueryParam("skip_degree") @DefaultValue("0") long skipDegree, + @QueryParam("with_vertex") + @DefaultValue("false") boolean withVertex, + @QueryParam("with_edge") + @DefaultValue("false") boolean withEdge, @QueryParam("capacity") - @DefaultValue(DEFAULT_CAPACITY) long capacity, - @QueryParam("with_vertex") boolean withVertex) { + @DefaultValue(DEFAULT_CAPACITY) long capacity) { LOG.debug("Graph [{}] get weighted shortest path between '{}' and " + "'{}' with direction {}, edge label {}, weight property {}, " + "max degree '{}', skip degree '{}', capacity '{}', " + - "and with vertex '{}'", + "with_vertex '{}' and with_edge '{}'", graph, source, target, direction, edgeLabel, weight, - maxDegree, skipDegree, capacity, withVertex); + maxDegree, skipDegree, capacity, withVertex, withEdge); + ApiMeasurer measure = new ApiMeasurer(); Id sourceId = VertexAPI.checkAndParseVertexId(source); Id targetId = VertexAPI.checkAndParseVertexId(target); Directions dir = Directions.convert(EdgeAPI.parseDirection(direction)); @@ -89,14 +95,38 @@ public String get(@Context GraphManager manager, SingleSourceShortestPathTraverser traverser = new SingleSourceShortestPathTraverser(g); - NodeWithWeight path = traverser.weightedShortestPath( - sourceId, targetId, dir, edgeLabel, weight, - maxDegree, skipDegree, capacity); - Iterator iterator = QueryResults.emptyIterator(); - if (path != null && withVertex) { - assert !path.node().path().isEmpty(); - iterator = g.vertices(path.node().path().toArray()); + SingleSourceShortestPathTraverser.NodeWithWeight node = + traverser.weightedShortestPath(sourceId, targetId, + dir, edgeLabel, weight, + maxDegree, skipDegree, capacity); + measure.addIterCount(traverser.vertexIterCounter.get(), + traverser.edgeIterCounter.get()); + + if (node == null) { + return manager.serializer(g, measure.measures()) + .writeWeightedPath(null, + QueryResults.emptyIterator(), + QueryResults.emptyIterator()); + } + + Iterator iterVertex; + List vertexIds = node.node().path(); + if (withVertex && !vertexIds.isEmpty()) { + iterVertex = g.vertices(vertexIds.toArray()); + measure.addIterCount(vertexIds.size(), 0L); + } else { + iterVertex = vertexIds.iterator(); + } + + Iterator iterEdge; + Set edges = node.getEdges(); + if (withEdge && !edges.isEmpty()) { + iterEdge = edges.iterator(); + } else { + iterEdge = HugeTraverser.EdgeRecord.getEdgeIds(edges).iterator(); } - return manager.serializer(g).writeWeightedPath(path, iterator); + + return manager.serializer(g, measure.measures()) + .writeWeightedPath(node, iterVertex, iterEdge); } } diff --git a/hugegraph-api/src/main/java/org/apache/hugegraph/config/ServerOptions.java b/hugegraph-api/src/main/java/org/apache/hugegraph/config/ServerOptions.java index 95a53faa39..e66b593568 100644 --- a/hugegraph-api/src/main/java/org/apache/hugegraph/config/ServerOptions.java +++ b/hugegraph-api/src/main/java/org/apache/hugegraph/config/ServerOptions.java @@ -264,4 +264,4 @@ public static synchronized ServerOptions instance() { disallowEmpty(), true ); -} +} \ No newline at end of file diff --git a/hugegraph-api/src/main/java/org/apache/hugegraph/core/GraphManager.java b/hugegraph-api/src/main/java/org/apache/hugegraph/core/GraphManager.java index b203c10470..2c73b5ee93 100644 --- a/hugegraph-api/src/main/java/org/apache/hugegraph/core/GraphManager.java +++ b/hugegraph-api/src/main/java/org/apache/hugegraph/core/GraphManager.java @@ -224,6 +224,10 @@ public Serializer serializer(Graph g) { return JsonSerializer.instance(); } + public Serializer serializer(Graph g, Map apiMeasure) { + return JsonSerializer.instance(apiMeasure); + } + public void rollbackAll() { for (Graph graph : this.graphs.values()) { if (graph.features().graph().supportsTransactions() && diff --git a/hugegraph-api/src/main/java/org/apache/hugegraph/serializer/JsonSerializer.java b/hugegraph-api/src/main/java/org/apache/hugegraph/serializer/JsonSerializer.java index 8103602234..035499c598 100644 --- a/hugegraph-api/src/main/java/org/apache/hugegraph/serializer/JsonSerializer.java +++ b/hugegraph-api/src/main/java/org/apache/hugegraph/serializer/JsonSerializer.java @@ -24,11 +24,6 @@ import java.util.List; import java.util.Map; -import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversal; -import org.apache.tinkerpop.gremlin.structure.Edge; -import org.apache.tinkerpop.gremlin.structure.Vertex; -import org.apache.tinkerpop.gremlin.structure.util.CloseableIterator; - import org.apache.hugegraph.HugeException; import org.apache.hugegraph.api.API; import org.apache.hugegraph.auth.SchemaDefine.AuthElement; @@ -47,25 +42,44 @@ import org.apache.hugegraph.traversal.algorithm.SingleSourceShortestPathTraverser.WeightedPaths; import org.apache.hugegraph.traversal.optimize.TraversalUtil; import org.apache.hugegraph.util.JsonUtil; +import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversal; +import org.apache.tinkerpop.gremlin.structure.Edge; +import org.apache.tinkerpop.gremlin.structure.Vertex; +import org.apache.tinkerpop.gremlin.structure.util.CloseableIterator; + import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; public class JsonSerializer implements Serializer { private static final int LBUF_SIZE = 1024; - - private static JsonSerializer INSTANCE = new JsonSerializer(); + private static final String MEASURE_KEY = "measure"; + private static final JsonSerializer INSTANCE = new JsonSerializer(); + private Map apiMeasure = null; private JsonSerializer() { } + private JsonSerializer(Map apiMeasure) { + this.apiMeasure = apiMeasure; + } + public static JsonSerializer instance() { return INSTANCE; } + public static JsonSerializer instance(Map apiMeasure) { + return new JsonSerializer(apiMeasure); + } + @Override public String writeMap(Map map) { - return JsonUtil.toJson(map); + ImmutableMap.Builder builder = ImmutableMap.builder(); + builder.putAll(map); + if (this.apiMeasure != null) { + builder.put(MEASURE_KEY, this.apiMeasure); + } + return JsonUtil.toJson(builder.build()); } @Override @@ -73,6 +87,10 @@ public String writeList(String label, Collection list) { try (ByteArrayOutputStream out = new ByteArrayOutputStream(LBUF_SIZE)) { out.write(String.format("{\"%s\": ", label).getBytes(API.CHARSET)); out.write(JsonUtil.toJson(list).getBytes(API.CHARSET)); + if (this.apiMeasure != null) { + out.write(String.format(",\"%s\": ", MEASURE_KEY).getBytes(API.CHARSET)); + out.write(JsonUtil.toJson(this.apiMeasure).getBytes(API.CHARSET)); + } out.write("}".getBytes(API.CHARSET)); return out.toString(API.CHARSET); } catch (Exception e) { @@ -122,6 +140,11 @@ private String writeIterator(String label, Iterator iter, out.write(page.getBytes(API.CHARSET)); } + if (this.apiMeasure != null) { + out.write(String.format(",\"%s\":[", MEASURE_KEY).getBytes(API.CHARSET)); + out.write(JsonUtil.toJson(this.apiMeasure).getBytes(API.CHARSET)); + } + out.write("}".getBytes(API.CHARSET)); return out.toString(API.CHARSET); } catch (HugeException e) { @@ -144,7 +167,7 @@ public String writePropertyKey(PropertyKey propertyKey) { @Override public String writeTaskWithSchema( - SchemaElement.TaskWithSchema taskWithSchema) { + SchemaElement.TaskWithSchema taskWithSchema) { StringBuilder builder = new StringBuilder(); long id = taskWithSchema.task() == null ? 0L : taskWithSchema.task().asLong(); @@ -162,10 +185,14 @@ public String writeTaskWithSchema( "TaskWithSchema, only support " + "[PropertyKey, IndexLabel]", schemaElement); } - return builder.append("{\"").append(type).append("\": ") - .append(schema) - .append(", \"task_id\": ").append(id).append("}") - .toString(); + builder.append("{\"").append(type).append("\": ") + .append(schema).append(", \"task_id\": ") + .append(id); + if (this.apiMeasure != null) { + builder.append(String.format(",\"%s\":[", MEASURE_KEY)); + builder.append(JsonUtil.toJson(this.apiMeasure)); + } + return builder.append("}").toString(); } @Override @@ -245,27 +272,36 @@ public String writeAuthElements(String label, @Override public String writePaths(String name, Collection paths, - boolean withCrossPoint, - Iterator vertices) { + boolean withCrossPoint, Iterator vertices, + Iterator edges) { List> pathList = new ArrayList<>(paths.size()); for (HugeTraverser.Path path : paths) { pathList.add(path.toMap(withCrossPoint)); } - Map results; - if (vertices == null) { - results = ImmutableMap.of(name, pathList); - } else { - results = ImmutableMap.of(name, pathList, "vertices", vertices); + ImmutableMap.Builder builder = ImmutableMap.builder(); + builder.put(name, pathList); + + if (vertices != null) { + builder.put("vertices", vertices); + } + + if (edges != null) { + builder.put("edges", edges); + } + + if (this.apiMeasure != null) { + builder.put(MEASURE_KEY, this.apiMeasure); } - return JsonUtil.toJson(results); + + return JsonUtil.toJson(builder.build()); } @Override public String writeCrosspoints(CrosspointsPaths paths, - Iterator iterator, + Iterator vertices, + Iterator edges, boolean withPath) { - Map results; List> pathList; if (withPath) { pathList = new ArrayList<>(); @@ -275,50 +311,81 @@ public String writeCrosspoints(CrosspointsPaths paths, } else { pathList = ImmutableList.of(); } - results = ImmutableMap.of("crosspoints", paths.crosspoints(), - "paths", pathList, - "vertices", iterator); - return JsonUtil.toJson(results); + ImmutableMap.Builder builder = ImmutableMap.builder() + .put("crosspoints", + paths.crosspoints()) + .put("paths", pathList) + .put("vertices", vertices) + .put("edges", edges); + if (this.apiMeasure != null) { + builder.put(MEASURE_KEY, this.apiMeasure); + } + return JsonUtil.toJson(builder.build()); } @Override public String writeSimilars(SimilarsMap similars, - Iterator vertices) { - return JsonUtil.toJson(ImmutableMap.of("similars", similars.toMap(), - "vertices", vertices)); + Iterator vertices) { + ImmutableMap.Builder builder = ImmutableMap.builder() + .put("similars", + similars.toMap()) + .put("vertices", vertices); + if (this.apiMeasure != null) { + builder.put(MEASURE_KEY, this.apiMeasure); + } + return JsonUtil.toJson(builder.build()); } @Override - public String writeWeightedPath(NodeWithWeight path, - Iterator vertices) { + public String writeWeightedPath(NodeWithWeight path, Iterator vertices, + Iterator edges) { Map pathMap = path == null ? ImmutableMap.of() : path.toMap(); - return JsonUtil.toJson(ImmutableMap.of("path", pathMap, - "vertices", vertices)); + ImmutableMap.Builder builder = ImmutableMap.builder() + .put("path", pathMap) + .put("vertices", vertices) + .put("edges", edges); + if (this.apiMeasure != null) { + builder.put(MEASURE_KEY, this.apiMeasure); + } + return JsonUtil.toJson(builder.build()); } @Override - public String writeWeightedPaths(WeightedPaths paths, - Iterator vertices) { + public String writeWeightedPaths(WeightedPaths paths, Iterator vertices, + Iterator edges) { Map> pathMap = paths == null ? ImmutableMap.of() : paths.toMap(); - return JsonUtil.toJson(ImmutableMap.of("paths", pathMap, - "vertices", vertices)); + ImmutableMap.Builder builder = ImmutableMap.builder() + .put("paths", pathMap) + .put("vertices", vertices) + .put("edges", edges); + if (this.apiMeasure != null) { + builder.put(MEASURE_KEY, this.apiMeasure); + } + return JsonUtil.toJson(builder.build()); } @Override public String writeNodesWithPath(String name, List nodes, long size, Collection paths, - Iterator vertices) { + Iterator vertices, Iterator edges) { List> pathList = new ArrayList<>(); for (HugeTraverser.Path path : paths) { pathList.add(path.toMap(false)); } - Map results; - results = ImmutableMap.of(name, nodes, "size", size, - "paths", pathList, "vertices", vertices); - return JsonUtil.toJson(results); + ImmutableMap.Builder builder = ImmutableMap.builder() + .put(name, nodes) + .put("size", size) + .put("paths", pathList) + .put("vertices", vertices) + .put("edges", edges); + if (this.apiMeasure != null) { + builder.put(MEASURE_KEY, this.apiMeasure); + } + + return JsonUtil.toJson(builder.build()); } } diff --git a/hugegraph-api/src/main/java/org/apache/hugegraph/serializer/Serializer.java b/hugegraph-api/src/main/java/org/apache/hugegraph/serializer/Serializer.java index f3b0cdcace..96fa634202 100644 --- a/hugegraph-api/src/main/java/org/apache/hugegraph/serializer/Serializer.java +++ b/hugegraph-api/src/main/java/org/apache/hugegraph/serializer/Serializer.java @@ -22,9 +22,6 @@ import java.util.List; import java.util.Map; -import org.apache.tinkerpop.gremlin.structure.Edge; -import org.apache.tinkerpop.gremlin.structure.Vertex; - import org.apache.hugegraph.auth.SchemaDefine.AuthElement; import org.apache.hugegraph.backend.id.Id; import org.apache.hugegraph.schema.EdgeLabel; @@ -37,6 +34,8 @@ import org.apache.hugegraph.traversal.algorithm.HugeTraverser; import org.apache.hugegraph.traversal.algorithm.SingleSourceShortestPathTraverser.NodeWithWeight; import org.apache.hugegraph.traversal.algorithm.SingleSourceShortestPathTraverser.WeightedPaths; +import org.apache.tinkerpop.gremlin.structure.Edge; +import org.apache.tinkerpop.gremlin.structure.Vertex; public interface Serializer { @@ -77,23 +76,26 @@ public interface Serializer { String writeAuthElements(String label, List users); String writePaths(String name, Collection paths, - boolean withCrossPoint, Iterator vertices); + boolean withCrossPoint, Iterator vertices, + Iterator edges); default String writePaths(String name, Collection paths, boolean withCrossPoint) { - return this.writePaths(name, paths, withCrossPoint, null); + return this.writePaths(name, paths, withCrossPoint, null, null); } - String writeCrosspoints(CrosspointsPaths paths, Iterator iterator, - boolean withPath); + String writeCrosspoints(CrosspointsPaths paths, Iterator vertices, + Iterator edges, boolean withPath); - String writeSimilars(SimilarsMap similars, Iterator vertices); + String writeSimilars(SimilarsMap similars, Iterator vertices); - String writeWeightedPath(NodeWithWeight path, Iterator vertices); + String writeWeightedPath(NodeWithWeight path, Iterator vertices, + Iterator edges); - String writeWeightedPaths(WeightedPaths paths, Iterator vertices); + String writeWeightedPaths(WeightedPaths paths, Iterator vertices, + Iterator edges); String writeNodesWithPath(String name, List nodes, long size, Collection paths, - Iterator vertices); + Iterator vertices, Iterator edges); } diff --git a/hugegraph-core/src/main/java/org/apache/hugegraph/job/algorithm/comm/TriangleCountAlgorithm.java b/hugegraph-core/src/main/java/org/apache/hugegraph/job/algorithm/comm/TriangleCountAlgorithm.java index 2f512f7169..0fba245966 100644 --- a/hugegraph-core/src/main/java/org/apache/hugegraph/job/algorithm/comm/TriangleCountAlgorithm.java +++ b/hugegraph-core/src/main/java/org/apache/hugegraph/job/algorithm/comm/TriangleCountAlgorithm.java @@ -26,19 +26,29 @@ import org.apache.commons.lang.mutable.MutableLong; import org.apache.hugegraph.backend.id.Id; import org.apache.hugegraph.backend.id.IdGenerator; -import org.apache.tinkerpop.gremlin.structure.Edge; - import org.apache.hugegraph.job.UserJob; import org.apache.hugegraph.structure.HugeEdge; import org.apache.hugegraph.type.define.Directions; import org.apache.hugegraph.util.E; import org.apache.hugegraph.util.InsertionOrderUtil; +import org.apache.tinkerpop.gremlin.structure.Edge; + import com.google.common.collect.ImmutableMap; public class TriangleCountAlgorithm extends AbstractCommAlgorithm { public static final String ALGO_NAME = "triangle_count"; + protected static int workersWhenBoth(Map parameters) { + Directions direction = direction4Out(parameters); + int workers = workers(parameters); + E.checkArgument(direction == Directions.BOTH || workers <= 0, + "The workers must be not set when direction!=BOTH, " + + "but got workers=%s and direction=%s", + workers, direction); + return workers; + } + @Override public String name() { return ALGO_NAME; @@ -60,16 +70,6 @@ public Object call(UserJob job, Map parameters) { } } - protected static int workersWhenBoth(Map parameters) { - Directions direction = direction4Out(parameters); - int workers = workers(parameters); - E.checkArgument(direction == Directions.BOTH || workers <= 0, - "The workers must be not set when direction!=BOTH, " + - "but got workers=%s and direction=%s", - workers, direction); - return workers; - } - protected static class Traverser extends AlgoTraverser { protected static final String KEY_TRIANGLES = "triangles"; @@ -83,8 +83,12 @@ protected Traverser(UserJob job, String name, int workers) { super(job, name, workers); } + protected static Set newOrderedSet() { + return new TreeSet<>(); + } + public Object triangleCount(Directions direction, long degree) { - Map results = triangles( direction, degree); + Map results = triangles(direction, degree); results = InsertionOrderUtil.newMap(results); results.remove(KEY_TRIADS); return results; @@ -191,7 +195,7 @@ private Set adjacentVertices(Id source, long degree, MutableLong edgesCount) { Iterator adjVertices = this.adjacentVertices(source, Directions.BOTH, - null, degree); + (Id) null, degree); Set set = newOrderedSet(); while (adjVertices.hasNext()) { edgesCount.increment(); @@ -206,7 +210,7 @@ protected long intersect(long degree, Set adjVertices) { Id empty = IdGenerator.ZERO; Iterator vertices; for (Id v : adjVertices) { - vertices = this.adjacentVertices(v, dir, null, degree); + vertices = this.adjacentVertices(v, dir, (Id) null, degree); Id lastVertex = empty; while (vertices.hasNext()) { Id vertex = vertices.next(); @@ -231,9 +235,5 @@ protected long intersect(long degree, Set adjVertices) { protected long localTriads(int size) { return size * (size - 1L) / 2L; } - - protected static Set newOrderedSet() { - return new TreeSet<>(); - } } } diff --git a/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/algorithm/CollectionPathsTraverser.java b/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/algorithm/CollectionPathsTraverser.java index 76db199498..51e919dff8 100644 --- a/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/algorithm/CollectionPathsTraverser.java +++ b/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/algorithm/CollectionPathsTraverser.java @@ -21,15 +21,17 @@ import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.Set; import org.apache.hugegraph.HugeGraph; import org.apache.hugegraph.backend.id.Id; +import org.apache.hugegraph.structure.HugeVertex; import org.apache.hugegraph.traversal.algorithm.steps.EdgeStep; import org.apache.hugegraph.traversal.algorithm.strategy.TraverseStrategy; +import org.apache.hugegraph.util.E; +import org.apache.tinkerpop.gremlin.structure.Edge; import org.apache.tinkerpop.gremlin.structure.Vertex; -import org.apache.hugegraph.structure.HugeVertex; -import org.apache.hugegraph.util.E; import com.google.common.collect.ImmutableList; public class CollectionPathsTraverser extends HugeTraverser { @@ -38,10 +40,10 @@ public CollectionPathsTraverser(HugeGraph graph) { super(graph); } - public Collection paths(Iterator sources, - Iterator targets, - EdgeStep step, int depth, boolean nearest, - long capacity, long limit) { + public WrappedPathCollection paths(Iterator sources, + Iterator targets, + EdgeStep step, int depth, boolean nearest, + long capacity, long limit) { checkCapacity(capacity); checkLimit(limit); @@ -63,31 +65,33 @@ public Collection paths(Iterator sources, "but got: %s", MAX_VERTICES, sourceList.size()); checkPositive(depth, "max depth"); + boolean concurrent = depth >= this.concurrentDepth(); TraverseStrategy strategy = TraverseStrategy.create( - depth >= this.concurrentDepth(), - this.graph()); + concurrent, this.graph()); Traverser traverser; if (nearest) { traverser = new NearestTraverser(this, strategy, sourceList, targetList, step, - depth, capacity, limit); + depth, capacity, limit, concurrent); } else { traverser = new Traverser(this, strategy, sourceList, targetList, step, - depth, capacity, limit); + depth, capacity, limit, concurrent); } do { // Forward traverser.forward(); if (traverser.finished()) { - return traverser.paths(); + Collection paths = traverser.paths(); + return new WrappedPathCollection(paths, traverser.edgeResults.getEdges(paths)); } // Backward traverser.backward(); if (traverser.finished()) { - return traverser.paths(); + Collection paths = traverser.paths(); + return new WrappedPathCollection(paths, traverser.edgeResults.getEdges(paths)); } } while (true); } @@ -98,8 +102,9 @@ private static class Traverser extends PathTraverser { public Traverser(HugeTraverser traverser, TraverseStrategy strategy, Collection sources, Collection targets, - EdgeStep step, int depth, long capacity, long limit) { - super(traverser, strategy, sources, targets, capacity, limit); + EdgeStep step, int depth, long capacity, long limit, + boolean concurrent) { + super(traverser, strategy, sources, targets, capacity, limit, concurrent); this.step = step; this.totalSteps = depth; } @@ -180,15 +185,15 @@ protected void reInitCurrentStepIfNeeded(EdgeStep step, } } - private class NearestTraverser extends Traverser { + private static class NearestTraverser extends Traverser { public NearestTraverser(HugeTraverser traverser, TraverseStrategy strategy, Collection sources, Collection targets, EdgeStep step, int depth, long capacity, - long limit) { + long limit, boolean concurrent) { super(traverser, strategy, sources, targets, step, - depth, capacity, limit); + depth, capacity, limit, concurrent); } @Override @@ -274,4 +279,23 @@ protected int accessedNodes() { return this.sourcesAll.size() + this.targetsAll.size(); } } + + public static class WrappedPathCollection { + + private final Collection paths; + private final Set edges; + + public WrappedPathCollection(Collection paths, Set edges) { + this.paths = paths; + this.edges = edges; + } + + public Collection paths() { + return paths; + } + + public Set edges() { + return edges; + } + } } diff --git a/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/algorithm/CustomizePathsTraverser.java b/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/algorithm/CustomizePathsTraverser.java index 3d3559b05a..28e30367d6 100644 --- a/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/algorithm/CustomizePathsTraverser.java +++ b/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/algorithm/CustomizePathsTraverser.java @@ -22,25 +22,55 @@ import java.util.List; import java.util.Map; -import jakarta.ws.rs.core.MultivaluedMap; - import org.apache.hugegraph.HugeGraph; import org.apache.hugegraph.backend.id.Id; -import org.apache.tinkerpop.gremlin.structure.Edge; -import org.apache.tinkerpop.gremlin.structure.Vertex; - import org.apache.hugegraph.structure.HugeEdge; import org.apache.hugegraph.structure.HugeVertex; import org.apache.hugegraph.traversal.algorithm.steps.WeightedEdgeStep; import org.apache.hugegraph.util.CollectionUtil; import org.apache.hugegraph.util.E; +import org.apache.tinkerpop.gremlin.structure.Edge; +import org.apache.tinkerpop.gremlin.structure.Vertex; + import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; +import jakarta.ws.rs.core.MultivaluedMap; + public class CustomizePathsTraverser extends HugeTraverser { + private final EdgeRecord edgeResults; + public CustomizePathsTraverser(HugeGraph graph) { super(graph); + this.edgeResults = new EdgeRecord(false); + } + + public static List topNPath(List paths, + boolean incr, long limit) { + paths.sort((p1, p2) -> { + WeightPath wp1 = (WeightPath) p1; + WeightPath wp2 = (WeightPath) p2; + int result = Double.compare(wp1.totalWeight(), wp2.totalWeight()); + return incr ? result : -result; + }); + + if (limit == NO_LIMIT || paths.size() <= limit) { + return paths; + } + return paths.subList(0, (int) limit); + } + + private static List sample(List nodes, long sample) { + if (nodes.size() <= sample) { + return nodes; + } + List result = newList((int) sample); + int size = nodes.size(); + for (int random : CollectionUtil.randomSet(0, size, (int) sample)) { + result.add(nodes.get(random)); + } + return result; } public List customizedPaths(Iterator vertices, @@ -64,7 +94,8 @@ public List customizedPaths(Iterator vertices, int pathCount = 0; long access = 0; MultivaluedMap newVertices = null; - root : for (WeightedEdgeStep step : steps) { + root: + for (WeightedEdgeStep step : steps) { stepNum--; newVertices = newMultivalueMap(); Iterator edges; @@ -75,7 +106,11 @@ public List customizedPaths(Iterator vertices, edges = this.edgesOfVertex(entry.getKey(), step.step()); while (edges.hasNext()) { HugeEdge edge = (HugeEdge) edges.next(); + this.edgeIterCounter.addAndGet(1L); Id target = edge.id().otherVertexId(); + + this.edgeResults.addEdge(entry.getKey(), target, edge); + for (Node n : entry.getValue()) { // If have loop, skip target if (n.contains(target)) { @@ -113,6 +148,7 @@ public List customizedPaths(Iterator vertices, } } } + this.vertexIterCounter.addAndGet(sources.size()); // Re-init sources sources = newVertices; } @@ -120,6 +156,9 @@ public List customizedPaths(Iterator vertices, return ImmutableList.of(); } List paths = newList(); + if (newVertices == null) { + return ImmutableList.of(); + } for (List nodes : newVertices.values()) { for (Node n : nodes) { if (sorted) { @@ -133,36 +172,13 @@ public List customizedPaths(Iterator vertices, return paths; } - public static List topNPath(List paths, - boolean incr, long limit) { - paths.sort((p1, p2) -> { - WeightPath wp1 = (WeightPath) p1; - WeightPath wp2 = (WeightPath) p2; - int result = Double.compare(wp1.totalWeight(), wp2.totalWeight()); - return incr ? result : -result; - }); - - if (limit == NO_LIMIT || paths.size() <= limit) { - return paths; - } - return paths.subList(0, (int) limit); - } - - private static List sample(List nodes, long sample) { - if (nodes.size() <= sample) { - return nodes; - } - List result = newList((int) sample); - int size = nodes.size(); - for (int random : CollectionUtil.randomSet(0, size, (int) sample)) { - result.add(nodes.get(random)); - } - return result; + public EdgeRecord edgeResults() { + return edgeResults; } public static class WeightNode extends Node { - private double weight; + private final double weight; public WeightNode(Id id, Node parent, double weight) { super(id, parent); @@ -183,7 +199,7 @@ public List weights() { public static class WeightPath extends Path { - private List weights; + private final List weights; private double totalWeight; public WeightPath(List vertices, diff --git a/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/algorithm/CustomizedCrosspointsTraverser.java b/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/algorithm/CustomizedCrosspointsTraverser.java index f097711e04..7b6bf8f76f 100644 --- a/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/algorithm/CustomizedCrosspointsTraverser.java +++ b/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/algorithm/CustomizedCrosspointsTraverser.java @@ -24,26 +24,82 @@ import java.util.Set; import java.util.stream.Collectors; -import jakarta.ws.rs.core.MultivaluedMap; - import org.apache.hugegraph.HugeGraph; import org.apache.hugegraph.backend.id.Id; +import org.apache.hugegraph.structure.HugeEdge; +import org.apache.hugegraph.structure.HugeVertex; import org.apache.hugegraph.traversal.algorithm.steps.EdgeStep; import org.apache.hugegraph.type.define.Directions; +import org.apache.hugegraph.util.CollectionUtil; +import org.apache.hugegraph.util.E; import org.apache.tinkerpop.gremlin.structure.Edge; import org.apache.tinkerpop.gremlin.structure.Vertex; -import org.apache.hugegraph.structure.HugeEdge; -import org.apache.hugegraph.structure.HugeVertex; -import org.apache.hugegraph.util.CollectionUtil; -import org.apache.hugegraph.util.E; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; +import jakarta.ws.rs.core.MultivaluedMap; + public class CustomizedCrosspointsTraverser extends HugeTraverser { + private final EdgeRecord edgeResults; + public CustomizedCrosspointsTraverser(HugeGraph graph) { super(graph); + this.edgeResults = new EdgeRecord(false); + } + + private static CrosspointsPaths intersectionPaths(List sources, + List paths, + long limit) { + // Split paths by end vertices + MultivaluedMap endVertices = newMultivalueMap(); + for (Path path : paths) { + List vertices = path.vertices(); + int length = vertices.size(); + endVertices.add(vertices.get(0), vertices.get(length - 1)); + } + + Set sourceIds = sources.stream().map(HugeVertex::id) + .collect(Collectors.toSet()); + Set ids = endVertices.keySet(); + if (sourceIds.size() != ids.size() || !sourceIds.containsAll(ids)) { + return CrosspointsPaths.EMPTY; + } + + // Get intersection of end vertices + Collection intersection = null; + for (List ends : endVertices.values()) { + if (intersection == null) { + intersection = ends; + } else { + intersection = CollectionUtil.intersect(intersection, ends); + } + if (intersection == null || intersection.isEmpty()) { + return CrosspointsPaths.EMPTY; + } + } + assert intersection != null; + // Limit intersection number to limit crosspoints vertices in result + int size = intersection.size(); + if (limit != NO_LIMIT && size > limit) { + intersection = newList(intersection).subList(0, size - 1); + } + + // Filter intersection paths + List results = newList(); + for (Path path : paths) { + List vertices = path.vertices(); + int length = vertices.size(); + if (intersection.contains(vertices.get(length - 1))) { + results.add(path); + } + } + return new CrosspointsPaths(newSet(intersection), results); + } + + public EdgeRecord edgeResults() { + return edgeResults; } public CrosspointsPaths crosspointsPaths(Iterator vertices, @@ -64,6 +120,8 @@ public CrosspointsPaths crosspointsPaths(Iterator vertices, initialSources.add(vertex.id(), node); } List paths = newList(); + long edgeCount = 0L; + long vertexCount = 0L; for (PathPattern pathPattern : pathPatterns) { MultivaluedMap sources = initialSources; @@ -79,9 +137,14 @@ public CrosspointsPaths crosspointsPaths(Iterator vertices, for (Map.Entry> entry : sources.entrySet()) { List adjacency = newList(); edges = this.edgesOfVertex(entry.getKey(), step.edgeStep); + vertexCount += 1; while (edges.hasNext()) { HugeEdge edge = (HugeEdge) edges.next(); + edgeCount += 1; Id target = edge.id().otherVertexId(); + + this.edgeResults.addEdge(entry.getKey(), target, edge); + for (Node n : entry.getValue()) { // If have loop, skip target if (n.contains(target)) { @@ -104,67 +167,21 @@ public CrosspointsPaths crosspointsPaths(Iterator vertices, sources = newVertices; } assert stepNum == 0; + assert newVertices != null; for (List nodes : newVertices.values()) { for (Node n : nodes) { paths.add(new Path(n.path())); } } } + this.vertexIterCounter.addAndGet(vertexCount); + this.edgeIterCounter.addAndGet(edgeCount); return intersectionPaths(verticesList, paths, limit); } - private static CrosspointsPaths intersectionPaths(List sources, - List paths, - long limit) { - // Split paths by end vertices - MultivaluedMap endVertices = newMultivalueMap(); - for (Path path : paths) { - List vertices = path.vertices(); - int length = vertices.size(); - endVertices.add(vertices.get(0), vertices.get(length - 1)); - } - - Set sourceIds = sources.stream().map(HugeVertex::id) - .collect(Collectors.toSet()); - Set ids = endVertices.keySet(); - if (sourceIds.size() != ids.size() || !sourceIds.containsAll(ids)) { - return CrosspointsPaths.EMPTY; - } - - // Get intersection of end vertices - Collection intersection = null; - for (List ends : endVertices.values()) { - if (intersection == null) { - intersection = ends; - } else { - intersection = CollectionUtil.intersect(intersection, ends); - } - if (intersection == null || intersection.isEmpty()) { - return CrosspointsPaths.EMPTY; - } - } - assert intersection != null; - // Limit intersection number to limit crosspoints vertices in result - int size = intersection.size(); - if (limit != NO_LIMIT && size > limit) { - intersection = newList(intersection).subList(0, size - 1); - } - - // Filter intersection paths - List results = newList(); - for (Path path : paths) { - List vertices = path.vertices(); - int length = vertices.size(); - if (intersection.contains(vertices.get(length - 1))) { - results.add(path); - } - } - return new CrosspointsPaths(newSet(intersection), results); - } - public static class PathPattern { - private List steps; + private final List steps; public PathPattern() { this.steps = newList(); @@ -201,8 +218,8 @@ public static class CrosspointsPaths { ImmutableSet.of(), ImmutableList.of() ); - private Set crosspoints; - private List paths; + private final Set crosspoints; + private final List paths; public CrosspointsPaths(Set crosspoints, List paths) { this.crosspoints = crosspoints; diff --git a/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/algorithm/FusiformSimilarityTraverser.java b/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/algorithm/FusiformSimilarityTraverser.java index b39ea009ff..b322167c28 100644 --- a/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/algorithm/FusiformSimilarityTraverser.java +++ b/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/algorithm/FusiformSimilarityTraverser.java @@ -22,27 +22,27 @@ import java.util.Map; import java.util.Set; -import jakarta.ws.rs.core.MultivaluedHashMap; -import jakarta.ws.rs.core.MultivaluedMap; - import org.apache.commons.lang3.mutable.MutableInt; import org.apache.hugegraph.HugeGraph; import org.apache.hugegraph.backend.id.Id; import org.apache.hugegraph.schema.EdgeLabel; +import org.apache.hugegraph.structure.HugeEdge; +import org.apache.hugegraph.structure.HugeVertex; import org.apache.hugegraph.type.define.Directions; import org.apache.hugegraph.type.define.Frequency; +import org.apache.hugegraph.util.E; +import org.apache.hugegraph.util.InsertionOrderUtil; import org.apache.tinkerpop.gremlin.structure.Edge; import org.apache.tinkerpop.gremlin.structure.Vertex; import org.apache.tinkerpop.gremlin.util.iterator.IteratorUtils; -import org.apache.hugegraph.structure.HugeEdge; -import org.apache.hugegraph.structure.HugeVertex; -import org.apache.hugegraph.util.E; -import org.apache.hugegraph.util.InsertionOrderUtil; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; +import jakarta.ws.rs.core.MultivaluedHashMap; +import jakarta.ws.rs.core.MultivaluedMap; + public class FusiformSimilarityTraverser extends HugeTraverser { private long accessed = 0L; @@ -51,6 +51,20 @@ public FusiformSimilarityTraverser(HugeGraph graph) { super(graph); } + private static void checkGroupArgs(String groupProperty, int minGroups) { + if (groupProperty == null) { + E.checkArgument(minGroups == 0, + "Can't set min group count when " + + "group property not set"); + } else { + E.checkArgument(!groupProperty.isEmpty(), + "The group property can't be empty"); + E.checkArgument(minGroups > 0, + "Must set min group count when " + + "group property set"); + } + } + public SimilarsMap fusiformSimilarity(Iterator vertices, Directions direction, String label, int minNeighbors, double alpha, @@ -69,10 +83,10 @@ public SimilarsMap fusiformSimilarity(Iterator vertices, HugeVertex vertex = (HugeVertex) vertices.next(); // Find fusiform similarity for current vertex Set result = this.fusiformSimilarityForVertex( - vertex, direction, label, - minNeighbors, alpha, minSimilars, top, - groupProperty, minGroups, degree, capacity, - withIntermediary); + vertex, direction, label, + minNeighbors, alpha, minSimilars, top, + groupProperty, minGroups, degree, capacity, + withIntermediary); if (result.isEmpty()) { continue; } @@ -87,11 +101,11 @@ public SimilarsMap fusiformSimilarity(Iterator vertices, } private Set fusiformSimilarityForVertex( - HugeVertex vertex, Directions direction, - String label, int minNeighbors, double alpha, - int minSimilars, int top, String groupProperty, - int minGroups, long degree, long capacity, - boolean withIntermediary) { + HugeVertex vertex, Directions direction, + String label, int minNeighbors, double alpha, + int minSimilars, int top, String groupProperty, + int minGroups, long degree, long capacity, + boolean withIntermediary) { boolean matched = this.matchMinNeighborCount(vertex, direction, label, minNeighbors, degree); if (!matched) { @@ -105,6 +119,7 @@ private Set fusiformSimilarityForVertex( Map similars = newMap(); MultivaluedMap intermediaries = new MultivaluedHashMap<>(); Set neighbors = newIdSet(); + long vertexCount = 1L; while (edges.hasNext()) { Id target = ((HugeEdge) edges.next()).id().otherVertexId(); if (neighbors.contains(target)) { @@ -116,6 +131,7 @@ private Set fusiformSimilarityForVertex( Directions backDir = direction.opposite(); Iterator backEdges = this.edgesOfVertex(target, backDir, labelId, degree); + vertexCount += 1L; Set currentSimilars = newIdSet(); while (backEdges.hasNext()) { Id node = ((HugeEdge) backEdges.next()).id().otherVertexId(); @@ -137,6 +153,9 @@ private Set fusiformSimilarityForVertex( count.increment(); } } + this.edgeIterCounter.addAndGet(this.accessed); + this.vertexIterCounter.addAndGet(vertexCount); + // Delete source vertex assert similars.containsKey(vertex.id()); similars.remove(vertex.id()); @@ -189,20 +208,6 @@ private Set fusiformSimilarityForVertex( return result; } - private static void checkGroupArgs(String groupProperty, int minGroups) { - if (groupProperty == null) { - E.checkArgument(minGroups == 0, - "Can't set min group count when " + - "group property not set"); - } else { - E.checkArgument(!groupProperty.isEmpty(), - "The group property can't be empty"); - E.checkArgument(minGroups > 0, - "Must set min group count when " + - "group property set"); - } - } - private boolean matchMinNeighborCount(HugeVertex vertex, Directions direction, String label, @@ -249,7 +254,7 @@ public Similar(Id id, double score, List intermediaries) { this.id = id; this.score = score; assert newSet(intermediaries).size() == intermediaries.size() : - "Invalid intermediaries"; + "Invalid intermediaries"; this.intermediaries = intermediaries; } diff --git a/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/algorithm/HugeTraverser.java b/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/algorithm/HugeTraverser.java index 21344e6b2a..c0d36f31bd 100644 --- a/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/algorithm/HugeTraverser.java +++ b/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/algorithm/HugeTraverser.java @@ -19,15 +19,16 @@ import java.util.Collection; import java.util.Collections; +import java.util.HashMap; +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 java.util.concurrent.ConcurrentHashMap; - -import jakarta.ws.rs.core.MultivaluedHashMap; -import jakarta.ws.rs.core.MultivaluedMap; +import java.util.concurrent.atomic.AtomicLong; +import java.util.stream.Collectors; import org.apache.commons.collections.CollectionUtils; import org.apache.hugegraph.HugeException; @@ -39,39 +40,38 @@ import org.apache.hugegraph.backend.query.QueryResults; import org.apache.hugegraph.backend.tx.GraphTransaction; import org.apache.hugegraph.config.CoreOptions; -import org.apache.hugegraph.schema.SchemaLabel; -import org.apache.hugegraph.traversal.algorithm.steps.EdgeStep; -import org.apache.hugegraph.type.HugeType; -import org.apache.hugegraph.type.define.CollectionType; -import org.apache.hugegraph.type.define.Directions; -import org.apache.hugegraph.type.define.HugeKeys; -import org.apache.hugegraph.util.collection.CollectionFactory; -import org.apache.tinkerpop.gremlin.structure.Edge; -import org.slf4j.Logger; - import org.apache.hugegraph.exception.NotFoundException; import org.apache.hugegraph.iterator.ExtendableIterator; import org.apache.hugegraph.iterator.FilterIterator; import org.apache.hugegraph.iterator.LimitIterator; import org.apache.hugegraph.iterator.MapperIterator; import org.apache.hugegraph.perf.PerfUtil.Watched; +import org.apache.hugegraph.schema.SchemaLabel; import org.apache.hugegraph.structure.HugeEdge; +import org.apache.hugegraph.traversal.algorithm.steps.EdgeStep; import org.apache.hugegraph.traversal.optimize.TraversalUtil; +import org.apache.hugegraph.type.HugeType; +import org.apache.hugegraph.type.define.CollectionType; +import org.apache.hugegraph.type.define.Directions; +import org.apache.hugegraph.type.define.HugeKeys; import org.apache.hugegraph.util.CollectionUtil; import org.apache.hugegraph.util.E; import org.apache.hugegraph.util.InsertionOrderUtil; import org.apache.hugegraph.util.Log; +import org.apache.hugegraph.util.collection.CollectionFactory; +import org.apache.hugegraph.util.collection.ObjectIntMapping; +import org.apache.hugegraph.util.collection.ObjectIntMappingFactory; +import org.apache.tinkerpop.gremlin.structure.Edge; +import org.slf4j.Logger; + import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; -public class HugeTraverser { - - protected static final Logger LOG = Log.logger(HugeTraverser.class); - - private HugeGraph graph; +import jakarta.ws.rs.core.MultivaluedHashMap; +import jakarta.ws.rs.core.MultivaluedMap; - private static CollectionFactory collectionFactory; +public class HugeTraverser { public static final String DEFAULT_CAPACITY = "10000000"; public static final String DEFAULT_ELEMENTS_LIMIT = "10000000"; @@ -82,13 +82,16 @@ public class HugeTraverser { public static final String DEFAULT_SAMPLE = "100"; public static final String DEFAULT_WEIGHT = "0"; public static final int DEFAULT_MAX_DEPTH = 5000; - - protected static final int MAX_VERTICES = 10; - // Empirical value of scan limit, with which results can be returned in 3s public static final String DEFAULT_PAGE_LIMIT = "100000"; - public static final long NO_LIMIT = -1L; + protected static final Logger LOG = Log.logger(HugeTraverser.class); + protected static final int MAX_VERTICES = 10; + private static CollectionFactory collectionFactory; + private final HugeGraph graph; + // for apimeasure + public AtomicLong edgeIterCounter = new AtomicLong(0); + public AtomicLong vertexIterCounter = new AtomicLong(0); public HugeTraverser(HugeGraph graph) { this.graph = graph; @@ -97,6 +100,178 @@ public HugeTraverser(HugeGraph graph) { } } + public static void checkDegree(long degree) { + checkPositiveOrNoLimit(degree, "max degree"); + } + + public static void checkCapacity(long capacity) { + checkPositiveOrNoLimit(capacity, "capacity"); + } + + public static void checkLimit(long limit) { + checkPositiveOrNoLimit(limit, "limit"); + } + + public static void checkPositive(long value, String name) { + E.checkArgument(value > 0, + "The %s parameter must be > 0, but got %s", + name, value); + } + + public static void checkPositiveOrNoLimit(long value, String name) { + E.checkArgument(value > 0L || value == NO_LIMIT, + "The %s parameter must be > 0 or == %s, but got: %s", + name, NO_LIMIT, value); + } + + public static void checkNonNegative(long value, String name) { + E.checkArgument(value >= 0L, + "The %s parameter must be >= 0, but got: %s", + name, value); + } + + public static void checkNonNegativeOrNoLimit(long value, String name) { + E.checkArgument(value >= 0L || value == NO_LIMIT, + "The %s parameter must be >= 0 or == %s, but got: %s", + name, NO_LIMIT, value); + } + + public static void checkCapacity(long capacity, long access, + String traverse) { + if (capacity != NO_LIMIT && access > capacity) { + throw new HugeException("Exceed capacity '%s' while finding %s", + capacity, traverse); + } + } + + public static void checkSkipDegree(long skipDegree, long degree, + long capacity) { + E.checkArgument(skipDegree >= 0L && + skipDegree <= Query.DEFAULT_CAPACITY, + "The skipped degree must be in [0, %s], but got '%s'", + Query.DEFAULT_CAPACITY, skipDegree); + if (capacity != NO_LIMIT) { + E.checkArgument(degree != NO_LIMIT && degree < capacity, + "The max degree must be < capacity"); + E.checkArgument(skipDegree < capacity, + "The skipped degree must be < capacity"); + } + if (skipDegree > 0L) { + E.checkArgument(degree != NO_LIMIT && skipDegree >= degree, + "The skipped degree must be >= max degree, " + + "but got skipped degree '%s' and max degree '%s'", + skipDegree, degree); + } + } + + public static > Map topN( + Map map, + boolean sorted, + long limit) { + if (sorted) { + map = CollectionUtil.sortByValue(map, false); + } + if (limit == NO_LIMIT || map.size() <= limit) { + return map; + } + Map results = InsertionOrderUtil.newMap(); + long count = 0L; + for (Map.Entry entry : map.entrySet()) { + results.put(entry.getKey(), entry.getValue()); + if (++count >= limit) { + break; + } + } + return results; + } + + public static Iterator skipSuperNodeIfNeeded(Iterator edges, + long degree, + long skipDegree) { + if (skipDegree <= 0L) { + return edges; + } + List edgeList = newList(); + for (int i = 1; edges.hasNext(); i++) { + Edge edge = edges.next(); + if (i <= degree) { + edgeList.add(edge); + } + if (i >= skipDegree) { + return QueryResults.emptyIterator(); + } + } + return edgeList.iterator(); + } + + protected static Set newIdSet() { + return collectionFactory.newIdSet(); + } + + protected static Set newSet() { + return newSet(false); + } + + protected static Set newSet(boolean concurrent) { + if (concurrent) { + return ConcurrentHashMap.newKeySet(); + } else { + return collectionFactory.newSet(); + } + } + + protected static Set newSet(int initialCapacity) { + return collectionFactory.newSet(initialCapacity); + } + + protected static Set newSet(Collection collection) { + return collectionFactory.newSet(collection); + } + + protected static List newList() { + return collectionFactory.newList(); + } + + protected static List newList(int initialCapacity) { + return collectionFactory.newList(initialCapacity); + } + + protected static List newList(Collection collection) { + return collectionFactory.newList(collection); + } + + protected static Map newMap() { + return collectionFactory.newMap(); + } + + protected static Map newMap(int initialCapacity) { + return collectionFactory.newMap(initialCapacity); + } + + protected static MultivaluedMap newMultivalueMap() { + return new MultivaluedHashMap<>(); + } + + protected static List joinPath(Node prev, Node back, boolean ring) { + // Get self path + List path = prev.path(); + + // Get reversed other path + List backPath = back.path(); + Collections.reverse(backPath); + + if (!ring) { + // Avoid loop in path + if (CollectionUtils.containsAny(path, backPath)) { + return ImmutableList.of(); + } + } + + // Append other path behind self path + path.addAll(backPath); + return path; + } + public HugeGraph graph() { return this.graph; } @@ -157,6 +332,15 @@ protected Set adjacentVertices(Id source, EdgeStep step) { return neighbors; } + protected Iterator adjacentVertices(Id source, Directions dir, + List labels, long limit) { + Iterator edges = this.edgesOfVertex(source, dir, labels, limit); + return new MapperIterator<>(edges, e -> { + HugeEdge edge = (HugeEdge) e; + return edge.id().otherVertexId(); + }); + } + @Watched protected Iterator edgesOfVertex(Id source, Directions dir, Id label, long limit) { @@ -189,9 +373,26 @@ protected Iterator edgesOfVertex(Id source, Directions dir, } long[] count = new long[1]; - return new LimitIterator<>(results, e -> { - return count[0]++ >= limit; - }); + return new LimitIterator<>(results, e -> count[0]++ >= limit); + } + + protected Iterator edgesOfVertex(Id source, Directions dir, + List labels, long limit) { + if (labels == null || labels.isEmpty()) { + return this.edgesOfVertex(source, dir, (Id) null, limit); + } + ExtendableIterator results = new ExtendableIterator<>(); + for (Id label : labels) { + E.checkNotNull(label, "edge label"); + results.extend(this.edgesOfVertex(source, dir, label, limit)); + } + + if (limit == NO_LIMIT) { + return results; + } + + long[] count = new long[1]; + return new LimitIterator<>(results, e -> count[0]++ >= limit); } protected Iterator edgesOfVertex(Id source, EdgeStep edgeStep) { @@ -253,7 +454,7 @@ private void fillFilterBySortKeys(Query query, Id[] edgeLabels, if (!GraphTransaction.matchFullEdgeSortKeys(condQuery, this.graph())) { Id label = condQuery.condition(HugeKeys.LABEL); E.checkArgument(false, "The properties %s does not match " + - "sort keys of edge label '%s'", + "sort keys of edge label '%s'", this.graph().mapPkId2Name(properties.keySet()), this.graph().edgeLabel(label).name()); } @@ -308,182 +509,10 @@ protected void checkVertexExist(Id vertexId, String name) { this.graph.vertex(vertexId); } catch (NotFoundException e) { throw new IllegalArgumentException(String.format( - "The %s with id '%s' does not exist", name, vertexId), e); - } - } - - public static void checkDegree(long degree) { - checkPositiveOrNoLimit(degree, "max degree"); - } - - public static void checkCapacity(long capacity) { - checkPositiveOrNoLimit(capacity, "capacity"); - } - - public static void checkLimit(long limit) { - checkPositiveOrNoLimit(limit, "limit"); - } - - public static void checkPositive(long value, String name) { - E.checkArgument(value > 0, - "The %s parameter must be > 0, but got %s", - name, value); - } - - public static void checkPositiveOrNoLimit(long value, String name) { - E.checkArgument(value > 0L || value == NO_LIMIT, - "The %s parameter must be > 0 or == %s, but got: %s", - name, NO_LIMIT, value); - } - - public static void checkNonNegative(long value, String name) { - E.checkArgument(value >= 0L, - "The %s parameter must be >= 0, but got: %s", - name, value); - } - - public static void checkNonNegativeOrNoLimit(long value, String name) { - E.checkArgument(value >= 0L || value == NO_LIMIT, - "The %s parameter must be >= 0 or == %s, but got: %s", - name, NO_LIMIT, value); - } - - public static void checkCapacity(long capacity, long access, - String traverse) { - if (capacity != NO_LIMIT && access > capacity) { - throw new HugeException("Exceed capacity '%s' while finding %s", - capacity, traverse); - } - } - - public static void checkSkipDegree(long skipDegree, long degree, - long capacity) { - E.checkArgument(skipDegree >= 0L && - skipDegree <= Query.DEFAULT_CAPACITY, - "The skipped degree must be in [0, %s], but got '%s'", - Query.DEFAULT_CAPACITY, skipDegree); - if (capacity != NO_LIMIT) { - E.checkArgument(degree != NO_LIMIT && degree < capacity, - "The max degree must be < capacity"); - E.checkArgument(skipDegree < capacity, - "The skipped degree must be < capacity"); - } - if (skipDegree > 0L) { - E.checkArgument(degree != NO_LIMIT && skipDegree >= degree, - "The skipped degree must be >= max degree, " + - "but got skipped degree '%s' and max degree '%s'", - skipDegree, degree); - } - } - - public static > Map topN( - Map map, - boolean sorted, - long limit) { - if (sorted) { - map = CollectionUtil.sortByValue(map, false); - } - if (limit == NO_LIMIT || map.size() <= limit) { - return map; - } - Map results = InsertionOrderUtil.newMap(); - long count = 0L; - for (Map.Entry entry : map.entrySet()) { - results.put(entry.getKey(), entry.getValue()); - if (++count >= limit) { - break; - } - } - return results; - } - - public static Iterator skipSuperNodeIfNeeded(Iterator edges, - long degree, - long skipDegree) { - if (skipDegree <= 0L) { - return edges; - } - List edgeList = newList(); - for (int i = 1; edges.hasNext(); i++) { - Edge edge = edges.next(); - if (i <= degree) { - edgeList.add(edge); - } - if (i >= skipDegree) { - return QueryResults.emptyIterator(); - } - } - return edgeList.iterator(); - } - - protected static Set newIdSet() { - return collectionFactory.newIdSet(); - } - - protected static Set newSet() { - return newSet(false); - } - - protected static Set newSet(boolean concurrent) { - if (concurrent) { - return ConcurrentHashMap.newKeySet(); - } else { - return collectionFactory.newSet(); + "The %s with id '%s' does not exist", name, vertexId), e); } } - protected static Set newSet(int initialCapacity) { - return collectionFactory.newSet(initialCapacity); - } - - protected static Set newSet(Collection collection) { - return collectionFactory.newSet(collection); - } - - protected static List newList() { - return collectionFactory.newList(); - } - - protected static List newList(int initialCapacity) { - return collectionFactory.newList(initialCapacity); - } - - protected static List newList(Collection collection) { - return collectionFactory.newList(collection); - } - - protected static Map newMap() { - return collectionFactory.newMap(); - } - - protected static Map newMap(int initialCapacity) { - return collectionFactory.newMap(initialCapacity); - } - - protected static MultivaluedMap newMultivalueMap() { - return new MultivaluedHashMap<>(); - } - - protected static List joinPath(Node prev, Node back, boolean ring) { - // Get self path - List path = prev.path(); - - // Get reversed other path - List backPath = back.path(); - Collections.reverse(backPath); - - if (!ring) { - // Avoid loop in path - if (CollectionUtils.containsAny(path, backPath)) { - return ImmutableList.of(); - } - } - - // Append other path behind self path - path.addAll(backPath); - return path; - } - public static class Node { private final Id id; @@ -560,6 +589,7 @@ public static class Path { private final Id crosspoint; private final List vertices; + private Set edges = Collections.emptySet(); public Path(List vertices) { this(null, vertices); @@ -570,6 +600,19 @@ public Path(Id crosspoint, List vertices) { this.vertices = vertices; } + public Path(List vertices, Set edges) { + this(null, vertices); + this.edges = edges; + } + + public Set getEdges() { + return edges; + } + + public void setEdges(Set edges) { + this.edges = edges; + } + public Id crosspoint() { return this.crosspoint; } @@ -615,6 +658,7 @@ public int hashCode() { * Compares the specified object with this path for equality. * Returns true if and only if both have same vertices list * without regard of crosspoint. + * * @param other the object to be compared for equality with this path * @return true if the specified object is equal to this path */ @@ -638,6 +682,13 @@ public static class PathSet implements Set { private final Set paths; + private Set edges = Collections.emptySet(); + + public PathSet(Set paths, Set edges) { + this(paths); + this.edges = edges; + } + public PathSet() { this(newSet()); } @@ -646,6 +697,18 @@ private PathSet(Set paths) { this.paths = paths; } + public Set getPaths() { + return this.paths; + } + + public Set getEdges() { + return edges; + } + + public void setEdges(Set edges) { + this.edges = edges; + } + @Override public boolean add(Path path) { return this.paths.add(path); @@ -729,7 +792,7 @@ public String toString() { } public void append(Id current) { - for (Iterator iter = paths.iterator(); iter.hasNext();) { + for (Iterator iter = paths.iterator(); iter.hasNext(); ) { Path path = iter.next(); if (path.vertices().contains(current)) { iter.remove(); @@ -739,4 +802,80 @@ public void append(Id current) { } } } + + public static class EdgeRecord { + private final Map edgeMap; + private final ObjectIntMapping idMapping; + + public EdgeRecord(boolean concurrent) { + this.edgeMap = new HashMap<>(); + this.idMapping = ObjectIntMappingFactory.newObjectIntMapping(concurrent); + } + + private static Long makeVertexPairIndex(int source, int target) { + return ((long) source & 0xFFFFFFFFL) | + (((long) target << 32) & 0xFFFFFFFF00000000L); + } + + public static Set getEdgeIds(Set edges) { + return edges.stream().map(edge -> ((HugeEdge) edge).id()).collect(Collectors.toSet()); + } + + private int code(Id id) { + if (id.number()) { + long l = id.asLong(); + if (0 <= l && l <= Integer.MAX_VALUE) { + return (int) l; + } + } + int code = this.idMapping.object2Code(id); + assert code > 0; + return -code; + } + + public void addEdge(Id source, Id target, Edge edge) { + Long index = makeVertexPairIndex(this.code(source), this.code(target)); + this.edgeMap.put(index, edge); + } + + private Edge getEdge(Id source, Id target) { + Long index = makeVertexPairIndex(this.code(source), this.code(target)); + return this.edgeMap.get(index); + } + + public Set getEdges(HugeTraverser.Path path) { + if (path == null || path.vertices().isEmpty()) { + return new HashSet<>(); + } + Iterator vertexIter = path.vertices().iterator(); + return getEdges(vertexIter); + } + + public Set getEdges(Collection paths) { + Set edgeIds = new HashSet<>(); + for (HugeTraverser.Path path : paths) { + edgeIds.addAll(getEdges(path)); + } + return edgeIds; + } + + public Set getEdges(Iterator vertexIter) { + Set edges = new HashSet<>(); + Id first = vertexIter.next(); + Id second; + while (vertexIter.hasNext()) { + second = vertexIter.next(); + Edge edge = getEdge(first, second); + if (edge == null) { + edge = getEdge(second, first); + } + if (edge != null) { + edges.add(edge); + } + first = second; + } + return edges; + } + + } } diff --git a/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/algorithm/JaccardSimilarTraverser.java b/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/algorithm/JaccardSimilarTraverser.java index 240792abe2..a5539cd0f6 100644 --- a/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/algorithm/JaccardSimilarTraverser.java +++ b/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/algorithm/JaccardSimilarTraverser.java @@ -27,10 +27,10 @@ import org.apache.hugegraph.backend.id.Id; import org.apache.hugegraph.traversal.algorithm.steps.EdgeStep; import org.apache.hugegraph.type.define.Directions; -import org.apache.tinkerpop.gremlin.util.iterator.IteratorUtils; - import org.apache.hugegraph.util.CollectionUtil; import org.apache.hugegraph.util.E; +import org.apache.tinkerpop.gremlin.util.iterator.IteratorUtils; + import com.google.common.collect.ImmutableMap; public class JaccardSimilarTraverser extends OltpTraverser { @@ -39,6 +39,12 @@ public JaccardSimilarTraverser(HugeGraph graph) { super(graph); } + private static void reachCapacity(long count, long capacity) { + if (capacity != NO_LIMIT && count > capacity) { + throw new HugeException("Reach capacity '%s'", capacity); + } + } + public double jaccardSimilarity(Id vertex, Id other, Directions dir, String label, long degree) { E.checkNotNull(vertex, "vertex id"); @@ -51,9 +57,14 @@ public double jaccardSimilarity(Id vertex, Id other, Directions dir, Id labelId = this.getEdgeLabelId(label); Set sourceNeighbors = IteratorUtils.set(this.adjacentVertices( - vertex, dir, labelId, degree)); + vertex, dir, labelId, degree)); Set targetNeighbors = IteratorUtils.set(this.adjacentVertices( - other, dir, labelId, degree)); + other, dir, labelId, degree)); + + this.vertexIterCounter.addAndGet(2L); + this.edgeIterCounter.addAndGet(sourceNeighbors.size()); + this.edgeIterCounter.addAndGet(targetNeighbors.size()); + return jaccardSimilarity(sourceNeighbors, targetNeighbors); } @@ -96,6 +107,10 @@ public Map jaccardSimilarsConcurrent(Id source, EdgeStep step, // Query neighbors Set layer1s = this.adjacentVertices(source, step); + + this.vertexIterCounter.addAndGet(1L); + this.edgeIterCounter.addAndGet(layer1s.size()); + reachCapacity(count.get() + layer1s.size(), capacity); count.addAndGet(layer1s.size()); if (layer1s.isEmpty()) { @@ -111,6 +126,10 @@ public Map jaccardSimilarsConcurrent(Id source, EdgeStep step, return; } Set layer2s = this.adjacentVertices(id, step); + + this.vertexIterCounter.addAndGet(1L); + this.edgeIterCounter.addAndGet(layer2s.size()); + if (layer2s.isEmpty()) { results.put(id, 0.0D); } @@ -130,6 +149,10 @@ public Map jaccardSimilarsConcurrent(Id source, EdgeStep step, return; } Set layer3s = this.adjacentVertices(id, step); + + this.vertexIterCounter.addAndGet(1L); + this.edgeIterCounter.addAndGet(layer3s.size()); + reachCapacity(count.get() + layer3s.size(), capacity); if (layer3s.isEmpty()) { results.put(id, 0.0D); @@ -152,6 +175,10 @@ public Map jaccardSimilarsSingle(Id source, EdgeStep step, // Query neighbors Set layer1s = this.adjacentVertices(source, step); + + this.vertexIterCounter.addAndGet(1L); + this.edgeIterCounter.addAndGet(layer1s.size()); + reachCapacity(count + layer1s.size(), capacity); count += layer1s.size(); if (layer1s.isEmpty()) { @@ -168,6 +195,10 @@ public Map jaccardSimilarsSingle(Id source, EdgeStep step, continue; } layer2s = this.adjacentVertices(neighbor, step); + + this.vertexIterCounter.addAndGet(1L); + this.edgeIterCounter.addAndGet(layer2s.size()); + if (layer2s.isEmpty()) { results.put(neighbor, 0.0D); continue; @@ -188,6 +219,10 @@ public Map jaccardSimilarsSingle(Id source, EdgeStep step, continue; } layer3s = this.adjacentVertices(neighbor, step); + + this.vertexIterCounter.addAndGet(1L); + this.edgeIterCounter.addAndGet(layer3s.size()); + reachCapacity(count + layer3s.size(), capacity); if (layer3s.isEmpty()) { results.put(neighbor, 0.0D); @@ -201,10 +236,4 @@ public Map jaccardSimilarsSingle(Id source, EdgeStep step, return results; } - - private static void reachCapacity(long count, long capacity) { - if (capacity != NO_LIMIT && count > capacity) { - throw new HugeException("Reach capacity '%s'", capacity); - } - } } diff --git a/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/algorithm/KneighborTraverser.java b/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/algorithm/KneighborTraverser.java index c9381cd423..b3ae29ac8f 100644 --- a/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/algorithm/KneighborTraverser.java +++ b/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/algorithm/KneighborTraverser.java @@ -23,13 +23,12 @@ import org.apache.hugegraph.HugeGraph; import org.apache.hugegraph.backend.id.Id; +import org.apache.hugegraph.structure.HugeEdge; import org.apache.hugegraph.traversal.algorithm.records.KneighborRecords; import org.apache.hugegraph.traversal.algorithm.steps.EdgeStep; import org.apache.hugegraph.type.define.Directions; -import org.apache.tinkerpop.gremlin.structure.Edge; - -import org.apache.hugegraph.structure.HugeEdge; import org.apache.hugegraph.util.E; +import org.apache.tinkerpop.gremlin.structure.Edge; public class KneighborTraverser extends OltpTraverser { @@ -53,12 +52,15 @@ public Set kneighbor(Id sourceV, Directions dir, Set all = newSet(); latest.add(sourceV); + this.vertexIterCounter.addAndGet(1L); while (depth-- > 0) { long remaining = limit == NO_LIMIT ? NO_LIMIT : limit - all.size(); latest = this.adjacentVertices(sourceV, latest, dir, labelId, all, degree, remaining); all.addAll(latest); + this.vertexIterCounter.addAndGet(1L); + this.edgeIterCounter.addAndGet(latest.size()); if (reachLimit(limit, all.size())) { break; } @@ -84,9 +86,15 @@ public KneighborRecords customizedKneighbor(Id source, EdgeStep step, return; } Iterator edges = edgesOfVertex(v, step); + this.vertexIterCounter.addAndGet(1L); while (!this.reachLimit(limit, records.size()) && edges.hasNext()) { - Id target = ((HugeEdge) edges.next()).id().otherVertexId(); + HugeEdge edge = (HugeEdge) edges.next(); + Id target = edge.id().otherVertexId(); records.addPath(v, target); + + records.edgeResults().addEdge(v, target, edge); + + this.edgeIterCounter.addAndGet(1L); } }; diff --git a/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/algorithm/KoutTraverser.java b/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/algorithm/KoutTraverser.java index 2c268c94b4..9f40be8fbd 100644 --- a/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/algorithm/KoutTraverser.java +++ b/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/algorithm/KoutTraverser.java @@ -24,13 +24,12 @@ import org.apache.hugegraph.HugeException; import org.apache.hugegraph.HugeGraph; import org.apache.hugegraph.backend.id.Id; +import org.apache.hugegraph.structure.HugeEdge; import org.apache.hugegraph.traversal.algorithm.records.KoutRecords; import org.apache.hugegraph.traversal.algorithm.steps.EdgeStep; import org.apache.hugegraph.type.define.Directions; -import org.apache.tinkerpop.gremlin.structure.Edge; - -import org.apache.hugegraph.structure.HugeEdge; import org.apache.hugegraph.util.E; +import org.apache.tinkerpop.gremlin.structure.Edge; public class KoutTraverser extends OltpTraverser { @@ -66,6 +65,7 @@ public Set kout(Id sourceV, Directions dir, String label, long remaining = capacity == NO_LIMIT ? NO_LIMIT : capacity - latest.size(); + this.vertexIterCounter.addAndGet(1L); while (depth-- > 0) { // Just get limit nodes in last layer if limit < remaining capacity if (depth == 0 && limit != NO_LIMIT && @@ -80,14 +80,16 @@ public Set kout(Id sourceV, Directions dir, String label, latest = this.adjacentVertices(sourceV, latest, dir, labelId, null, degree, remaining); } + this.vertexIterCounter.addAndGet(1L); + this.edgeIterCounter.addAndGet(latest.size()); if (capacity != NO_LIMIT) { // Update 'remaining' value to record remaining capacity remaining -= latest.size(); if (remaining <= 0 && depth > 0) { throw new HugeException( - "Reach capacity '%s' while remaining depth '%s'", - capacity, depth); + "Reach capacity '%s' while remaining depth '%s'", + capacity, depth); } } } @@ -114,11 +116,17 @@ public KoutRecords customizedKout(Id source, EdgeStep step, return; } Iterator edges = edgesOfVertex(v, step); + this.vertexIterCounter.addAndGet(1L); while (!this.reachLimit(limit, depth[0], records.size()) && edges.hasNext()) { - Id target = ((HugeEdge) edges.next()).id().otherVertexId(); + HugeEdge edge = (HugeEdge) edges.next(); + Id target = edge.id().otherVertexId(); records.addPath(v, target); this.checkCapacity(capacity, records.accessed(), depth[0]); + + records.edgeResults().addEdge(v, target, edge); + + this.edgeIterCounter.addAndGet(1L); } }; @@ -136,8 +144,8 @@ private void checkCapacity(long capacity, long accessed, long depth) { } if (accessed >= capacity && depth > 0) { throw new HugeException( - "Reach capacity '%s' while remaining depth '%s'", - capacity, depth); + "Reach capacity '%s' while remaining depth '%s'", + capacity, depth); } } diff --git a/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/algorithm/MultiNodeShortestPathTraverser.java b/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/algorithm/MultiNodeShortestPathTraverser.java index 493b97286d..aa498fa956 100644 --- a/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/algorithm/MultiNodeShortestPathTraverser.java +++ b/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/algorithm/MultiNodeShortestPathTraverser.java @@ -19,28 +19,55 @@ import java.util.Iterator; import java.util.List; +import java.util.Set; import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.CopyOnWriteArraySet; import java.util.function.Consumer; import org.apache.commons.lang3.tuple.Pair; import org.apache.hugegraph.HugeGraph; import org.apache.hugegraph.backend.id.Id; +import org.apache.hugegraph.structure.HugeVertex; import org.apache.hugegraph.traversal.algorithm.steps.EdgeStep; +import org.apache.hugegraph.util.E; +import org.apache.tinkerpop.gremlin.structure.Edge; import org.apache.tinkerpop.gremlin.structure.Vertex; import org.apache.tinkerpop.gremlin.util.iterator.IteratorUtils; -import org.apache.hugegraph.structure.HugeVertex; -import org.apache.hugegraph.util.E; - public class MultiNodeShortestPathTraverser extends OltpTraverser { public MultiNodeShortestPathTraverser(HugeGraph graph) { super(graph); } - public List multiNodeShortestPath(Iterator vertices, - EdgeStep step, int maxDepth, - long capacity) { + private static void cmn(List all, int m, int n, int current, + List result, Consumer> consumer) { + assert m <= all.size(); + assert current <= all.size(); + if (result == null) { + result = newList(n); + } + if (n == 0) { + // All n items are selected + consumer.accept(result); + return; + } + if (m < n || current >= all.size()) { + return; + } + + // Select current item, continue to select C(m-1, n-1) + int index = result.size(); + result.add(all.get(current)); + cmn(all, m - 1, n - 1, ++current, result, consumer); + // Not select current item, continue to select C(m-1, n) + result.remove(index); + cmn(all, m - 1, n, current, result, consumer); + } + + public WrappedListPath multiNodeShortestPath(Iterator vertices, + EdgeStep step, int maxDepth, + long capacity) { List vertexList = IteratorUtils.list(vertices); int vertexCount = vertexList.size(); E.checkState(vertexCount >= 2 && vertexCount <= MAX_VERTICES, @@ -56,70 +83,70 @@ public List multiNodeShortestPath(Iterator vertices, }); if (maxDepth >= this.concurrentDepth() && vertexCount > 10) { - return this.multiNodeShortestPathConcurrent(pairs, step, - maxDepth, capacity); + return this.multiNodeShortestPathConcurrent(pairs, step, maxDepth, capacity); } else { - return this.multiNodeShortestPathSingle(pairs, step, - maxDepth, capacity); + return this.multiNodeShortestPathSingle(pairs, step, maxDepth, capacity); } } - public List multiNodeShortestPathConcurrent(List> pairs, - EdgeStep step, - int maxDepth, - long capacity) { - List results = new CopyOnWriteArrayList<>(); + public WrappedListPath multiNodeShortestPathConcurrent(List> pairs, + EdgeStep step, int maxDepth, + long capacity) { + List paths = new CopyOnWriteArrayList<>(); + Set edges = new CopyOnWriteArraySet<>(); ShortestPathTraverser traverser = - new ShortestPathTraverser(this.graph()); + new ShortestPathTraverser(this.graph()); this.traversePairs(pairs.iterator(), pair -> { Path path = traverser.shortestPath(pair.getLeft(), pair.getRight(), step, maxDepth, capacity); if (!Path.EMPTY.equals(path)) { - results.add(path); + paths.add(path); } + edges.addAll(path.getEdges()); }); + this.vertexIterCounter.addAndGet(traverser.vertexIterCounter.get()); + this.edgeIterCounter.addAndGet(traverser.edgeIterCounter.get()); - return results; + return new WrappedListPath(paths, edges); } - public List multiNodeShortestPathSingle(List> pairs, - EdgeStep step, int maxDepth, - long capacity) { - List results = newList(); + public WrappedListPath multiNodeShortestPathSingle(List> pairs, + EdgeStep step, int maxDepth, + long capacity) { + List paths = newList(); + Set edges = newSet(); ShortestPathTraverser traverser = - new ShortestPathTraverser(this.graph()); + new ShortestPathTraverser(this.graph()); for (Pair pair : pairs) { Path path = traverser.shortestPath(pair.getLeft(), pair.getRight(), step, maxDepth, capacity); if (!Path.EMPTY.equals(path)) { - results.add(path); + paths.add(path); } + edges.addAll(path.getEdges()); } - return results; + this.vertexIterCounter.addAndGet(traverser.vertexIterCounter.get()); + this.edgeIterCounter.addAndGet(traverser.edgeIterCounter.get()); + + return new WrappedListPath(paths, edges); } - private static void cmn(List all, int m, int n, int current, - List result, Consumer> consumer) { - assert m <= all.size(); - assert current <= all.size(); - if (result == null) { - result = newList(n); - } - if (n == 0) { - // All n items are selected - consumer.accept(result); - return; + public static class WrappedListPath { + + private final List paths; + private final Set edges; + + public WrappedListPath(List paths, Set edges) { + this.paths = paths; + this.edges = edges; } - if (m < n || current >= all.size()) { - return; + + public List paths() { + return paths; } - // Select current item, continue to select C(m-1, n-1) - int index = result.size(); - result.add(all.get(current)); - cmn(all, m - 1, n - 1, ++current, result, consumer); - // Not select current item, continue to select C(m-1, n) - result.remove(index); - cmn(all, m - 1, n, current, result, consumer); + public Set edges() { + return edges; + } } } diff --git a/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/algorithm/PathTraverser.java b/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/algorithm/PathTraverser.java index 7ae281640d..ac98872c41 100644 --- a/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/algorithm/PathTraverser.java +++ b/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/algorithm/PathTraverser.java @@ -17,6 +17,8 @@ package org.apache.hugegraph.traversal.algorithm; +import static org.apache.hugegraph.traversal.algorithm.HugeTraverser.NO_LIMIT; + import java.util.Collection; import java.util.Iterator; import java.util.List; @@ -25,21 +27,18 @@ import java.util.function.BiConsumer; import org.apache.hugegraph.backend.id.Id; +import org.apache.hugegraph.structure.HugeEdge; +import org.apache.hugegraph.traversal.algorithm.HugeTraverser.EdgeRecord; import org.apache.hugegraph.traversal.algorithm.steps.EdgeStep; import org.apache.hugegraph.traversal.algorithm.strategy.TraverseStrategy; import org.apache.tinkerpop.gremlin.structure.Edge; -import org.apache.hugegraph.structure.HugeEdge; - -import static org.apache.hugegraph.traversal.algorithm.HugeTraverser.NO_LIMIT; - public abstract class PathTraverser { protected final HugeTraverser traverser; - - protected int stepCount; protected final long capacity; protected final long limit; + protected int stepCount; protected int totalSteps; // TODO: delete or implement abstract method protected Map> sources; @@ -52,10 +51,11 @@ public abstract class PathTraverser { protected Set paths; protected TraverseStrategy traverseStrategy; + protected EdgeRecord edgeResults; public PathTraverser(HugeTraverser traverser, TraverseStrategy strategy, Collection sources, Collection targets, - long capacity, long limit) { + long capacity, long limit, boolean concurrent) { this.traverser = traverser; this.traverseStrategy = strategy; @@ -79,6 +79,8 @@ public PathTraverser(HugeTraverser traverser, TraverseStrategy strategy, this.targetsAll.putAll(this.targets); this.paths = this.newPathSet(); + + this.edgeResults = new EdgeRecord(concurrent); } public void forward() { @@ -145,9 +147,13 @@ private void traverseOne(Id v, EdgeStep step, boolean forward) { while (edges.hasNext()) { HugeEdge edge = (HugeEdge) edges.next(); Id target = edge.id().otherVertexId(); + this.traverser.edgeIterCounter.addAndGet(1L); + + this.edgeResults.addEdge(v, target, edge); this.processOne(v, target, forward); } + this.traverser.vertexIterCounter.addAndGet(1L); } private void processOne(Id source, Id target, boolean forward) { @@ -205,10 +211,7 @@ protected boolean finished() { protected boolean reachLimit() { HugeTraverser.checkCapacity(this.capacity, this.accessedNodes(), "template paths"); - if (this.limit == NO_LIMIT || this.pathCount() < this.limit) { - return false; - } - return true; + return this.limit != NO_LIMIT && this.pathCount() >= this.limit; } protected int accessedNodes() { diff --git a/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/algorithm/PathsTraverser.java b/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/algorithm/PathsTraverser.java index bdc9712c53..d8b256082d 100644 --- a/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/algorithm/PathsTraverser.java +++ b/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/algorithm/PathsTraverser.java @@ -21,13 +21,12 @@ import org.apache.hugegraph.HugeGraph; import org.apache.hugegraph.backend.id.Id; -import org.apache.hugegraph.traversal.algorithm.records.PathsRecords; -import org.apache.hugegraph.type.define.Directions; -import org.apache.tinkerpop.gremlin.structure.Edge; - import org.apache.hugegraph.perf.PerfUtil.Watched; import org.apache.hugegraph.structure.HugeEdge; +import org.apache.hugegraph.traversal.algorithm.records.PathsRecords; +import org.apache.hugegraph.type.define.Directions; import org.apache.hugegraph.util.E; +import org.apache.tinkerpop.gremlin.structure.Edge; public class PathsTraverser extends HugeTraverser { @@ -75,6 +74,8 @@ public PathSet paths(Id sourceV, Directions sourceDir, } traverser.backward(sourceV, targetDir); } + vertexIterCounter.addAndGet(traverser.vertexCounter); + edgeIterCounter.addAndGet(traverser.edgeCounter); return traverser.paths(); } @@ -88,6 +89,8 @@ private class Traverser { private final long limit; private final PathSet paths; + private long vertexCounter; + private long edgeCounter; public Traverser(Id sourceV, Id targetV, Id label, long degree, long capacity, long limit) { @@ -96,6 +99,8 @@ public Traverser(Id sourceV, Id targetV, Id label, this.degree = degree; this.capacity = capacity; this.limit = limit; + this.vertexCounter = 0L; + this.edgeCounter = 0L; this.paths = new PathSet(); } @@ -115,10 +120,11 @@ public void forward(Id targetV, Directions direction) { } edges = edgesOfVertex(vid, direction, this.label, this.degree); - + this.vertexCounter += 1L; while (edges.hasNext()) { HugeEdge edge = (HugeEdge) edges.next(); Id target = edge.id().otherVertexId(); + this.edgeCounter += 1L; PathSet results = this.record.findPath(target, null, true, false); @@ -148,10 +154,11 @@ public void backward(Id sourceV, Directions direction) { } edges = edgesOfVertex(vid, direction, this.label, this.degree); - + this.vertexCounter += 1L; while (edges.hasNext()) { HugeEdge edge = (HugeEdge) edges.next(); Id target = edge.id().otherVertexId(); + this.edgeCounter += 1L; PathSet results = this.record.findPath(target, null, true, false); @@ -175,5 +182,9 @@ private boolean reachLimit() { checkCapacity(this.capacity, this.record.accessed(), "paths"); return this.limit != NO_LIMIT && this.paths.size() >= this.limit; } + + public long accessed() { + return this.record.accessed(); + } } } diff --git a/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/algorithm/SameNeighborTraverser.java b/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/algorithm/SameNeighborTraverser.java index a551eb7cc2..06c5bec275 100644 --- a/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/algorithm/SameNeighborTraverser.java +++ b/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/algorithm/SameNeighborTraverser.java @@ -17,15 +17,17 @@ package org.apache.hugegraph.traversal.algorithm; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; import java.util.Set; import org.apache.hugegraph.HugeGraph; import org.apache.hugegraph.backend.id.Id; import org.apache.hugegraph.type.define.Directions; -import org.apache.tinkerpop.gremlin.util.iterator.IteratorUtils; - import org.apache.hugegraph.util.CollectionUtil; import org.apache.hugegraph.util.E; +import org.apache.tinkerpop.gremlin.util.iterator.IteratorUtils; public class SameNeighborTraverser extends HugeTraverser { @@ -46,11 +48,56 @@ public Set sameNeighbors(Id vertex, Id other, Directions direction, Id labelId = this.getEdgeLabelId(label); Set sourceNeighbors = IteratorUtils.set(this.adjacentVertices( - vertex, direction, labelId, degree)); + vertex, direction, labelId, degree)); Set targetNeighbors = IteratorUtils.set(this.adjacentVertices( - other, direction, labelId, degree)); + other, direction, labelId, degree)); Set sameNeighbors = (Set) CollectionUtil.intersect( - sourceNeighbors, targetNeighbors); + sourceNeighbors, targetNeighbors); + + this.vertexIterCounter.addAndGet(2L); + this.edgeIterCounter.addAndGet(sourceNeighbors.size()); + this.edgeIterCounter.addAndGet(targetNeighbors.size()); + + if (limit != NO_LIMIT) { + int end = Math.min(sameNeighbors.size(), limit); + sameNeighbors = CollectionUtil.subSet(sameNeighbors, 0, end); + } + return sameNeighbors; + } + + public Set sameNeighbors(List vertexIds, Directions direction, + List labels, long degree, int limit) { + E.checkNotNull(vertexIds, "vertex ids"); + E.checkArgument(vertexIds.size() >= 2, "vertex_list size can't " + + "be less than 2"); + for (Id id : vertexIds) { + this.checkVertexExist(id, "vertex"); + } + E.checkNotNull(direction, "direction"); + checkDegree(degree); + checkLimit(limit); + + List labelsId = new ArrayList<>(); + if (labels != null) { + for (String label : labels) { + labelsId.add(this.getEdgeLabelId(label)); + } + } + + Set sameNeighbors = new HashSet<>(); + for (int i = 0; i < vertexIds.size(); i++) { + Set vertexNeighbors = IteratorUtils.set(this.adjacentVertices( + vertexIds.get(i), direction, labelsId, degree)); + if (i == 0) { + sameNeighbors = vertexNeighbors; + } else { + sameNeighbors = (Set) CollectionUtil.intersect( + sameNeighbors, vertexNeighbors); + } + this.vertexIterCounter.addAndGet(1L); + this.edgeIterCounter.addAndGet(vertexNeighbors.size()); + } + if (limit != NO_LIMIT) { int end = Math.min(sameNeighbors.size(), limit); sameNeighbors = CollectionUtil.subSet(sameNeighbors, 0, end); diff --git a/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/algorithm/ShortestPathTraverser.java b/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/algorithm/ShortestPathTraverser.java index e9584191f0..45ccdc6345 100644 --- a/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/algorithm/ShortestPathTraverser.java +++ b/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/algorithm/ShortestPathTraverser.java @@ -20,18 +20,19 @@ import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.Set; import org.apache.hugegraph.HugeGraph; import org.apache.hugegraph.backend.id.Id; +import org.apache.hugegraph.perf.PerfUtil.Watched; +import org.apache.hugegraph.structure.HugeEdge; import org.apache.hugegraph.traversal.algorithm.records.ShortestPathRecords; import org.apache.hugegraph.traversal.algorithm.steps.EdgeStep; import org.apache.hugegraph.type.define.Directions; +import org.apache.hugegraph.util.E; import org.apache.tinkerpop.gremlin.structure.Edge; import org.apache.tinkerpop.gremlin.util.iterator.IteratorUtils; -import org.apache.hugegraph.perf.PerfUtil.Watched; -import org.apache.hugegraph.structure.HugeEdge; -import org.apache.hugegraph.util.E; import com.google.common.collect.ImmutableList; public class ShortestPathTraverser extends HugeTraverser { @@ -81,7 +82,15 @@ public Path shortestPath(Id sourceV, Id targetV, Directions dir, checkCapacity(traverser.capacity, traverser.accessed(), "shortest path"); } - return paths.isEmpty() ? Path.EMPTY : paths.iterator().next(); + + this.vertexIterCounter.addAndGet(traverser.vertexCount); + this.edgeIterCounter.addAndGet(traverser.pathResults.accessed()); + + Path path = paths.isEmpty() ? Path.EMPTY : paths.iterator().next(); + + Set edges = traverser.edgeResults.getEdges(path); + path.setEdges(edges); + return path; } public Path shortestPath(Id sourceV, Id targetV, EdgeStep step, @@ -126,31 +135,40 @@ public PathSet allShortestPaths(Id sourceV, Id targetV, Directions dir, checkCapacity(traverser.capacity, traverser.accessed(), "shortest path"); } + + this.vertexIterCounter.addAndGet(traverser.vertexCount); + this.edgeIterCounter.addAndGet(traverser.pathResults.accessed()); + + paths.setEdges(traverser.edgeResults.getEdges(paths)); return paths; } private class Traverser { - private final ShortestPathRecords record; + private final ShortestPathRecords pathResults; + private final EdgeRecord edgeResults; private final Directions direction; private final Map labels; private final long degree; private final long skipDegree; private final long capacity; + private long vertexCount; public Traverser(Id sourceV, Id targetV, Directions dir, Map labels, long degree, long skipDegree, long capacity) { - this.record = new ShortestPathRecords(sourceV, targetV); + this.pathResults = new ShortestPathRecords(sourceV, targetV); + this.edgeResults = new EdgeRecord(false); this.direction = dir; this.labels = labels; this.degree = degree; this.skipDegree = skipDegree; this.capacity = capacity; + this.vertexCount = 0L; } public PathSet traverse(boolean all) { - return this.record.sourcesLessThanTargets() ? + return this.pathResults.sourcesLessThanTargets() ? this.forward(all) : this.backward(all); } @@ -162,21 +180,26 @@ public PathSet forward(boolean all) { PathSet results = new PathSet(); long degree = this.skipDegree > 0L ? this.skipDegree : this.degree; - this.record.startOneLayer(true); - while (this.record.hasNextKey()) { - Id source = this.record.nextKey(); + this.pathResults.startOneLayer(true); + while (this.pathResults.hasNextKey()) { + Id source = this.pathResults.nextKey(); Iterator edges = edgesOfVertex(source, this.direction, this.labels, degree); edges = skipSuperNodeIfNeeded(edges, this.degree, this.skipDegree); + + this.vertexCount += 1L; + while (edges.hasNext()) { HugeEdge edge = (HugeEdge) edges.next(); Id target = edge.id().otherVertexId(); - PathSet paths = this.record.findPath(target, - t -> !this.superNode(t, this.direction), - all, false); + this.edgeResults.addEdge(source, target, edge); + + PathSet paths = this.pathResults.findPath(target, + t -> !this.superNode(t, this.direction), + all, false); if (paths.isEmpty()) { continue; @@ -186,9 +209,10 @@ public PathSet forward(boolean all) { return paths; } } + } - this.record.finishOneLayer(); + this.pathResults.finishOneLayer(); return results; } @@ -202,21 +226,26 @@ public PathSet backward(boolean all) { long degree = this.skipDegree > 0L ? this.skipDegree : this.degree; Directions opposite = this.direction.opposite(); - this.record.startOneLayer(false); - while (this.record.hasNextKey()) { - Id source = this.record.nextKey(); + this.pathResults.startOneLayer(false); + while (this.pathResults.hasNextKey()) { + Id source = this.pathResults.nextKey(); Iterator edges = edgesOfVertex(source, opposite, this.labels, degree); edges = skipSuperNodeIfNeeded(edges, this.degree, this.skipDegree); + + this.vertexCount += 1L; + while (edges.hasNext()) { HugeEdge edge = (HugeEdge) edges.next(); Id target = edge.id().otherVertexId(); - PathSet paths = this.record.findPath(target, - t -> !this.superNode(t, opposite), - all, false); + this.edgeResults.addEdge(source, target, edge); + + PathSet paths = this.pathResults.findPath(target, + t -> !this.superNode(t, opposite), + all, false); if (paths.isEmpty()) { continue; @@ -229,7 +258,7 @@ public PathSet backward(boolean all) { } // Re-init targets - this.record.finishOneLayer(); + this.pathResults.finishOneLayer(); return results; } @@ -244,7 +273,7 @@ private boolean superNode(Id vertex, Directions direction) { } private long accessed() { - return this.record.accessed(); + return this.pathResults.accessed(); } } } diff --git a/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/algorithm/SingleSourceShortestPathTraverser.java b/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/algorithm/SingleSourceShortestPathTraverser.java index 0929711402..12d90600ec 100644 --- a/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/algorithm/SingleSourceShortestPathTraverser.java +++ b/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/algorithm/SingleSourceShortestPathTraverser.java @@ -17,6 +17,9 @@ package org.apache.hugegraph.traversal.algorithm; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; @@ -26,14 +29,14 @@ import org.apache.hugegraph.HugeGraph; import org.apache.hugegraph.backend.id.Id; import org.apache.hugegraph.backend.query.QueryResults; -import org.apache.hugegraph.type.define.Directions; -import org.apache.tinkerpop.gremlin.structure.Edge; - import org.apache.hugegraph.structure.HugeEdge; +import org.apache.hugegraph.type.define.Directions; import org.apache.hugegraph.util.CollectionUtil; import org.apache.hugegraph.util.E; import org.apache.hugegraph.util.InsertionOrderUtil; import org.apache.hugegraph.util.NumericUtil; +import org.apache.tinkerpop.gremlin.structure.Edge; + import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; @@ -57,13 +60,21 @@ public WeightedPaths singleSourceShortestPaths(Id sourceV, Directions dir, Id labelId = this.getEdgeLabelId(label); Traverser traverser = new Traverser(sourceV, dir, labelId, weight, - degree, skipDegree, capacity, - limit); + degree, skipDegree, capacity, limit); while (true) { // Found, reach max depth or reach capacity, stop searching traverser.forward(); if (traverser.done()) { - return traverser.shortestPaths(); + this.vertexIterCounter.addAndGet(traverser.vertexCount); + this.edgeIterCounter.addAndGet(traverser.edgeCount); + WeightedPaths paths = traverser.shortestPaths(); + List> pathList = paths.pathList(); + Set edges = new HashSet<>(); + for (List path : pathList) { + edges.addAll(traverser.edgeRecord.getEdges(path.iterator())); + } + paths.setEdges(edges); + return paths; } checkCapacity(traverser.capacity, traverser.size, "shortest path"); } @@ -91,18 +102,107 @@ public NodeWithWeight weightedShortestPath(Id sourceV, Id targetV, traverser.forward(); Map results = traverser.shortestPaths(); if (results.containsKey(targetV) || traverser.done()) { - return results.get(targetV); + this.vertexIterCounter.addAndGet(traverser.vertexCount); + this.edgeIterCounter.addAndGet(traverser.edgeCount); + NodeWithWeight nodeWithWeight = results.get(targetV); + if (nodeWithWeight != null) { + Iterator vertexIter = nodeWithWeight.node.path().iterator(); + Set edges = traverser.edgeRecord.getEdges(vertexIter); + nodeWithWeight.setEdges(edges); + } + return nodeWithWeight; } checkCapacity(traverser.capacity, traverser.size, "shortest path"); } } + public static class NodeWithWeight implements Comparable { + + private final double weight; + private final Node node; + + private Set edges = Collections.emptySet(); + + public NodeWithWeight(double weight, Node node) { + this.weight = weight; + this.node = node; + } + + public NodeWithWeight(double weight, Id id, NodeWithWeight prio) { + this(weight, new Node(id, prio.node())); + } + + public Set getEdges() { + return edges; + } + + public void setEdges(Set edges) { + this.edges = edges; + } + + public double weight() { + return weight; + } + + public Node node() { + return this.node; + } + + public Map toMap() { + return ImmutableMap.of("weight", this.weight, + "vertices", this.node().path()); + } + + @Override + public int compareTo(NodeWithWeight other) { + return Double.compare(this.weight, other.weight); + } + } + + public static class WeightedPaths extends LinkedHashMap { + + private static final long serialVersionUID = -313873642177730993L; + private Set edges = Collections.emptySet(); + + public Set getEdges() { + return edges; + } + + public void setEdges(Set edges) { + this.edges = edges; + } + + public Set vertices() { + Set vertices = newIdSet(); + vertices.addAll(this.keySet()); + for (NodeWithWeight nw : this.values()) { + vertices.addAll(nw.node().path()); + } + return vertices; + } + + public List> pathList() { + List> pathList = new ArrayList<>(); + for (NodeWithWeight nw : this.values()) { + pathList.add(nw.node.path()); + } + return pathList; + } + + public Map> toMap() { + Map> results = newMap(); + for (Map.Entry entry : this.entrySet()) { + Id source = entry.getKey(); + NodeWithWeight nw = entry.getValue(); + Map result = nw.toMap(); + results.put(source, result); + } + return results; + } + } + private class Traverser { - private WeightedPaths findingNodes = new WeightedPaths(); - private WeightedPaths foundNodes = new WeightedPaths(); - private Set sources; - private Id source; private final Directions direction; private final Id label; private final String weight; @@ -110,15 +210,21 @@ private class Traverser { private final long skipDegree; private final long capacity; private final long limit; - private long size; + private final WeightedPaths findingNodes = new WeightedPaths(); + private final WeightedPaths foundNodes = new WeightedPaths(); + private final EdgeRecord edgeRecord; + private final Id source; + private final long size; + private Set sources; + private long vertexCount; + private long edgeCount; private boolean done = false; public Traverser(Id sourceV, Directions dir, Id label, String weight, - long degree, long skipDegree, long capacity, - long limit) { + long degree, long skipDegree, long capacity, long limit) { this.source = sourceV; this.sources = ImmutableSet.of(new NodeWithWeight( - 0D, new Node(sourceV, null))); + 0D, new Node(sourceV, null))); this.direction = dir; this.label = label; this.weight = weight; @@ -127,6 +233,9 @@ public Traverser(Id sourceV, Directions dir, Id label, String weight, this.capacity = capacity; this.limit = limit; this.size = 0L; + this.vertexCount = 0L; + this.edgeCount = 0L; + this.edgeRecord = new EdgeRecord(false); } /** @@ -143,12 +252,16 @@ public void forward() { HugeEdge edge = (HugeEdge) edges.next(); Id target = edge.id().otherVertexId(); + this.edgeCount += 1L; + if (this.foundNodes.containsKey(target) || this.source.equals(target)) { // Already find shortest path for target, skip continue; } + this.edgeRecord.addEdge(node.node().id(), target, edge); + double currentWeight = this.edgeWeight(edge); double weight = currentWeight + node.weight(); NodeWithWeight nw = new NodeWithWeight(weight, target, node); @@ -164,9 +277,10 @@ public void forward() { } } } + this.vertexCount += sources.size(); Map sorted = CollectionUtil.sortByValue( - this.findingNodes, true); + this.findingNodes, true); double minWeight = 0; Set newSources = InsertionOrderUtil.newSet(); for (Map.Entry entry : sorted.entrySet()) { @@ -209,7 +323,7 @@ private double edgeWeight(HugeEdge edge) { edgeWeight = 1.0; } else { edgeWeight = NumericUtil.convertToNumber( - edge.value(this.weight)).doubleValue(); + edge.value(this.weight)).doubleValue(); } return edgeWeight; } @@ -232,62 +346,4 @@ private Iterator skipSuperNodeIfNeeded(Iterator edges) { return edgeList.iterator(); } } - - public static class NodeWithWeight implements Comparable { - - private final double weight; - private final Node node; - - public NodeWithWeight(double weight, Node node) { - this.weight = weight; - this.node = node; - } - - public NodeWithWeight(double weight, Id id, NodeWithWeight prio) { - this(weight, new Node(id, prio.node())); - } - - public double weight() { - return weight; - } - - public Node node() { - return this.node; - } - - public Map toMap() { - return ImmutableMap.of("weight", this.weight, - "vertices", this.node().path()); - } - - @Override - public int compareTo(NodeWithWeight other) { - return Double.compare(this.weight, other.weight); - } - } - - public static class WeightedPaths extends LinkedHashMap { - - private static final long serialVersionUID = -313873642177730993L; - - public Set vertices() { - Set vertices = newIdSet(); - vertices.addAll(this.keySet()); - for (NodeWithWeight nw : this.values()) { - vertices.addAll(nw.node().path()); - } - return vertices; - } - - public Map> toMap() { - Map> results = newMap(); - for (Map.Entry entry : this.entrySet()) { - Id source = entry.getKey(); - NodeWithWeight nw = entry.getValue(); - Map result = nw.toMap(); - results.put(source, result); - } - return results; - } - } } diff --git a/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/algorithm/SubGraphTraverser.java b/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/algorithm/SubGraphTraverser.java index b1e2405c28..25e03996eb 100644 --- a/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/algorithm/SubGraphTraverser.java +++ b/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/algorithm/SubGraphTraverser.java @@ -22,16 +22,15 @@ import java.util.Map; import java.util.Set; -import jakarta.ws.rs.core.MultivaluedMap; - import org.apache.hugegraph.HugeGraph; import org.apache.hugegraph.backend.id.Id; +import org.apache.hugegraph.structure.HugeEdge; import org.apache.hugegraph.type.define.Directions; +import org.apache.hugegraph.util.E; import org.apache.tinkerpop.gremlin.structure.Edge; import org.apache.tinkerpop.gremlin.util.iterator.IteratorUtils; -import org.apache.hugegraph.structure.HugeEdge; -import org.apache.hugegraph.util.E; +import jakarta.ws.rs.core.MultivaluedMap; public class SubGraphTraverser extends HugeTraverser { @@ -39,17 +38,35 @@ public SubGraphTraverser(HugeGraph graph) { super(graph); } - public PathSet 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, false); + private static boolean hasMultiEdges(List edges, Id target) { + boolean hasOutEdge = false; + boolean hasInEdge = false; + for (Edge edge : edges) { + if (((HugeEdge) edge).id().otherVertexId().equals(target)) { + if (((HugeEdge) edge).direction() == Directions.OUT) { + hasOutEdge = true; + } else { + hasInEdge = true; + } + if (hasOutEdge && hasInEdge) { + return true; + } + } + } + return false; + } + + public PathSet 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, false); } public PathSet 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, sourceInRing); + return this.subGraphPaths(sourceV, dir, label, depth, degree, capacity, + limit, true, sourceInRing); } private PathSet subGraphPaths(Id sourceV, Directions dir, String label, @@ -69,48 +86,79 @@ private PathSet subGraphPaths(Id sourceV, Directions dir, String label, capacity, limit, rings, sourceInRing); PathSet paths = new PathSet(); - while (true) { + do { paths.addAll(traverser.forward(dir)); - if (--depth <= 0 || traverser.reachLimit() || - traverser.finished()) { - break; - } - } + } while (--depth > 0 && !traverser.reachLimit() && + !traverser.finished()); + this.vertexIterCounter.addAndGet(traverser.accessedVertices.size()); + this.edgeIterCounter.addAndGet(traverser.edgeCount); + paths.setEdges(traverser.edgeRecord.getEdges(paths)); return paths; } - private static boolean hasMultiEdges(List edges, Id target) { - boolean hasOutEdge = false; - boolean hasInEdge = false; - for (Edge edge : edges) { - if (((HugeEdge) edge).id().otherVertexId().equals(target)) { - if (((HugeEdge) edge).direction() == Directions.OUT) { - hasOutEdge = true; - } else { - hasInEdge = true; - } - if (hasOutEdge && hasInEdge) { - return true; + private static class RingPath extends Path { + + public RingPath(Id crosspoint, List vertices) { + super(crosspoint, vertices); + } + + @Override + public int hashCode() { + int hashCode = 0; + for (Id id : this.vertices()) { + hashCode ^= id.hashCode(); + } + return hashCode; + } + + /** + * Compares the specified object with this path for equality. + * Returns true if other path is equal to or + * reversed of this path. + * + * @param other the object to be compared + * @return true if the specified object is equal to or + * reversed of this path + */ + @Override + public boolean equals(Object other) { + if (!(other instanceof RingPath)) { + return false; + } + List vertices = this.vertices(); + List otherVertices = ((Path) other).vertices(); + + if (vertices.equals(otherVertices)) { + return true; + } + if (vertices.size() != otherVertices.size()) { + return false; + } + for (int i = 0, size = vertices.size(); i < size; i++) { + int j = size - i - 1; + if (!vertices.get(i).equals(otherVertices.get(j))) { + return false; } } + return true; } - return false; } private class Traverser { private final Id source; - private MultivaluedMap sources = newMultivalueMap(); - private Set accessedVertices = newIdSet(); - 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 final Set accessedVertices = newIdSet(); + private final EdgeRecord edgeRecord; + private MultivaluedMap sources = newMultivalueMap(); + private int depth; private long pathCount; + private long edgeCount; public Traverser(Id sourceV, Id label, int depth, long degree, long capacity, long limit, boolean rings, @@ -126,6 +174,8 @@ public Traverser(Id sourceV, Id label, int depth, long degree, this.rings = rings; this.sourceInRing = sourceInRing; this.pathCount = 0L; + this.edgeCount = 0L; + this.edgeRecord = new EdgeRecord(false); } /** @@ -140,7 +190,7 @@ public PathSet forward(Directions direction) { Id vid = entry.getKey(); // Record edgeList to determine if multiple edges exist List edgeList = IteratorUtils.list(edgesOfVertex( - vid, direction, this.label, this.degree)); + vid, direction, this.label, this.degree)); edges = edgeList.iterator(); if (!edges.hasNext()) { @@ -163,7 +213,11 @@ public PathSet forward(Directions direction) { while (edges.hasNext()) { neighborCount++; HugeEdge edge = (HugeEdge) edges.next(); + this.edgeCount += 1L; Id target = edge.id().otherVertexId(); + + this.edgeRecord.addEdge(vid, target, edge); + // Avoid deduplicate path if (currentNeighbors.contains(target)) { continue; @@ -241,62 +295,11 @@ public PathSet forward(Directions direction) { private boolean reachLimit() { checkCapacity(this.capacity, this.accessedVertices.size(), this.rings ? "rings" : "rays"); - if (this.limit == NO_LIMIT || this.pathCount < this.limit) { - return false; - } - return true; + return this.limit != NO_LIMIT && this.pathCount >= this.limit; } private boolean finished() { return this.sources.isEmpty(); } } - - private static class RingPath extends Path { - - public RingPath(Id crosspoint, List vertices) { - super(crosspoint, vertices); - } - - @Override - public int hashCode() { - int hashCode = 0; - for (Id id : this.vertices()) { - hashCode ^= id.hashCode(); - } - return hashCode; - } - - /** - * Compares the specified object with this path for equality. - * Returns true if other path is equal to or - * reversed of this path. - * @param other the object to be compared - * @return true if the specified object is equal to or - * reversed of this path - */ - @Override - public boolean equals(Object other) { - if (!(other instanceof RingPath)) { - return false; - } - List vertices = this.vertices(); - List otherVertices = ((Path) other).vertices(); - - if (vertices.equals(otherVertices)) { - return true; - } - if (vertices.size() != otherVertices.size()) { - return false; - } - assert vertices.size() == otherVertices.size(); - for (int i = 0, size = vertices.size(); i < size; i++) { - int j = size - i - 1; - if (!vertices.get(i).equals(otherVertices.get(j))) { - return false; - } - } - return true; - } - } } diff --git a/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/algorithm/TemplatePathsTraverser.java b/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/algorithm/TemplatePathsTraverser.java index 85ce74651d..949014e192 100644 --- a/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/algorithm/TemplatePathsTraverser.java +++ b/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/algorithm/TemplatePathsTraverser.java @@ -25,13 +25,13 @@ import org.apache.hugegraph.HugeGraph; import org.apache.hugegraph.backend.id.Id; +import org.apache.hugegraph.structure.HugeVertex; import org.apache.hugegraph.traversal.algorithm.steps.EdgeStep; import org.apache.hugegraph.traversal.algorithm.steps.RepeatEdgeStep; import org.apache.hugegraph.traversal.algorithm.strategy.TraverseStrategy; -import org.apache.tinkerpop.gremlin.structure.Vertex; - -import org.apache.hugegraph.structure.HugeVertex; import org.apache.hugegraph.util.E; +import org.apache.tinkerpop.gremlin.structure.Edge; +import org.apache.tinkerpop.gremlin.structure.Vertex; public class TemplatePathsTraverser extends HugeTraverser { @@ -39,11 +39,11 @@ public TemplatePathsTraverser(HugeGraph graph) { super(graph); } - public Set templatePaths(Iterator sources, - Iterator targets, - List steps, - boolean withRing, - long capacity, long limit) { + public WrappedPathSet templatePaths(Iterator sources, + Iterator targets, + List steps, + boolean withRing, long capacity, + long limit) { checkCapacity(capacity); checkLimit(limit); @@ -68,23 +68,26 @@ public Set templatePaths(Iterator sources, for (RepeatEdgeStep step : steps) { totalSteps += step.maxTimes(); } + + boolean concurrent = totalSteps >= this.concurrentDepth(); TraverseStrategy strategy = TraverseStrategy.create( - totalSteps >= this.concurrentDepth(), - this.graph()); + concurrent, this.graph()); Traverser traverser = new Traverser(this, strategy, sourceList, targetList, steps, - withRing, capacity, limit); + withRing, capacity, limit, concurrent); do { // Forward traverser.forward(); if (traverser.finished()) { - return traverser.paths(); + Set paths = traverser.paths(); + return new WrappedPathSet(paths, traverser.edgeResults.getEdges(paths)); } // Backward traverser.backward(); if (traverser.finished()) { - return traverser.paths(); + Set paths = traverser.paths(); + return new WrappedPathSet(paths, traverser.edgeResults.getEdges(paths)); } } while (true); } @@ -98,14 +101,14 @@ private static class Traverser extends PathTraverser { protected int sourceIndex; protected int targetIndex; - protected boolean sourceFinishOneStep = false; - protected boolean targetFinishOneStep = false; + protected boolean sourceFinishOneStep; + protected boolean targetFinishOneStep; public Traverser(HugeTraverser traverser, TraverseStrategy strategy, Collection sources, Collection targets, List steps, boolean withRing, - long capacity, long limit) { - super(traverser, strategy, sources, targets, capacity, limit); + long capacity, long limit, boolean concurrent) { + super(traverser, strategy, sources, targets, capacity, limit, concurrent); this.steps = steps; this.withRing = withRing; @@ -135,7 +138,7 @@ public void beforeTraverse(boolean forward) { public void afterTraverse(EdgeStep step, boolean forward) { Map> all = forward ? this.sourcesAll : - this.targetsAll; + this.targetsAll; this.addNewVerticesToAll(all); this.reInitCurrentStepIfNeeded(step, forward); this.stepCount++; @@ -276,4 +279,23 @@ public boolean lastSuperStep() { this.targetIndex == this.sourceIndex + 1; } } + + public static class WrappedPathSet { + + private final Set paths; + private final Set edges; + + public WrappedPathSet(Set paths, Set edges) { + this.paths = paths; + this.edges = edges; + } + + public Set paths() { + return paths; + } + + public Set edges() { + return edges; + } + } } diff --git a/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/algorithm/records/ShortestPathRecords.java b/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/algorithm/records/ShortestPathRecords.java index ce3647de2e..fe05d00824 100644 --- a/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/algorithm/records/ShortestPathRecords.java +++ b/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/algorithm/records/ShortestPathRecords.java @@ -23,14 +23,14 @@ import java.util.function.Function; import org.apache.hugegraph.backend.id.Id; +import org.apache.hugegraph.traversal.algorithm.HugeTraverser.Path; +import org.apache.hugegraph.traversal.algorithm.HugeTraverser.PathSet; import org.apache.hugegraph.traversal.algorithm.records.record.Int2IntRecord; import org.apache.hugegraph.traversal.algorithm.records.record.Record; import org.apache.hugegraph.traversal.algorithm.records.record.RecordType; import org.apache.hugegraph.util.collection.CollectionFactory; import org.apache.hugegraph.util.collection.IntMap; import org.apache.hugegraph.util.collection.IntSet; -import org.apache.hugegraph.traversal.algorithm.HugeTraverser.Path; -import org.apache.hugegraph.traversal.algorithm.HugeTraverser.PathSet; public class ShortestPathRecords extends DoubleWayMultiPathsRecords { diff --git a/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/algorithm/records/SingleWayMultiPathsRecords.java b/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/algorithm/records/SingleWayMultiPathsRecords.java index 4e53ecc16b..d41adc92a8 100644 --- a/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/algorithm/records/SingleWayMultiPathsRecords.java +++ b/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/algorithm/records/SingleWayMultiPathsRecords.java @@ -25,17 +25,18 @@ import org.apache.hugegraph.HugeException; import org.apache.hugegraph.backend.id.Id; -import org.apache.hugegraph.type.define.CollectionType; -import org.apache.hugegraph.util.collection.CollectionFactory; -import org.apache.hugegraph.util.collection.IntIterator; -import org.apache.hugegraph.util.collection.IntMap; -import org.apache.hugegraph.util.collection.IntSet; import org.apache.hugegraph.perf.PerfUtil.Watched; +import org.apache.hugegraph.traversal.algorithm.HugeTraverser.EdgeRecord; import org.apache.hugegraph.traversal.algorithm.HugeTraverser.Path; import org.apache.hugegraph.traversal.algorithm.HugeTraverser.PathSet; import org.apache.hugegraph.traversal.algorithm.records.record.Int2IntRecord; import org.apache.hugegraph.traversal.algorithm.records.record.Record; import org.apache.hugegraph.traversal.algorithm.records.record.RecordType; +import org.apache.hugegraph.type.define.CollectionType; +import org.apache.hugegraph.util.collection.CollectionFactory; +import org.apache.hugegraph.util.collection.IntIterator; +import org.apache.hugegraph.util.collection.IntMap; +import org.apache.hugegraph.util.collection.IntSet; public abstract class SingleWayMultiPathsRecords extends AbstractRecords { @@ -44,7 +45,7 @@ public abstract class SingleWayMultiPathsRecords extends AbstractRecords { private final int sourceCode; private final boolean nearest; private final IntSet accessedVertices; - + private final EdgeRecord edgeResults; private IntIterator parentRecordKeys; public SingleWayMultiPathsRecords(RecordType type, boolean concurrent, @@ -58,6 +59,7 @@ public SingleWayMultiPathsRecords(RecordType type, boolean concurrent, firstRecord.addPath(this.sourceCode, 0); this.records = new Stack<>(); this.records.push(firstRecord); + this.edgeResults = new EdgeRecord(concurrent); this.accessedVertices = CollectionFactory.newIntSet(); } @@ -176,6 +178,10 @@ protected final Stack records() { return this.records; } + public EdgeRecord edgeResults() { + return edgeResults; + } + public abstract int size(); public abstract List ids(long limit); diff --git a/hugegraph-test/src/main/java/org/apache/hugegraph/api/traversers/JaccardSimilarityApiTest.java b/hugegraph-test/src/main/java/org/apache/hugegraph/api/traversers/JaccardSimilarityApiTest.java index 1ce5f6d705..6fba538d54 100644 --- a/hugegraph-test/src/main/java/org/apache/hugegraph/api/traversers/JaccardSimilarityApiTest.java +++ b/hugegraph-test/src/main/java/org/apache/hugegraph/api/traversers/JaccardSimilarityApiTest.java @@ -19,14 +19,15 @@ import java.util.Map; -import jakarta.ws.rs.core.Response; +import org.apache.hugegraph.api.BaseApiTest; import org.junit.Assert; import org.junit.Before; import org.junit.Test; -import org.apache.hugegraph.api.BaseApiTest; import com.google.common.collect.ImmutableMap; +import jakarta.ws.rs.core.Response; + public class JaccardSimilarityApiTest extends BaseApiTest { static final String PATH = TRAVERSERS_API + "/jaccardsimilarity"; @@ -72,9 +73,10 @@ public void testPost() { "\"top\": 3}", markoId); Response r = client().post(PATH, reqBody); String content = assertResponseStatus(200, r); - Double rippleJaccardSimilarity = assertJsonContains(content, rippleId); - Double peterJaccardSimilarity = assertJsonContains(content, peterId); - Double jsonJaccardSimilarity = assertJsonContains(content, jsonId); + Map jaccardSimilarity = assertJsonContains(content, "jaccard_similarity"); + Double rippleJaccardSimilarity = assertMapContains(jaccardSimilarity, rippleId); + Double peterJaccardSimilarity = assertMapContains(jaccardSimilarity, peterId); + Double jsonJaccardSimilarity = assertMapContains(jaccardSimilarity, jsonId); Assert.assertEquals(0.3333, rippleJaccardSimilarity.doubleValue(), 0.0001); Assert.assertEquals(0.25, peterJaccardSimilarity.doubleValue(), 0.0001); From 4d7ad86776482900ef75406760b6a6391ef08781 Mon Sep 17 00:00:00 2001 From: lzyxx <94185075+lzyxx77@users.noreply.github.com> Date: Tue, 22 Aug 2023 14:07:34 +0800 Subject: [PATCH 2/3] fix checkstyle: Update StandardStateMachineCallback.java (#2290) During the compilation of your code, an informational message was displayed indicating an issue with the file /home/lzy/hugegraph/hugegraph-core/src/main/java/org/apache/hugegraph/masterelection/StandardStateMachineCallback.java at line 36. The specific problem was that the length of this line exceeded 100 characters, with a total of 101 characters. To address this issue, I have made modifications to this class. I have split the originally long line into multiple lines to ensure that each line's length adheres to the coding standards' specified limits. This action not only aligns with the requirements of the code style, but also improves the readability and maintainability of the code. --- .../hugegraph/masterelection/StandardStateMachineCallback.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/hugegraph-core/src/main/java/org/apache/hugegraph/masterelection/StandardStateMachineCallback.java b/hugegraph-core/src/main/java/org/apache/hugegraph/masterelection/StandardStateMachineCallback.java index 88bec95b31..28e01d2913 100644 --- a/hugegraph-core/src/main/java/org/apache/hugegraph/masterelection/StandardStateMachineCallback.java +++ b/hugegraph-core/src/main/java/org/apache/hugegraph/masterelection/StandardStateMachineCallback.java @@ -33,7 +33,8 @@ public class StandardStateMachineCallback implements StateMachineCallback { private boolean isMaster = false; - public StandardStateMachineCallback(TaskManager taskManager, GlobalMasterInfo globalMasterInfo) { + public StandardStateMachineCallback(TaskManager taskManager, + GlobalMasterInfo globalMasterInfo) { this.taskManager = taskManager; this.taskManager.enableRoleElected(true); this.globalMasterInfo = globalMasterInfo; From 77c76124af0fbb186d16790220583b7e58b0df9c Mon Sep 17 00:00:00 2001 From: V_Galaxy Date: Fri, 25 Aug 2023 15:20:44 +0800 Subject: [PATCH 3/3] chore(dist): replace wget to curl to download swagger-ui (#2277) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Main Changes: 1. replace `wget` by `curl` when downloading `swagger-ui` 2. silence the output of `curl` and `tar` commands 3. reuse the existing `v4.15.5.tar.gz` before downloading 4. avoid downloading `swagger-ui` in non-Linux platforms to prevent build failures (there might be a cross-platform build approach available 🤔) 5. wrapp the script content within `` blocks ensures that the script retains its original format when generating the `dist.sh` script (also suppresses automatic indentation) 6. remove intermediate files at the end of the script **An alternative approach**, during the generation of the `dist.sh` script, only the `${final.name}` property from the build process is utilized. It might be possible to separately store a `dist.sh` script within hugegraph-dist, then use `sed` during the build process to replace the value of `${final.name}`, **thereby avoiding the need to embed script content within the pom file**. --------- Co-authored-by: imbajin --- hugegraph-dist/dist.sh | 46 ++++++++ hugegraph-dist/pom.xml | 231 +++++++++++++++++++++++++++++------------ pom.xml | 26 +++++ 3 files changed, 234 insertions(+), 69 deletions(-) create mode 100644 hugegraph-dist/dist.sh diff --git a/hugegraph-dist/dist.sh b/hugegraph-dist/dist.sh new file mode 100644 index 0000000000..64029d49a0 --- /dev/null +++ b/hugegraph-dist/dist.sh @@ -0,0 +1,46 @@ +#!/bin/bash +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to You 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. +# +VERSION=4.15.5 + +curl --version >/dev/null 2>&1 || + { + echo 'ERROR: Please install `curl` first if you need `swagger-ui`' + exit + } + +# TODO: perhaps it's necessary verify the checksum before reusing the existing tar +if [[ ! -f v$VERSION.tar.gz ]]; then + curl -s -S -L -o v$VERSION.tar.gz \ + https://github.com/swagger-api/swagger-ui/archive/refs/tags/v$VERSION.tar.gz || + { + echo 'ERROR: Download `swagger-ui` failed, please check your network connection' + exit + } +fi + +tar zxf v$VERSION.tar.gz -C . >/dev/null 2>&1 + +echo "window.onload = function() { window.ui = SwaggerUIBundle({ +url:'/openapi.json',dom_id:'#swagger-ui',deepLinking:true,layout:'StandaloneLayout', +presets:[SwaggerUIBundle.presets.apis, SwaggerUIStandalonePreset ], +plugins:[SwaggerUIBundle.plugins.DownloadUrl]});};" > \ + swagger-ui-$VERSION/dist/swagger-initializer.js + +# conceal the VERSION from the outside +mv swagger-ui-$VERSION swagger-ui +echo 'INFO: Successfully download `swagger-ui`' diff --git a/hugegraph-dist/pom.xml b/hugegraph-dist/pom.xml index 8c23199544..48ad2017b7 100644 --- a/hugegraph-dist/pom.xml +++ b/hugegraph-dist/pom.xml @@ -124,30 +124,6 @@ - - maven-assembly-plugin - 2.4 - - - assembly-hugegraph - package - - single - - - false - false - ${top.level.dir} - - - ${assembly.descriptor.dir}/assembly.xml - - ${final.name} - - - - - org.apache.maven.plugins maven-clean-plugin @@ -168,51 +144,168 @@ - - - maven-antrun-plugin - - - download-swagger-ui - package - - run - - - - - wget --version 1>/dev/null || exit - wget https://github.com/swagger-api/swagger-ui/archive/refs/tags/v4.15.5.tar.gz - tar zxvf v4.15.5.tar.gz - echo "window.onload = function() { window.ui = SwaggerUIBundle({ - url:'/openapi.json',dom_id:'#swagger-ui',deepLinking:true,layout:'StandaloneLayout', - presets:[SwaggerUIBundle.presets.apis, SwaggerUIStandalonePreset ], - plugins:[SwaggerUIBundle.plugins.DownloadUrl]});};" > swagger-ui-4.15.5/dist/swagger-initializer.js - cp -r swagger-ui-4.15.5/dist ../${final.name}/swagger-ui - - - - - - - - - package - - run - - - - - - - - - - - - - + + + + + org.apache.maven.plugins + maven-assembly-plugin + 2.4 + + + assembly-hugegraph + package + + single + + + false + false + ${top.level.dir} + + + ${assembly.descriptor.dir}/assembly.xml + + ${final.name} + + + + + + org.apache.maven.plugins + maven-antrun-plugin + 1.8 + + + download-swagger-ui + prepare-package + + run + + + + + + + + + + + install-swagger-ui + package + + run + + + + + + + + + + + + + + + + + + + + + + + assembly-hugegraph + + + + maven-assembly-plugin + + + + + + + !skip-assembly-hugegraph + + + + + unix-package + + + + maven-antrun-plugin + + + + + + unix + Linux + + + + + mac-package + + + + maven-antrun-plugin + + + + + + mac + + + + + tar-package + + + + org.apache.maven.plugins + maven-antrun-plugin + 1.8 + + + tar-package + package + + run + + + + + + + + + + + + + + + + + + + !skip-tar-package + + + + diff --git a/pom.xml b/pom.xml index 719328316c..5cc42a954d 100644 --- a/pom.xml +++ b/pom.xml @@ -453,6 +453,32 @@ true + + org.apache.maven.plugins + maven-enforcer-plugin + + + enforce-version + + enforce + + + false + + + + + + [1.8,12) + + + [3.5.0,) + + + + + +