diff --git a/.github/workflows/client-ci.yml b/.github/workflows/client-ci.yml index be0dcba37..7275a039e 100644 --- a/.github/workflows/client-ci.yml +++ b/.github/workflows/client-ci.yml @@ -25,29 +25,44 @@ jobs: USE_STAGE: 'true' # Whether to include the stage repository. TRAVIS_DIR: hugegraph-client/assembly/travis # TODO: replace it with the (latest - n) commit id (n >= 15) - COMMIT_ID: 6a4041e + # hugegraph commit date: 2024-10-10 + COMMIT_ID: 29ecc0 strategy: fail-fast: false matrix: JAVA_VERSION: [ '8' ] + steps: - - name: Install JDK 8 - uses: actions/setup-java@v3 + - name: Fetch code + uses: actions/checkout@v4 with: - java-version: ${{ matrix.JAVA_VERSION }} - distribution: 'zulu' + fetch-depth: 2 + # TODO: do we need it? (need test) - name: Cache Maven packages - uses: actions/cache@v3 + uses: actions/cache@v4 with: - path: ~/.m2 - key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }} - restore-keys: ${{ runner.os }}-m2 + path: ~/.m2/repository + key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} + restore-keys: ${{ runner.os }}-maven- - - name: Checkout - uses: actions/checkout@v3 + - name: Install JDK 11 for graph server + uses: actions/setup-java@v4 with: - fetch-depth: 2 + java-version: '11' + distribution: 'zulu' + cache: 'maven' + + - name: Prepare env and service + run: | + $TRAVIS_DIR/install-hugegraph-from-source.sh $COMMIT_ID + + - name: Install Java ${{ matrix.JAVA_VERSION }} for client + uses: actions/setup-java@v4 + with: + java-version: ${{ matrix.JAVA_VERSION }} + distribution: 'zulu' + cache: 'maven' - name: Use staged maven repo if: ${{ env.USE_STAGE == 'true' }} @@ -59,10 +74,6 @@ jobs: run: | mvn -e compile -pl hugegraph-client -Dmaven.javadoc.skip=true -ntp - - name: Prepare env and service - run: | - $TRAVIS_DIR/install-hugegraph-from-source.sh $COMMIT_ID - - name: Run test run: | cd hugegraph-client && ls * diff --git a/.github/workflows/loader-ci.yml b/.github/workflows/loader-ci.yml index 420b473c3..d537583fb 100644 --- a/.github/workflows/loader-ci.yml +++ b/.github/workflows/loader-ci.yml @@ -27,7 +27,7 @@ jobs: TRAVIS_DIR: hugegraph-loader/assembly/travis STATIC_DIR: hugegraph-loader/assembly/static # TODO: replace it with the (latest - n) commit id (n >= 15) - COMMIT_ID: 6a4041e + COMMIT_ID: f6f3708 DB_USER: root DB_PASS: root DB_DATABASE: load_test diff --git a/.github/workflows/spark-connector-ci.yml b/.github/workflows/spark-connector-ci.yml index 29ea58ca0..b34169ef4 100644 --- a/.github/workflows/spark-connector-ci.yml +++ b/.github/workflows/spark-connector-ci.yml @@ -25,7 +25,7 @@ jobs: env: USE_STAGE: 'true' # Whether to include the stage repository. TRAVIS_DIR: hugegraph-spark-connector/assembly/travis - COMMIT_ID: 6a4041e + COMMIT_ID: f6f3708 steps: - name: Install JDK 11 uses: actions/setup-java@v4 diff --git a/hugegraph-client/pom.xml b/hugegraph-client/pom.xml index 08b8d3ebe..d9b95e939 100644 --- a/hugegraph-client/pom.xml +++ b/hugegraph-client/pom.xml @@ -27,6 +27,7 @@ hugegraph-client + ${revision} jar ${project.artifactId} @@ -48,6 +49,9 @@ org.mockito mockito-core + + ${mockito.version} test diff --git a/hugegraph-client/src/main/java/org/apache/hugegraph/structure/GraphElement.java b/hugegraph-client/src/main/java/org/apache/hugegraph/structure/GraphElement.java index 915cb1701..1b6982a3d 100644 --- a/hugegraph-client/src/main/java/org/apache/hugegraph/structure/GraphElement.java +++ b/hugegraph-client/src/main/java/org/apache/hugegraph/structure/GraphElement.java @@ -77,6 +77,7 @@ public GraphElement property(String name, Object value) { E.checkArgument(ReflectionUtil.isSimpleType(clazz) || clazz.equals(UUID.class) || clazz.equals(Date.class) || + clazz.equals(byte[].class) || value instanceof List || value instanceof Set, "Invalid property value type: '%s'", clazz); diff --git a/hugegraph-client/src/main/java/org/apache/hugegraph/structure/constant/EdgeLabelType.java b/hugegraph-client/src/main/java/org/apache/hugegraph/structure/constant/EdgeLabelType.java new file mode 100644 index 000000000..34db91d4b --- /dev/null +++ b/hugegraph-client/src/main/java/org/apache/hugegraph/structure/constant/EdgeLabelType.java @@ -0,0 +1,56 @@ +/* + * 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 org.apache.hugegraph.structure.constant; + +public enum EdgeLabelType { + + NORMAL(0, "NORMAL"), + + PARENT(1, "PARENT"), + + SUB(2, "SUB"); + + private byte code = 0; + private String name = null; + + EdgeLabelType(int code, String name) { + assert code < 256; + this.code = (byte) code; + this.name = name; + } + + public boolean parent() { + return this == PARENT; + } + + public boolean sub() { + return this == SUB; + } + + public boolean normal() { + return this == NORMAL; + } + + public byte code() { + return this.code; + } + + public String string() { + return this.name; + } +} diff --git a/hugegraph-client/src/main/java/org/apache/hugegraph/structure/graph/Edge.java b/hugegraph-client/src/main/java/org/apache/hugegraph/structure/graph/Edge.java index d5275b409..fe7352581 100644 --- a/hugegraph-client/src/main/java/org/apache/hugegraph/structure/graph/Edge.java +++ b/hugegraph-client/src/main/java/org/apache/hugegraph/structure/graph/Edge.java @@ -142,10 +142,10 @@ public void targetLabel(String targetLabel) { public String name() { if (this.name == null) { String[] idParts = SplicingIdGenerator.split(this.id); - E.checkState(idParts.length == 4, - "The edge id must be formatted by 4 parts, " + + E.checkState(idParts.length == 5 || idParts.length == 6, + "The edge id must be formatted by 5~6 parts, " + "actual is %s", idParts.length); - this.name = idParts[2]; + this.name = idParts[idParts.length - 2]; } return this.name; } diff --git a/hugegraph-client/src/main/java/org/apache/hugegraph/structure/schema/EdgeLabel.java b/hugegraph-client/src/main/java/org/apache/hugegraph/structure/schema/EdgeLabel.java index 807ddec9a..b2746ada0 100644 --- a/hugegraph-client/src/main/java/org/apache/hugegraph/structure/schema/EdgeLabel.java +++ b/hugegraph-client/src/main/java/org/apache/hugegraph/structure/schema/EdgeLabel.java @@ -18,13 +18,17 @@ package org.apache.hugegraph.structure.schema; import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; import java.util.List; +import java.util.Map; +import java.util.Set; import java.util.concurrent.CopyOnWriteArrayList; +import org.apache.hugegraph.driver.SchemaManager; +import org.apache.hugegraph.structure.constant.EdgeLabelType; import org.apache.hugegraph.structure.constant.Frequency; import org.apache.hugegraph.structure.constant.HugeType; -import org.apache.hugegraph.driver.SchemaManager; - import org.apache.hugegraph.util.CollectionUtil; import org.apache.hugegraph.util.E; @@ -33,12 +37,14 @@ public class EdgeLabel extends SchemaLabel { + @JsonProperty("edgelabel_type") + private EdgeLabelType edgeLabelType = EdgeLabelType.NORMAL; + @JsonProperty("parent_label") + private String parentLabel; @JsonProperty("frequency") private Frequency frequency; - @JsonProperty("source_label") - private String sourceLabel; - @JsonProperty("target_label") - private String targetLabel; + @JsonProperty("links") + private Set> links; @JsonProperty("sort_keys") private List sortKeys; @JsonProperty("ttl") @@ -50,6 +56,7 @@ public class EdgeLabel extends SchemaLabel { public EdgeLabel(@JsonProperty("name") String name) { super(name); this.frequency = Frequency.DEFAULT; + this.links = new HashSet<>(); this.sortKeys = new CopyOnWriteArrayList<>(); this.ttl = 0L; this.ttlStartTime = null; @@ -60,21 +67,59 @@ public String type() { return HugeType.EDGE_LABEL.string(); } + public boolean parent() { + return this.edgeLabelType.parent(); + } + + public boolean sub() { + return this.edgeLabelType.sub(); + } + + public String parentLabel() { + return this.parentLabel; + } + + public EdgeLabelType edgeLabelType() { + return this.edgeLabelType; + } + public Frequency frequency() { return this.frequency; } public String sourceLabel() { - return this.sourceLabel; + E.checkState(this.links.size() == 1, + "Only edge label has single vertex label pair can call " + + "sourceLabelName(), but current edge label got %s", + this.links.size()); + return this.links.iterator().next().keySet().iterator().next(); } public String targetLabel() { - return this.targetLabel; + E.checkState(this.links.size() == 1, + "Only edge label has single vertex label pair can call " + + "targetLabelName(), but current edge label got %s", this.links.size()); + return this.links.iterator().next().values().iterator().next(); + } + + public Set> links() { + return this.links; } public boolean linkedVertexLabel(String vertexLabel) { - return this.sourceLabel.equals(vertexLabel) || - this.targetLabel.equals(vertexLabel); + if (this.edgeLabelType.parent() || this.links == null || + this.links.isEmpty()) { + return false; + } + + for (Map pair : this.links) { + for (String str : pair.keySet()) { + if (str.equals(vertexLabel) || pair.get(str).equals(vertexLabel)) { + return true; + } + } + } + return false; } public List sortKeys() { @@ -91,14 +136,15 @@ public String ttlStartTime() { @Override public String toString() { - return String.format("{name=%s, sourceLabel=%s, targetLabel=%s, " + - "sortKeys=%s, indexLabels=%s, nullableKeys=%s, " + - "properties=%s, ttl=%s, ttlStartTime=%s, " + - "status=%s}", - this.name, this.sourceLabel, this.targetLabel, - this.sortKeys, this.indexLabels, - this.nullableKeys, this.properties, this.ttl, - this.ttlStartTime, this.status); + return String.format("{name=%s, " + "edgeLabel_type=%s, " + "parent_label=%s" + + "links=%s, sortKeys=%s, indexLabels=%s, " + + "nullableKeys=%s, properties=%s, ttl=%s, " + + "ttlStartTime=%s, status=%s}", + this.name, + this.edgeLabelType, this.parentLabel, + this.links, this.sortKeys, this.indexLabels, + this.nullableKeys, this.properties, this.ttl, + this.ttlStartTime, this.status); } public EdgeLabelV53 switchV53() { @@ -115,8 +161,24 @@ public interface Builder extends SchemaBuilder { Builder link(String sourceLabel, String targetLabel); + Builder asBase(); + + Builder withBase(String fatherLabel); + + /** + * Set the source label of the edge label + * + * @deprecated Suggested use {@link #link(String, String)} to set the source and target label pair + */ + @Deprecated Builder sourceLabel(String label); + /** + * Set the target label of the edge label + * + * @deprecated Suggested use {@link #link(String, String)} to set the source and target label pair + */ + @Deprecated Builder targetLabel(String label); Builder frequency(Frequency frequency); @@ -138,11 +200,15 @@ public interface Builder extends SchemaBuilder { public static class BuilderImpl implements Builder { - private EdgeLabel edgeLabel; - private SchemaManager manager; + private final EdgeLabel edgeLabel; + private final SchemaManager manager; + private String sourceLabel; + private String targetLabel; public BuilderImpl(String name, SchemaManager manager) { this.edgeLabel = new EdgeLabel(name); + this.sourceLabel = null; + this.targetLabel = null; this.manager = manager; } @@ -180,10 +246,10 @@ public Builder properties(String... properties) { @Override public Builder sortKeys(String... keys) { E.checkArgument(this.edgeLabel.sortKeys.isEmpty(), - "Not allowed to assign sort keys multi times"); + "Not allowed to assign sort keys multi times"); List sortKeys = Arrays.asList(keys); E.checkArgument(CollectionUtil.allUnique(sortKeys), - "Invalid sort keys %s, which contains some " + + "Invalid sort keys %s, which contains some " + "duplicate properties", sortKeys); this.edgeLabel.sortKeys.addAll(sortKeys); return this; @@ -197,20 +263,64 @@ public Builder nullableKeys(String... keys) { @Override public Builder link(String sourceLabel, String targetLabel) { - this.edgeLabel.sourceLabel = sourceLabel; - this.edgeLabel.targetLabel = targetLabel; + HashMap map = new HashMap<>(); + map.put(sourceLabel, targetLabel); + this.edgeLabel.links.add(map); + this.sourceLabel = null; + this.targetLabel = null; + return this; + } + + @Override + public Builder asBase() { + this.edgeLabel.edgeLabelType = EdgeLabelType.PARENT; + return this; + } + + @Override + public Builder withBase(String parentLabel) { + this.edgeLabel.edgeLabelType = EdgeLabelType.SUB; + this.edgeLabel.parentLabel = parentLabel; return this; } + /** + * Set the source label of the edge label + * + * @deprecated Suggested use {@link #link(String, String)} to set the source and target label pair + */ + @Deprecated @Override public Builder sourceLabel(String label) { - this.edgeLabel.sourceLabel = label; + E.checkArgument(this.edgeLabel.links.isEmpty(), + "Not allowed add source label to an edge label which " + + "already has links"); + if (this.targetLabel != null) { + link(label, this.targetLabel); + this.targetLabel = null; + } else { + this.sourceLabel = label; + } return this; } + /** + * Set the target label of the edge label + * + * @deprecated Suggested use {@link #link(String, String)} to set the source and target label pair + */ + @Deprecated @Override public Builder targetLabel(String label) { - this.edgeLabel.targetLabel = label; + E.checkArgument(this.edgeLabel.links.isEmpty(), + "Not allowed add source label to an edge label " + + "which already has links"); + if (this.sourceLabel != null) { + link(this.sourceLabel, label); + this.sourceLabel = null; + } else { + this.targetLabel = label; + } return this; } @@ -270,8 +380,8 @@ public Builder ifNotExist() { private void checkFrequency() { E.checkArgument(this.edgeLabel.frequency == Frequency.DEFAULT, - "Not allowed to change frequency for " + - "edge label '%s'", this.edgeLabel.name); + "Not allowed to change frequency for edge label '%s'", + this.edgeLabel.name); } } @@ -297,8 +407,8 @@ private EdgeLabelV53(EdgeLabel edgeLabel) { super(edgeLabel.name); this.frequency = edgeLabel.frequency; this.sortKeys = edgeLabel.sortKeys; - this.sourceLabel = edgeLabel.sourceLabel; - this.targetLabel = edgeLabel.targetLabel; + this.sourceLabel = edgeLabel.sourceLabel(); + this.targetLabel = edgeLabel.targetLabel(); this.id = edgeLabel.id(); this.properties = edgeLabel.properties(); this.userdata = edgeLabel.userdata(); diff --git a/hugegraph-client/src/test/java/org/apache/hugegraph/api/EdgeApiTest.java b/hugegraph-client/src/test/java/org/apache/hugegraph/api/EdgeApiTest.java index 347ecb2b5..18705969c 100644 --- a/hugegraph-client/src/test/java/org/apache/hugegraph/api/EdgeApiTest.java +++ b/hugegraph-client/src/test/java/org/apache/hugegraph/api/EdgeApiTest.java @@ -670,7 +670,7 @@ public void testGetNotExist() { // TODO: id to be modified edgeAPI.get(edgeId); }, e -> { - Assert.assertContains("Edge id must be formatted as 4~5 parts, " + + Assert.assertContains("Edge id must be formatted as 5~6 parts, " + "but got 1 parts: 'not-exist-edge-id'", e.getMessage()); Assert.assertInstanceOf(ServerException.class, e); diff --git a/hugegraph-client/src/test/java/org/apache/hugegraph/unit/RestResultTest.java b/hugegraph-client/src/test/java/org/apache/hugegraph/unit/RestResultTest.java index 518552d3f..45f8d415a 100644 --- a/hugegraph-client/src/test/java/org/apache/hugegraph/unit/RestResultTest.java +++ b/hugegraph-client/src/test/java/org/apache/hugegraph/unit/RestResultTest.java @@ -228,16 +228,21 @@ public void testReadVertexLabels() { @SneakyThrows @Test public void testReadEdgeLabel() { - String json = "{" - + "\"id\": 2," - + "\"source_label\": \"person\"," - + "\"index_labels\": [\"createdByDate\"]," - + "\"name\": \"created\"," - + "\"target_label\": \"software\"," - + "\"sort_keys\": []," - + "\"properties\": [\"date\"]," - + "\"frequency\": \"SINGLE\"" - + "}"; + String json = "{\n" + + " \"id\" : 14,\n" + + " \"name\" : \"created\",\n" + + " \"edgelabel_type\" : \"NORMAL\",\n" + + " \"links\" : [ {\n" + + " \"person\" : \"software\"\n" + + " } ],\n" + + " \"frequency\" : \"SINGLE\",\n" + + " \"sort_keys\" : [ ],\n" + + " \"nullable_keys\" : [ ],\n" + + " \"index_labels\" : [ ],\n" + + " \"properties\" : [ \"date\" ],\n" + + " \"status\" : \"CREATED\",\n" + + " \"enable_label_index\" : true\n" + + "}"; Mockito.when(this.mockResponse.code()).thenReturn(200); Mockito.when(this.mockResponse.headers()).thenReturn(null); @@ -259,27 +264,45 @@ public void testReadEdgeLabel() { @SneakyThrows @Test public void testReadEdgeLabels() { - String json = "{\"edgelabels\": [" - + "{" - + "\"id\": 2," - + "\"source_label\": \"person\"," - + "\"index_labels\": [\"createdByDate\"]," - + "\"name\": \"created\"," - + "\"target_label\": \"software\"," - + "\"sort_keys\": []," - + "\"properties\": [\"date\"]," - + "\"frequency\": \"SINGLE\"" - + "}," - + "{\"id\": 3," - + "\"source_label\": \"person\"," - + "\"index_labels\": []," - + "\"name\": \"knows\"," - + "\"target_label\": \"person\"," - + "\"sort_keys\": []," - + "\"properties\": [\"date\", \"city\"]," - + "\"frequency\": \"SINGLE\"" - + "}" - + "]}"; + String json = "{\n" + + " \"edgelabels\" : [ {\n" + + " \"id\" : 32,\n" + + " \"name\" : \"created\",\n" + + " \"edgelabel_type\" : \"NORMAL\",\n" + + " \"links\" : [ {\n" + + " \"person\" : \"software\"\n" + + " } ],\n" + + " \"frequency\" : \"SINGLE\",\n" + + " \"sort_keys\" : [ ],\n" + + " \"nullable_keys\" : [ ],\n" + + " \"index_labels\" : [ ],\n" + + " \"properties\" : [ \"date\" ],\n" + + " \"status\" : \"CREATED\",\n" + + " \"ttl\" : 0,\n" + + " \"enable_label_index\" : true,\n" + + " \"user_data\" : {\n" + + " \"~create_time\" : \"2024-10-04 15:17:10.061\"\n" + + " }\n" + + " }, {\n" + + " \"id\" : 31,\n" + + " \"name\" : \"knows\",\n" + + " \"edgelabel_type\" : \"NORMAL\",\n" + + " \"links\" : [ {\n" + + " \"person\" : \"person\"\n" + + " } ],\n" + + " \"frequency\" : \"SINGLE\",\n" + + " \"sort_keys\" : [ ],\n" + + " \"nullable_keys\" : [ ],\n" + + " \"index_labels\" : [ ],\n" + + " \"properties\" : [ \"city\", \"date\" ],\n" + + " \"status\" : \"CREATED\",\n" + + " \"ttl\" : 0,\n" + + " \"enable_label_index\" : true,\n" + + " \"user_data\" : {\n" + + " \"~create_time\" : \"2024-10-04 15:17:10.053\"\n" + + " }\n" + + " } ]\n" + + "}"; Mockito.when(this.mockResponse.code()).thenReturn(200); Mockito.when(this.mockResponse.headers()).thenReturn(null); diff --git a/hugegraph-loader/src/test/java/org/apache/hugegraph/loader/test/functional/FileLoadTest.java b/hugegraph-loader/src/test/java/org/apache/hugegraph/loader/test/functional/FileLoadTest.java index 06cbe0e54..4045bb89e 100644 --- a/hugegraph-loader/src/test/java/org/apache/hugegraph/loader/test/functional/FileLoadTest.java +++ b/hugegraph-loader/src/test/java/org/apache/hugegraph/loader/test/functional/FileLoadTest.java @@ -2474,7 +2474,7 @@ public void testNumberAndDatePrimaryKeysEncoded() { String v1Id = String.format("%s:%s", 1, LongEncoding.encodeNumber(100)); java.util.Date date = DateUtil.parse("2000-02-01", "yyyy-MM-dd"); String v2Id = String.format("%s:%s", 2, LongEncoding.encodeNumber(date)); - String eId = String.format("S1:%s>1>>S2:%s", + String eId = String.format("S1:%s>1>1>>S2:%s", LongEncoding.encodeNumber(100), LongEncoding.encodeNumber(date)); Assert.assertEquals(v1Id, v1.id()); diff --git a/pom.xml b/pom.xml index eb0ae13cb..a9429c941 100644 --- a/pom.xml +++ b/pom.xml @@ -98,7 +98,8 @@ - 1.3.0 + 1.5.0 + 1.3.0 ${project.artifactId} apache-${release.name}-incubating-${project.version}