diff --git a/pom.xml b/pom.xml index 13f6e7f7..27f77489 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ com.baidu.hugegraph hugegraph-client - 1.8.7 + 1.8.8 jar hugegraph-client @@ -113,7 +113,7 @@ - 1.8.7.0 + 1.8.8.0 diff --git a/src/main/java/com/baidu/hugegraph/api/traverser/CountAPI.java b/src/main/java/com/baidu/hugegraph/api/traverser/CountAPI.java new file mode 100644 index 00000000..4d25aeae --- /dev/null +++ b/src/main/java/com/baidu/hugegraph/api/traverser/CountAPI.java @@ -0,0 +1,51 @@ +/* + * Copyright 2017 HugeGraph Authors + * + * 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. + */ + +package com.baidu.hugegraph.api.traverser; + +import java.util.Map; + +import com.baidu.hugegraph.api.traverser.structure.CountRequest; +import com.baidu.hugegraph.client.RestClient; +import com.baidu.hugegraph.rest.RestResult; +import com.baidu.hugegraph.util.E; + +public class CountAPI extends TraversersAPI { + + private static final String COUNT = "count"; + + public CountAPI(RestClient client, String graph) { + super(client, graph); + } + + @Override + protected String type() { + return "count"; + } + + public long post(CountRequest request) { + this.client.checkApiVersion("0.55", "count"); + RestResult result = this.client.post(this.path(), request); + @SuppressWarnings("unchecked") + Map countMap = result.readObject(Map.class); + E.checkState(countMap.containsKey(COUNT), + "The result doesn't have key '%s'", COUNT); + return countMap.get(COUNT).longValue(); + } +} diff --git a/src/main/java/com/baidu/hugegraph/api/traverser/structure/CountRequest.java b/src/main/java/com/baidu/hugegraph/api/traverser/structure/CountRequest.java new file mode 100644 index 00000000..bad361e6 --- /dev/null +++ b/src/main/java/com/baidu/hugegraph/api/traverser/structure/CountRequest.java @@ -0,0 +1,196 @@ +/* + * Copyright 2017 HugeGraph Authors + * + * 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. + */ + +package com.baidu.hugegraph.api.traverser.structure; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import com.baidu.hugegraph.api.API; +import com.baidu.hugegraph.api.traverser.TraversersAPI; +import com.baidu.hugegraph.structure.constant.Direction; +import com.baidu.hugegraph.structure.constant.Traverser; +import com.baidu.hugegraph.util.E; +import com.fasterxml.jackson.annotation.JsonProperty; + +public class CountRequest { + + @JsonProperty("source") + private Object source; + @JsonProperty("steps") + private List steps; + @JsonProperty("contains_traversed") + public boolean containsTraversed; + @JsonProperty("dedup_size") + public long dedupSize; + + private CountRequest() { + this.source = null; + this.steps = new ArrayList<>(); + this.containsTraversed = false; + this.dedupSize = Traverser.DEFAULT_DEDUP_SIZE; + } + + @Override + public String toString() { + return String.format("CountRequest{source=%s,steps=%s," + + "containsTraversed=%s,dedupSize=%s", + this.source, this.steps, this.containsTraversed, + this.dedupSize); + } + + public static class Builder { + + private CountRequest request; + private List stepBuilders; + + public Builder() { + this.request = new CountRequest(); + this.stepBuilders = new ArrayList<>(); + } + + public Builder source(Object source) { + E.checkArgumentNotNull(source, "The source can't be null"); + this.request.source = source; + return this; + } + + public Step.Builder steps() { + Step.Builder builder = new Step.Builder(); + this.stepBuilders.add(builder); + return builder; + } + + public Builder containsTraversed(boolean containsTraversed) { + this.request.containsTraversed = containsTraversed; + return this; + } + + public Builder dedupSize(long dedupSize) { + checkDedupSize(dedupSize); + this.request.dedupSize = dedupSize; + return this; + } + + public CountRequest build() { + E.checkArgumentNotNull(this.request.source, + "The source can't be null"); + for (Step.Builder builder : this.stepBuilders) { + this.request.steps.add(builder.build()); + } + E.checkArgument(this.request.steps != null && + !this.request.steps.isEmpty(), + "The steps can't be null or empty"); + checkDedupSize(this.request.dedupSize); + return this.request; + } + } + + private static void checkDedupSize(long dedupSize) { + E.checkArgument(dedupSize >= 0L || dedupSize == API.NO_LIMIT, + "The dedup size must be >= 0 or == %s, but got: %s", + API.NO_LIMIT, dedupSize); + } + + public static class Step { + + @JsonProperty("direction") + private String direction; + @JsonProperty("labels") + private List labels; + @JsonProperty("properties") + private Map properties; + @JsonProperty("degree") + private long degree; + @JsonProperty("skip_degree") + private long skipDegree; + + private Step() { + this.direction = "BOTH"; + this.labels = new ArrayList<>(); + this.properties = new HashMap<>(); + this.degree = Traverser.DEFAULT_DEGREE; + this.skipDegree = Traverser.DEFAULT_SKIP_DEGREE; + } + + @Override + public String toString() { + return String.format("Step{direction=%s,labels=%s,properties=%s," + + "degree=%s,skipDegree=%s}", + this.direction, this.labels, this.properties, + this.degree, this.skipDegree); + } + + public static class Builder { + + private Step step; + + private Builder() { + this.step = new Step(); + } + + public Builder direction(Direction direction) { + this.step.direction = direction.toString(); + return this; + } + + public Builder labels(List labels) { + this.step.labels = labels; + return this; + } + + public Builder labels(String label) { + this.step.labels.add(label); + return this; + } + + public Builder properties(Map properties) { + this.step.properties = properties; + return this; + } + + public Builder properties(String key, Object value) { + this.step.properties.put(key, value); + return this; + } + + public Builder degree(long degree) { + TraversersAPI.checkDegree(degree); + this.step.degree = degree; + return this; + } + + public Builder skipDegree(long skipDegree) { + TraversersAPI.checkSkipDegree(skipDegree, this.step.degree, + API.NO_LIMIT); + this.step.skipDegree = skipDegree; + return this; + } + + private Step build() { + TraversersAPI.checkDegree(this.step.degree); + TraversersAPI.checkSkipDegree(this.step.skipDegree, + this.step.degree, API.NO_LIMIT); + return this.step; + } + } + } +} diff --git a/src/main/java/com/baidu/hugegraph/driver/TraverserManager.java b/src/main/java/com/baidu/hugegraph/driver/TraverserManager.java index 25c0fd57..06f41852 100644 --- a/src/main/java/com/baidu/hugegraph/driver/TraverserManager.java +++ b/src/main/java/com/baidu/hugegraph/driver/TraverserManager.java @@ -23,6 +23,7 @@ import java.util.List; import com.baidu.hugegraph.api.traverser.AllShortestPathsAPI; +import com.baidu.hugegraph.api.traverser.CountAPI; import com.baidu.hugegraph.api.traverser.CrosspointsAPI; import com.baidu.hugegraph.api.traverser.CustomizedCrosspointsAPI; import com.baidu.hugegraph.api.traverser.CustomizedPathsAPI; @@ -41,6 +42,7 @@ import com.baidu.hugegraph.api.traverser.SingleSourceShortestPathAPI; import com.baidu.hugegraph.api.traverser.VerticesAPI; import com.baidu.hugegraph.api.traverser.WeightedShortestPathAPI; +import com.baidu.hugegraph.api.traverser.structure.CountRequest; import com.baidu.hugegraph.api.traverser.structure.CrosspointsRequest; import com.baidu.hugegraph.api.traverser.structure.CustomizedCrosspoints; import com.baidu.hugegraph.api.traverser.structure.CustomizedPaths; @@ -81,6 +83,7 @@ public class TraverserManager { private CrosspointsAPI crosspointsAPI; private KoutAPI koutAPI; private KneighborAPI kneighborAPI; + private CountAPI countAPI; private RingsAPI ringsAPI; private RaysAPI raysAPI; private CustomizedPathsAPI customizedPathsAPI; @@ -106,6 +109,7 @@ public TraverserManager(RestClient client, GraphManager graphManager) { this.crosspointsAPI = new CrosspointsAPI(client, graph); this.koutAPI = new KoutAPI(client, graph); this.kneighborAPI = new KneighborAPI(client, graph); + this.countAPI = new CountAPI(client, graph); this.ringsAPI = new RingsAPI(client, graph); this.raysAPI = new RaysAPI(client, graph); this.customizedPathsAPI = new CustomizedPathsAPI(client, graph); @@ -379,6 +383,10 @@ public List kneighbor(Object sourceId, Direction direction, degree, limit); } + public long count(CountRequest request) { + return this.countAPI.post(request); + } + public List rings(Object sourceId, int depth) { return this.rings(sourceId, Direction.BOTH, null, depth, true, DEFAULT_DEGREE, DEFAULT_CAPACITY, diff --git a/src/main/java/com/baidu/hugegraph/structure/constant/Traverser.java b/src/main/java/com/baidu/hugegraph/structure/constant/Traverser.java index 3f2e2c49..fe2a93fd 100644 --- a/src/main/java/com/baidu/hugegraph/structure/constant/Traverser.java +++ b/src/main/java/com/baidu/hugegraph/structure/constant/Traverser.java @@ -27,6 +27,8 @@ public class Traverser { public static final long DEFAULT_DEGREE = 10_000L; public static final long DEFAULT_CROSSPOINT_LIMIT = 10_000L; public static final long DEFAULT_PATHS_LIMIT = 10L; + public static final long DEFAULT_DEDUP_SIZE = 1_000_000L; + public static final long DEFAULT_SKIP_DEGREE = 100_000L; public static final long DEFAULT_SAMPLE = 100L; public static final double DEFAULT_WEIGHT = 0.0D; public static final long DEFAULT_PAGE_LIMIT = 100_000L; diff --git a/src/test/java/com/baidu/hugegraph/api/ApiTestSuite.java b/src/test/java/com/baidu/hugegraph/api/ApiTestSuite.java index 36aef5a0..a8139627 100644 --- a/src/test/java/com/baidu/hugegraph/api/ApiTestSuite.java +++ b/src/test/java/com/baidu/hugegraph/api/ApiTestSuite.java @@ -45,6 +45,7 @@ RestoreApiTest.class, TraverserApiTest.class, + CountApiTest.class, RingsRaysApiTest.class, SameNeighborsApiTest.class, JaccardSimilarityApiTest.class, diff --git a/src/test/java/com/baidu/hugegraph/api/BaseApiTest.java b/src/test/java/com/baidu/hugegraph/api/BaseApiTest.java index c0f05d63..01fef4aa 100644 --- a/src/test/java/com/baidu/hugegraph/api/BaseApiTest.java +++ b/src/test/java/com/baidu/hugegraph/api/BaseApiTest.java @@ -36,6 +36,7 @@ import com.baidu.hugegraph.api.schema.VertexLabelAPI; import com.baidu.hugegraph.api.task.TaskAPI; import com.baidu.hugegraph.api.traverser.AllShortestPathsAPI; +import com.baidu.hugegraph.api.traverser.CountAPI; import com.baidu.hugegraph.api.traverser.CrosspointsAPI; import com.baidu.hugegraph.api.traverser.CustomizedCrosspointsAPI; import com.baidu.hugegraph.api.traverser.CustomizedPathsAPI; @@ -82,6 +83,7 @@ public class BaseApiTest extends BaseClientTest { protected static CrosspointsAPI crosspointsAPI; protected static KoutAPI koutAPI; protected static KneighborAPI kneighborAPI; + protected static CountAPI countAPI; protected static RingsAPI ringsAPI; protected static RaysAPI raysAPI; protected static CustomizedPathsAPI customizedPathsAPI; @@ -128,6 +130,7 @@ public static void init() { crosspointsAPI = new CrosspointsAPI(client, GRAPH); koutAPI = new KoutAPI(client, GRAPH); kneighborAPI = new KneighborAPI(client, GRAPH); + countAPI = new CountAPI(client, GRAPH); ringsAPI = new RingsAPI(client, GRAPH); raysAPI = new RaysAPI(client, GRAPH); customizedPathsAPI = new CustomizedPathsAPI(client, GRAPH); diff --git a/src/test/java/com/baidu/hugegraph/api/CountApiTest.java b/src/test/java/com/baidu/hugegraph/api/CountApiTest.java new file mode 100644 index 00000000..5080237a --- /dev/null +++ b/src/test/java/com/baidu/hugegraph/api/CountApiTest.java @@ -0,0 +1,497 @@ +/* + * Copyright 2017 HugeGraph Authors + * + * 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. + */ + +package com.baidu.hugegraph.api; + +import org.junit.BeforeClass; +import org.junit.Test; + +import com.baidu.hugegraph.api.traverser.structure.CountRequest; +import com.baidu.hugegraph.exception.ServerException; +import com.baidu.hugegraph.structure.constant.Direction; +import com.baidu.hugegraph.structure.constant.T; +import com.baidu.hugegraph.structure.graph.Vertex; +import com.baidu.hugegraph.testutil.Assert; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; + +public class CountApiTest extends BaseApiTest { + + @BeforeClass + public static void initGraph() { + schema().propertyKey("time") + .asDate() + .ifNotExist() + .create(); + + schema().propertyKey("weight") + .asDouble() + .ifNotExist() + .create(); + + schema().vertexLabel("node") + .useCustomizeStringId() + .ifNotExist() + .create(); + + schema().edgeLabel("link") + .sourceLabel("node").targetLabel("node") + .properties("time") + .multiTimes().sortKeys("time") + .ifNotExist() + .create(); + + schema().edgeLabel("relateTo") + .sourceLabel("node").targetLabel("node") + .properties("weight") + .ifNotExist() + .create(); + + Vertex va = graph().addVertex(T.label, "node", T.id, "A"); + Vertex vb = graph().addVertex(T.label, "node", T.id, "B"); + Vertex vc = graph().addVertex(T.label, "node", T.id, "C"); + Vertex vd = graph().addVertex(T.label, "node", T.id, "D"); + Vertex ve = graph().addVertex(T.label, "node", T.id, "E"); + Vertex vf = graph().addVertex(T.label, "node", T.id, "F"); + Vertex vg = graph().addVertex(T.label, "node", T.id, "G"); + Vertex vh = graph().addVertex(T.label, "node", T.id, "H"); + Vertex vi = graph().addVertex(T.label, "node", T.id, "I"); + Vertex vj = graph().addVertex(T.label, "node", T.id, "J"); + Vertex vk = graph().addVertex(T.label, "node", T.id, "K"); + Vertex vl = graph().addVertex(T.label, "node", T.id, "L"); + Vertex vm = graph().addVertex(T.label, "node", T.id, "M"); + Vertex vn = graph().addVertex(T.label, "node", T.id, "N"); + Vertex vo = graph().addVertex(T.label, "node", T.id, "O"); + Vertex vp = graph().addVertex(T.label, "node", T.id, "P"); + Vertex vq = graph().addVertex(T.label, "node", T.id, "Q"); + Vertex vr = graph().addVertex(T.label, "node", T.id, "R"); + Vertex vs = graph().addVertex(T.label, "node", T.id, "S"); + Vertex vt = graph().addVertex(T.label, "node", T.id, "T"); + Vertex vu = graph().addVertex(T.label, "node", T.id, "U"); + Vertex vv = graph().addVertex(T.label, "node", T.id, "V"); + Vertex vw = graph().addVertex(T.label, "node", T.id, "W"); + Vertex vx = graph().addVertex(T.label, "node", T.id, "X"); + Vertex vy = graph().addVertex(T.label, "node", T.id, "Y"); + Vertex vz = graph().addVertex(T.label, "node", T.id, "Z"); + + /* + * + * c -----> f + * ^ + * / d -----> g + * / ^ + * / / + * b ---> e -----> h + * ^ + * / j <----- m + * / / + * / / + * / < + * a <--- i <--- k <--- n + * . ^ + * . \ + * . \ + * . l <------ o + * > + * p ...> q ...> v + * ...> r ...> w + * ...> s ...> x + * ...> t ...> y + * ...> u ...> z + * + * Description: + * 1. ">","<","^" means arrow + * 2. "---" means "link" edge + * 3. "..." means "relateTo" edge + * + */ + va.addEdge("link", vb, "time", "2020-01-01"); + + vb.addEdge("link", vc, "time", "2020-01-02"); + vb.addEdge("link", vd, "time", "2020-01-03"); + vb.addEdge("link", ve, "time", "2020-01-04"); + + vc.addEdge("link", vf, "time", "2020-01-05"); + vd.addEdge("link", vg, "time", "2020-01-06"); + ve.addEdge("link", vh, "time", "2020-01-07"); + + vi.addEdge("link", va, "time", "2020-01-08"); + + vj.addEdge("link", vi, "time", "2020-01-09"); + vk.addEdge("link", vi, "time", "2020-01-10"); + vl.addEdge("link", vi, "time", "2020-01-11"); + + vm.addEdge("link", vj, "time", "2020-01-12"); + vn.addEdge("link", vk, "time", "2020-01-13"); + vo.addEdge("link", vl, "time", "2020-01-14"); + + va.addEdge("relateTo", vp, "weight", 0.0D); + + vp.addEdge("relateTo", vq, "weight", 0.1D); + vp.addEdge("relateTo", vr, "weight", 0.2D); + vp.addEdge("relateTo", vs, "weight", 0.3D); + vp.addEdge("relateTo", vt, "weight", 0.4D); + vp.addEdge("relateTo", vu, "weight", 0.5D); + + vq.addEdge("relateTo", vv, "weight", 0.6D); + vr.addEdge("relateTo", vw, "weight", 0.7D); + vs.addEdge("relateTo", vx, "weight", 0.8D); + vt.addEdge("relateTo", vy, "weight", 0.9D); + vu.addEdge("relateTo", vz, "weight", 1.0D); + } + + @Test + public void testCount() { + CountRequest.Builder builder = new CountRequest.Builder(); + builder.source("A").containsTraversed(false); + builder.steps().direction(Direction.OUT); + builder.steps().direction(Direction.OUT); + builder.steps().direction(Direction.OUT); + CountRequest request = builder.build(); + + long count = countAPI.post(request); + Assert.assertEquals(8L, count); + } + + @Test + public void testCountWithContainsTraversed() { + CountRequest.Builder builder = new CountRequest.Builder(); + builder.source("A").containsTraversed(true); + builder.steps().direction(Direction.OUT); + builder.steps().direction(Direction.OUT); + builder.steps().direction(Direction.OUT); + CountRequest request = builder.build(); + + long count = countAPI.post(request); + Assert.assertEquals(19L, count); + } + + @Test + public void testCountWithDirection() { + CountRequest.Builder builder = new CountRequest.Builder(); + builder.source("A").containsTraversed(true); + builder.steps().direction(Direction.OUT); + builder.steps().direction(Direction.OUT); + builder.steps().direction(Direction.OUT); + CountRequest request = builder.build(); + + long count = countAPI.post(request); + Assert.assertEquals(19L, count); + + builder = new CountRequest.Builder(); + builder.source("A").containsTraversed(false); + builder.steps().direction(Direction.OUT); + builder.steps().direction(Direction.OUT); + builder.steps().direction(Direction.OUT); + request = builder.build(); + + count = countAPI.post(request); + Assert.assertEquals(8L, count); + + builder = new CountRequest.Builder(); + builder.source("A").containsTraversed(false); + builder.steps().direction(Direction.IN); + builder.steps().direction(Direction.IN); + builder.steps().direction(Direction.IN); + request = builder.build(); + + count = countAPI.post(request); + Assert.assertEquals(3L, count); + + builder = new CountRequest.Builder(); + builder.source("A").containsTraversed(true); + builder.steps().direction(Direction.IN); + builder.steps().direction(Direction.IN); + builder.steps().direction(Direction.IN); + request = builder.build(); + + count = countAPI.post(request); + Assert.assertEquals(8L, count); + } + + @Test + public void testCountWithLabel() { + CountRequest.Builder builder = new CountRequest.Builder(); + builder.source("A").containsTraversed(false); + builder.steps().direction(Direction.OUT) + .labels(ImmutableList.of("link")); + builder.steps().direction(Direction.OUT) + .labels(ImmutableList.of("link")); + builder.steps().direction(Direction.OUT) + .labels(ImmutableList.of("link")); + CountRequest request = builder.build(); + + long count = countAPI.post(request); + Assert.assertEquals(3L, count); + + builder = new CountRequest.Builder(); + builder.source("A").containsTraversed(true); + builder.steps().direction(Direction.OUT) + .labels(ImmutableList.of("link")); + builder.steps().direction(Direction.OUT) + .labels(ImmutableList.of("link")); + builder.steps().direction(Direction.OUT) + .labels(ImmutableList.of("link")); + request = builder.build(); + + count = countAPI.post(request); + Assert.assertEquals(8L, count); + } + + @Test + public void testCountWithProperties() { + CountRequest.Builder builder = new CountRequest.Builder(); + builder.source("A").containsTraversed(false); + builder.steps().direction(Direction.OUT) + .labels(ImmutableList.of("link")) + .properties("time", "P.lt(\"2020-01-06\")"); + builder.steps().direction(Direction.OUT) + .labels(ImmutableList.of("link")) + .properties("time", "P.lt(\"2020-01-06\")"); + builder.steps().direction(Direction.OUT) + .labels(ImmutableList.of("link")) + .properties("time", "P.lt(\"2020-01-06\")"); + CountRequest request = builder.build(); + + long count = countAPI.post(request); + Assert.assertEquals(1L, count); + + builder = new CountRequest.Builder(); + builder.source("A").containsTraversed(true); + builder.steps().direction(Direction.OUT) + .labels(ImmutableList.of("link")) + .properties("time", "P.lt(\"2020-01-06\")"); + builder.steps().direction(Direction.OUT) + .labels(ImmutableList.of("link")) + .properties("time", "P.lt(\"2020-01-06\")"); + builder.steps().direction(Direction.OUT) + .labels(ImmutableList.of("link")) + .properties("time", "P.lt(\"2020-01-06\")"); + request = builder.build(); + + count = countAPI.post(request); + Assert.assertEquals(6L, count); + + builder = new CountRequest.Builder(); + builder.source("A").containsTraversed(false); + builder.steps().direction(Direction.OUT) + .labels(ImmutableList.of("link")); + builder.steps().direction(Direction.OUT) + .labels(ImmutableList.of("link")) + .properties("time", "P.gt(\"2020-01-03\")"); + builder.steps().direction(Direction.OUT) + .labels(ImmutableList.of("link")); + request = builder.build(); + + count = countAPI.post(request); + Assert.assertEquals(1L, count); + + builder = new CountRequest.Builder(); + builder.source("A").containsTraversed(true); + builder.steps().direction(Direction.OUT) + .labels(ImmutableList.of("link")); + builder.steps().direction(Direction.OUT) + .labels(ImmutableList.of("link")) + .properties("time", "P.gt(\"2020-01-03\")"); + builder.steps().direction(Direction.OUT) + .labels(ImmutableList.of("link")); + request = builder.build(); + + count = countAPI.post(request); + Assert.assertEquals(4L, count); + + builder = new CountRequest.Builder(); + builder.source("A").containsTraversed(false); + builder.steps().direction(Direction.OUT) + .labels(ImmutableList.of("link")); + builder.steps().direction(Direction.OUT) + .labels(ImmutableList.of("link")); + builder.steps().direction(Direction.OUT) + .labels(ImmutableList.of("link")) + .properties("time", "2020-01-07"); + request = builder.build(); + + count = countAPI.post(request); + Assert.assertEquals(1L, count); + } + + @Test + public void testCountWithDegree() { + CountRequest.Builder builder = new CountRequest.Builder(); + builder.source("A").containsTraversed(false); + builder.steps().direction(Direction.OUT); + builder.steps().direction(Direction.OUT); + builder.steps().direction(Direction.OUT); + CountRequest request = builder.build(); + + long count = countAPI.post(request); + Assert.assertEquals(8L, count); + + builder = new CountRequest.Builder(); + builder.source("A").containsTraversed(false); + builder.steps().direction(Direction.OUT).degree(1); + builder.steps().direction(Direction.OUT); + builder.steps().direction(Direction.OUT); + request = builder.build(); + + count = countAPI.post(request); + Assert.assertEquals(3L, count); + + builder = new CountRequest.Builder(); + builder.source("A").containsTraversed(false); + builder.steps().direction(Direction.OUT); + builder.steps().direction(Direction.OUT).degree(2); + builder.steps().direction(Direction.OUT); + request = builder.build(); + + count = countAPI.post(request); + Assert.assertEquals(4L, count); + + builder = new CountRequest.Builder(); + builder.source("A").containsTraversed(false); + builder.steps().direction(Direction.OUT); + builder.steps().direction(Direction.OUT).degree(4); + builder.steps().direction(Direction.OUT); + request = builder.build(); + + count = countAPI.post(request); + Assert.assertEquals(7L, count); + } + + @Test + public void testCountWithSkipDegree() { + CountRequest.Builder builder = new CountRequest.Builder(); + builder.source("A").containsTraversed(false); + builder.steps().direction(Direction.OUT); + builder.steps().direction(Direction.OUT); + builder.steps().direction(Direction.OUT); + CountRequest request = builder.build(); + + long count = countAPI.post(request); + Assert.assertEquals(8L, count); + + builder = new CountRequest.Builder(); + builder.source("A").containsTraversed(false); + builder.steps().direction(Direction.OUT); + builder.steps().direction(Direction.OUT).degree(3).skipDegree(5); + builder.steps().direction(Direction.OUT); + request = builder.build(); + + count = countAPI.post(request); + Assert.assertEquals(3L, count); + + builder = new CountRequest.Builder(); + builder.source("A").containsTraversed(false); + builder.steps().direction(Direction.OUT); + builder.steps().direction(Direction.OUT).degree(2).skipDegree(3); + builder.steps().direction(Direction.OUT); + request = builder.build(); + + count = countAPI.post(request); + Assert.assertEquals(0L, count); + + builder = new CountRequest.Builder(); + builder.source("A").containsTraversed(false); + builder.steps().direction(Direction.OUT); + builder.steps().direction(Direction.OUT).degree(3).skipDegree(4); + request = builder.build(); + + count = countAPI.post(request); + Assert.assertEquals(3L, count); + } + + @Test + public void testCountWithIllegalArgument() { + CountRequest.Builder builder = new CountRequest.Builder(); + + Assert.assertThrows(IllegalArgumentException.class, () -> { + builder.source(null); + }, e -> { + Assert.assertContains("The source can't be null", e.getMessage()); + }); + + Assert.assertThrows(IllegalArgumentException.class, () -> { + builder.dedupSize(-5); + }, e -> { + Assert.assertContains("The dedup size must be >= 0 or == -1, " + + "but got: ", e.getMessage()); + }); + + Assert.assertThrows(IllegalArgumentException.class, () -> { + builder.steps().degree(0); + }, e -> { + Assert.assertContains("Degree must be > 0 or == -1, but got: ", + e.getMessage()); + }); + + Assert.assertThrows(IllegalArgumentException.class, () -> { + builder.steps().skipDegree(-3); + }, e -> { + Assert.assertContains("The skipped degree must be >= 0, but got", + e.getMessage()); + }); + + Assert.assertThrows(IllegalArgumentException.class, () -> { + builder.steps().degree(5).skipDegree(3); + }, e -> { + Assert.assertContains("The skipped degree must be >= degree, ", + e.getMessage()); + }); + + CountRequest.Builder builder1 = new CountRequest.Builder(); + Assert.assertThrows(ServerException.class, () -> { + builder1.source("A").containsTraversed(false); + builder1.steps().properties(ImmutableMap.of("weight", 3.3D)); + countAPI.post(builder1.build()); + }, e -> { + Assert.assertContains("The properties filter condition can be " + + "set only if just set one edge label", + e.getMessage()); + }); + + CountRequest.Builder builder2 = new CountRequest.Builder(); + Assert.assertThrows(ServerException.class, () -> { + builder2.source("A").containsTraversed(false); + builder2.steps().labels(ImmutableList.of("link", "relateTo")) + .properties(ImmutableMap.of("weight", 3.3D)); + countAPI.post(builder2.build()); + }, e -> { + Assert.assertContains("The properties filter condition can be " + + "set only if just set one edge label", + e.getMessage()); + }); + + CountRequest.Builder builder3 = new CountRequest.Builder(); + builder3.source("A").containsTraversed(false); + builder3.steps().labels(ImmutableList.of("link")) + .properties(ImmutableMap.of("time", "2020-01-01")); + countAPI.post(builder3.build()); + + CountRequest.Builder builder4 = new CountRequest.Builder(); + Assert.assertThrows(ServerException.class, () -> { + builder4.source("A").containsTraversed(false); + builder4.steps().labels(ImmutableList.of("link")) + .properties(ImmutableMap.of("weight", 3.3D)); + countAPI.post(builder4.build()); + }, e -> { + Assert.assertContains("does not match sort keys of edge label", + e.getMessage()); + }); + } +}