From f6f37081971f1bdecd726f685961977d53e98269 Mon Sep 17 00:00:00 2001 From: V_Galaxy Date: Thu, 10 Oct 2024 00:15:32 +0800 Subject: [PATCH] BREAKING CHANGE(server): support "parent & child" EdgeLabel type (#2662) HugeGraph supports the parent-child edge feature, meaning that an Edgelabel can have a subordinate type. Using the bank transfer graph as an example, transfers may include person-to-person transfers (person-to-person), person-to-company transfers (person-to-company), and company-to-company transfers (company-to-company). These three different types of transfers share a common operation, transfer. In actual business scenarios, it is often necessary to retrieve all transfer edges with a single query. Currently, competitors can only manually split the transfer edge types, perform multiple queries, and then aggregate the results. With HugeGraph's parent-child edge feature, it is possible to query the corresponding person-to-person transfers, person-to-company transfers, and other sub-edge types, and also conveniently and efficiently retrieve all transfer-related edges at once. PS: The parent-child edge feature for the cassandra and scylladb backends has been temporarily disabled through the store feature. > Code formatting will be done separately in the next PR. Related to: - https://github.com/apache/incubator-hugegraph/issues/745 - https://github.com/apache/incubator-hugegraph/issues/447 --- .../iterator/ExtendableIterator.java | 11 + .../apache/hugegraph/api/graph/EdgeAPI.java | 12 +- .../hugegraph/api/schema/EdgeLabelAPI.java | 29 ++ .../store/cassandra/CassandraFeatures.java | 5 + .../store/cassandra/CassandraTables.java | 8 +- .../java/org/apache/hugegraph/HugeGraph.java | 27 +- .../backend/cache/CachedBackendStore.java | 30 ++ .../apache/hugegraph/backend/id/EdgeId.java | 68 ++-- .../backend/query/EdgesQueryIterator.java | 2 +- .../apache/hugegraph/backend/query/Query.java | 4 + .../backend/serializer/BinarySerializer.java | 36 +- .../backend/serializer/BytesBuffer.java | 5 +- .../backend/serializer/TableSerializer.java | 3 + .../backend/serializer/TableSerializerV2.java | 129 ++++++ .../backend/serializer/TextSerializer.java | 8 +- .../backend/store/AbstractBackendStore.java | 67 ++++ .../backend/store/BackendFeatures.java | 8 +- .../hugegraph/backend/store/BackendStore.java | 7 + .../store/memory/InMemoryDBTables.java | 61 ++- .../backend/store/raft/RaftBackendStore.java | 8 + .../backend/tx/GraphIndexTransaction.java | 19 + .../backend/tx/GraphTransaction.java | 128 +++++- .../backend/tx/SchemaTransaction.java | 16 +- .../backend/tx/SchemaTransactionV2.java | 25 +- .../io/GraphSONSchemaSerializer.java | 57 ++- .../apache/hugegraph/schema/EdgeLabel.java | 196 ++++++--- .../schema/builder/EdgeLabelBuilder.java | 202 ++++++++-- .../apache/hugegraph/structure/HugeEdge.java | 38 +- .../hugegraph/structure/HugeVertex.java | 2 + .../traversal/algorithm/HugeTraverser.java | 2 +- .../traversal/optimize/HugeVertexStep.java | 10 +- .../hugegraph/type/define/EdgeLabelType.java | 8 - .../hugegraph/type/define/HugeKeys.java | 3 + .../apache/hugegraph/example/Example4.java | 363 +++++++++++++++++ .../backend/store/hstore/HstoreStore.java | 318 +++++++-------- .../backend/store/mysql/MysqlSerializer.java | 4 +- .../backend/store/mysql/MysqlTables.java | 4 +- .../apache/hugegraph/core/BaseCoreTest.java | 18 +- .../apache/hugegraph/core/CoreTestSuite.java | 1 + .../core/ParentAndSubEdgeCoreTest.java | 375 ++++++++++++++++++ .../apache/hugegraph/core/TaskCoreTest.java | 8 +- .../apache/hugegraph/unit/FakeObjects.java | 4 +- .../apache/hugegraph/unit/id/EdgeIdTest.java | 12 + .../apache/hugegraph/unit/id/IdUtilTest.java | 54 +-- .../hugegraph/unit/util/JsonUtilTest.java | 16 +- 45 files changed, 2003 insertions(+), 408 deletions(-) create mode 100644 hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/backend/serializer/TableSerializerV2.java create mode 100644 hugegraph-server/hugegraph-example/src/main/java/org/apache/hugegraph/example/Example4.java create mode 100644 hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/core/ParentAndSubEdgeCoreTest.java diff --git a/hugegraph-commons/hugegraph-common/src/main/java/org/apache/hugegraph/iterator/ExtendableIterator.java b/hugegraph-commons/hugegraph-common/src/main/java/org/apache/hugegraph/iterator/ExtendableIterator.java index 94c734b988..b692ad361d 100644 --- a/hugegraph-commons/hugegraph-common/src/main/java/org/apache/hugegraph/iterator/ExtendableIterator.java +++ b/hugegraph-commons/hugegraph-common/src/main/java/org/apache/hugegraph/iterator/ExtendableIterator.java @@ -21,6 +21,8 @@ import java.util.Iterator; import java.util.concurrent.ConcurrentLinkedDeque; +import com.google.common.base.Preconditions; + import org.apache.hugegraph.util.E; public class ExtendableIterator extends WrappedIterator { @@ -54,6 +56,15 @@ public ExtendableIterator extend(Iterator iter) { return this; } + public static ExtendableIterator concat(Iterator lhs, Iterator rhs) { + Preconditions.checkNotNull(lhs); + Preconditions.checkNotNull(rhs); + if (lhs instanceof ExtendableIterator) { + return ((ExtendableIterator) lhs).extend(rhs); + } + return new ExtendableIterator<>(lhs, rhs); + } + @Override public void close() throws Exception { for (Iterator iter : this.itors) { diff --git a/hugegraph-server/hugegraph-api/src/main/java/org/apache/hugegraph/api/graph/EdgeAPI.java b/hugegraph-server/hugegraph-api/src/main/java/org/apache/hugegraph/api/graph/EdgeAPI.java index 25e8cacf00..6a289368e4 100644 --- a/hugegraph-server/hugegraph-api/src/main/java/org/apache/hugegraph/api/graph/EdgeAPI.java +++ b/hugegraph-server/hugegraph-api/src/main/java/org/apache/hugegraph/api/graph/EdgeAPI.java @@ -426,7 +426,15 @@ public static Direction parseDirection(String direction) { private Id getEdgeId(HugeGraph g, JsonEdge newEdge) { String sortKeys = ""; - Id labelId = g.edgeLabel(newEdge.label).id(); + EdgeLabel edgeLabel = g.edgeLabel(newEdge.label); + E.checkArgument(!edgeLabel.edgeLabelType().parent(), + "The label of the created/updated edge is not allowed" + + " to be the parent type"); + Id labelId = edgeLabel.id(); + Id subLabelId = edgeLabel.id(); + if (edgeLabel.edgeLabelType().sub()) { + labelId = edgeLabel.fatherId(); + } List sortKeyIds = g.edgeLabel(labelId).sortKeys(); if (!sortKeyIds.isEmpty()) { List sortKeyValues = new ArrayList<>(sortKeyIds.size()); @@ -442,7 +450,7 @@ private Id getEdgeId(HugeGraph g, JsonEdge newEdge) { sortKeys = ConditionQuery.concatValues(sortKeyValues); } EdgeId edgeId = new EdgeId(HugeVertex.getIdValue(newEdge.source), - Directions.OUT, labelId, sortKeys, + Directions.OUT, labelId, subLabelId, sortKeys, HugeVertex.getIdValue(newEdge.target)); if (newEdge.id != null) { E.checkArgument(edgeId.asString().equals(newEdge.id), diff --git a/hugegraph-server/hugegraph-api/src/main/java/org/apache/hugegraph/api/schema/EdgeLabelAPI.java b/hugegraph-server/hugegraph-api/src/main/java/org/apache/hugegraph/api/schema/EdgeLabelAPI.java index 97bad8c778..7e80afb61a 100644 --- a/hugegraph-server/hugegraph-api/src/main/java/org/apache/hugegraph/api/schema/EdgeLabelAPI.java +++ b/hugegraph-server/hugegraph-api/src/main/java/org/apache/hugegraph/api/schema/EdgeLabelAPI.java @@ -21,6 +21,7 @@ import java.util.Arrays; import java.util.List; import java.util.Map; +import java.util.Set; import org.apache.commons.collections.CollectionUtils; import org.apache.hugegraph.HugeGraph; @@ -32,6 +33,7 @@ import org.apache.hugegraph.define.Checkable; import org.apache.hugegraph.schema.EdgeLabel; import org.apache.hugegraph.schema.Userdata; +import org.apache.hugegraph.type.define.EdgeLabelType; import org.apache.hugegraph.type.define.Frequency; import org.apache.hugegraph.type.define.GraphMode; import org.apache.hugegraph.util.E; @@ -183,10 +185,16 @@ private static class JsonEdgeLabel implements Checkable { public long id; @JsonProperty("name") public String name; + @JsonProperty("edgelabel_type") + public EdgeLabelType edgeLabelType; + @JsonProperty("parent_label") + public String fatherLabel; @JsonProperty("source_label") public String sourceLabel; @JsonProperty("target_label") public String targetLabel; + @JsonProperty("links") + public Set> links; @JsonProperty("frequency") public Frequency frequency; @JsonProperty("properties") @@ -223,12 +231,33 @@ private EdgeLabel.Builder convert2Builder(HugeGraph g) { g, g.mode()); builder.id(this.id); } + if (this.edgeLabelType == null) { + this.edgeLabelType = EdgeLabelType.NORMAL; + } else if (this.edgeLabelType.parent()) { + builder.asBase(); + } else if (this.edgeLabelType.sub()) { + builder.withBase(this.fatherLabel); + } else { + E.checkArgument(this.edgeLabelType.normal(), + "Please enter a valid edge_label_type value " + + "in [NORMAL, PARENT, SUB]"); + } if (this.sourceLabel != null) { builder.sourceLabel(this.sourceLabel); } if (this.targetLabel != null) { builder.targetLabel(this.targetLabel); } + if (this.links != null && !this.links.isEmpty()) { + for (Map map : this.links) { + E.checkArgument(map.size() == 1, + "The map size must be 1, due to it is a " + + "pair"); + Map.Entry entry = + map.entrySet().iterator().next(); + builder.link(entry.getKey(), entry.getValue()); + } + } if (this.frequency != null) { builder.frequency(this.frequency); } diff --git a/hugegraph-server/hugegraph-cassandra/src/main/java/org/apache/hugegraph/backend/store/cassandra/CassandraFeatures.java b/hugegraph-server/hugegraph-cassandra/src/main/java/org/apache/hugegraph/backend/store/cassandra/CassandraFeatures.java index 3ca43050d3..0993fde1be 100644 --- a/hugegraph-server/hugegraph-cassandra/src/main/java/org/apache/hugegraph/backend/store/cassandra/CassandraFeatures.java +++ b/hugegraph-server/hugegraph-cassandra/src/main/java/org/apache/hugegraph/backend/store/cassandra/CassandraFeatures.java @@ -21,6 +21,11 @@ public class CassandraFeatures implements BackendFeatures { + @Override + public boolean supportsFatherAndSubEdgeLabel() { + return false; + } + @Override public boolean supportsScanToken() { return true; diff --git a/hugegraph-server/hugegraph-cassandra/src/main/java/org/apache/hugegraph/backend/store/cassandra/CassandraTables.java b/hugegraph-server/hugegraph-cassandra/src/main/java/org/apache/hugegraph/backend/store/cassandra/CassandraTables.java index 40b7e5a19d..ece8f2ae8e 100644 --- a/hugegraph-server/hugegraph-cassandra/src/main/java/org/apache/hugegraph/backend/store/cassandra/CassandraTables.java +++ b/hugegraph-server/hugegraph-cassandra/src/main/java/org/apache/hugegraph/backend/store/cassandra/CassandraTables.java @@ -21,6 +21,8 @@ import java.util.Arrays; import java.util.Iterator; import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; import org.apache.hugegraph.backend.BackendException; import org.apache.hugegraph.backend.id.EdgeId; @@ -51,6 +53,8 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; +import org.apache.hugegraph.util.HashUtil; + public class CassandraTables { public static final String LABEL_INDEX = "label_index"; @@ -400,7 +404,9 @@ protected List pkColumnName() { @Override protected List idColumnName() { - return Arrays.asList(EdgeId.KEYS); + return Arrays.stream(EdgeId.KEYS) + .filter(key -> !Objects.equals(key, HugeKeys.SUB_LABEL)) + .collect(Collectors.toList()); } @Override diff --git a/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/HugeGraph.java b/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/HugeGraph.java index ab460d495f..13d654d174 100644 --- a/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/HugeGraph.java +++ b/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/HugeGraph.java @@ -19,9 +19,12 @@ import java.util.ArrayList; import java.util.Collection; +import java.util.HashSet; import java.util.Iterator; import java.util.List; +import java.util.Set; +import org.apache.commons.lang3.tuple.Pair; import org.apache.hugegraph.auth.AuthManager; import org.apache.hugegraph.backend.id.Id; import org.apache.hugegraph.backend.query.Query; @@ -300,11 +303,25 @@ default Id[] mapElName2Id(String[] edgeLabels) { Id[] ids = new Id[edgeLabels.length]; for (int i = 0; i < edgeLabels.length; i++) { EdgeLabel edgeLabel = this.edgeLabel(edgeLabels[i]); - ids[i] = edgeLabel.id(); + if (edgeLabel.hasFather()) { + ids[i] = edgeLabel.fatherId(); + } else { + ids[i] = edgeLabel.id(); + } } return ids; } + default Set> mapPairId2Name( + Set> pairs) { + Set> results = new HashSet<>(pairs.size()); + for (Pair pair : pairs) { + results.add(Pair.of(this.vertexLabel(pair.getLeft()).name(), + this.vertexLabel(pair.getRight()).name())); + } + return results; + } + default Id[] mapVlName2Id(String[] vertexLabels) { Id[] ids = new Id[vertexLabels.length]; for (int i = 0; i < vertexLabels.length; i++) { @@ -314,6 +331,14 @@ default Id[] mapVlName2Id(String[] vertexLabels) { return ids; } + default EdgeLabel[] mapElName2El(String[] edgeLabels) { + EdgeLabel[] els = new EdgeLabel[edgeLabels.length]; + for (int i = 0; i < edgeLabels.length; i++) { + els[i] = this.edgeLabel(edgeLabels[i]); + } + return els; + } + static void registerTraversalStrategies(Class clazz) { TraversalStrategies strategies = TraversalStrategies.GlobalCache .getStrategies(Graph.class) diff --git a/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/backend/cache/CachedBackendStore.java b/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/backend/cache/CachedBackendStore.java index a1c632d9f5..e266c0623f 100644 --- a/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/backend/cache/CachedBackendStore.java +++ b/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/backend/cache/CachedBackendStore.java @@ -18,8 +18,13 @@ package org.apache.hugegraph.backend.cache; import java.util.Iterator; +import java.util.List; +import java.util.function.Function; +import org.apache.hugegraph.HugeGraph; import org.apache.hugegraph.backend.id.Id; +import org.apache.hugegraph.backend.query.ConditionQuery; +import org.apache.hugegraph.backend.query.ConditionQueryFlatten; import org.apache.hugegraph.backend.query.Query; import org.apache.hugegraph.backend.store.BackendEntry; import org.apache.hugegraph.backend.store.BackendFeatures; @@ -28,6 +33,8 @@ import org.apache.hugegraph.backend.store.BackendStoreProvider; import org.apache.hugegraph.backend.store.SystemSchemaStore; import org.apache.hugegraph.config.HugeConfig; +import org.apache.hugegraph.iterator.ExtendableIterator; +import org.apache.hugegraph.iterator.MapperIterator; import org.apache.hugegraph.type.HugeType; import org.apache.hugegraph.util.StringEncoding; @@ -177,6 +184,29 @@ public Iterator query(Query query) { } } + @Override + public Iterator> query(Iterator queries, + Function queryWriter, + HugeGraph hugeGraph) { + return new MapperIterator<>(queries, query -> { + assert query instanceof ConditionQuery; + List flattenQueryList = + ConditionQueryFlatten.flatten((ConditionQuery) query); + + if (flattenQueryList.size() > 1) { + ExtendableIterator itExtend + = new ExtendableIterator<>(); + flattenQueryList.forEach(cq -> { + Query cQuery = queryWriter.apply(cq); + itExtend.extend(this.query(cQuery)); + }); + return itExtend; + } else { + return this.query(queryWriter.apply(query)); + } + }); + } + @Override public Number queryNumber(Query query) { return this.store.queryNumber(query); diff --git a/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/backend/id/EdgeId.java b/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/backend/id/EdgeId.java index a9053e5135..fe030a153f 100644 --- a/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/backend/id/EdgeId.java +++ b/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/backend/id/EdgeId.java @@ -28,8 +28,13 @@ /** * Class used to format and parse id of edge, the edge id consists of: - * { source-vertex-id + edge-label + edge-name + target-vertex-id } - * NOTE: if we use `entry.type()` which is IN or OUT as a part of id, + * EdgeId = { source-vertex-id > direction > parentEdgeLabelId > subEdgeLabelId + * > sortKeys > target-vertex-id } + * NOTE: + *

1. for edges with edgeLabelType = NORMAL: edgeLabelId = parentEdgeLabelId = subEdgeLabelId; + * for edges with edgeLabelType = PARENT: edgeLabelId = subEdgeLabelId, parentEdgeLabelId = + * edgeLabelId.fatherId + *

2.if we use `entry.type()` which is IN or OUT as a part of id, * an edge's id will be different due to different directions (belongs * to 2 owner vertex) */ @@ -39,6 +44,7 @@ public class EdgeId implements Id { HugeKeys.OWNER_VERTEX, HugeKeys.DIRECTION, HugeKeys.LABEL, + HugeKeys.SUB_LABEL, HugeKeys.SORT_VALUES, HugeKeys.OTHER_VERTEX }; @@ -46,6 +52,7 @@ public class EdgeId implements Id { private final Id ownerVertexId; private final Directions direction; private final Id edgeLabelId; + private final Id subLabelId; private final String sortValues; private final Id otherVertexId; @@ -53,22 +60,23 @@ public class EdgeId implements Id { private String cache; public EdgeId(HugeVertex ownerVertex, Directions direction, - Id edgeLabelId, String sortValues, HugeVertex otherVertex) { - this(ownerVertex.id(), direction, edgeLabelId, + Id edgeLabelId, Id subLabelId, String sortValues, HugeVertex otherVertex) { + this(ownerVertex.id(), direction, edgeLabelId, subLabelId, sortValues, otherVertex.id()); } - public EdgeId(Id ownerVertexId, Directions direction, Id edgeLabelId, + public EdgeId(Id ownerVertexId, Directions direction, Id edgeLabelId, Id subLabelId, String sortValues, Id otherVertexId) { - this(ownerVertexId, direction, edgeLabelId, + this(ownerVertexId, direction, edgeLabelId, subLabelId, sortValues, otherVertexId, false); } - public EdgeId(Id ownerVertexId, Directions direction, Id edgeLabelId, + public EdgeId(Id ownerVertexId, Directions direction, Id edgeLabelId, Id subLabelId, String sortValues, Id otherVertexId, boolean directed) { this.ownerVertexId = ownerVertexId; this.direction = direction; this.edgeLabelId = edgeLabelId; + this.subLabelId = subLabelId; this.sortValues = sortValues; this.otherVertexId = otherVertexId; this.directed = directed; @@ -78,12 +86,12 @@ public EdgeId(Id ownerVertexId, Directions direction, Id edgeLabelId, @Watched public EdgeId switchDirection() { Directions direction = this.direction.opposite(); - return new EdgeId(this.otherVertexId, direction, this.edgeLabelId, + return new EdgeId(this.otherVertexId, direction, this.edgeLabelId, this.subLabelId, this.sortValues, this.ownerVertexId, this.directed); } public EdgeId directed(boolean directed) { - return new EdgeId(this.ownerVertexId, this.direction, this.edgeLabelId, + return new EdgeId(this.ownerVertexId, this.direction, this.edgeLabelId, this.subLabelId, this.sortValues, this.otherVertexId, directed); } @@ -107,6 +115,10 @@ public Id edgeLabelId() { return this.edgeLabelId; } + public Id subLabelId() { + return this.subLabelId; + } + public Directions direction() { return this.direction; } @@ -138,12 +150,14 @@ public String asString() { IdUtil.writeString(this.ownerVertexId), this.direction.type().string(), IdUtil.writeLong(this.edgeLabelId), + IdUtil.writeLong(this.subLabelId), this.sortValues, IdUtil.writeString(this.otherVertexId)); } else { this.cache = SplicingIdGenerator.concat( IdUtil.writeString(this.sourceVertexId()), IdUtil.writeLong(this.edgeLabelId), + IdUtil.writeLong(this.subLabelId), this.sortValues, IdUtil.writeString(this.targetVertexId())); } @@ -181,11 +195,13 @@ public int hashCode() { return this.ownerVertexId.hashCode() ^ this.direction.hashCode() ^ this.edgeLabelId.hashCode() ^ + this.subLabelId.hashCode() ^ this.sortValues.hashCode() ^ this.otherVertexId.hashCode(); } else { return this.sourceVertexId().hashCode() ^ this.edgeLabelId.hashCode() ^ + this.subLabelId.hashCode() ^ this.sortValues.hashCode() ^ this.targetVertexId().hashCode(); } @@ -201,11 +217,13 @@ public boolean equals(Object object) { return this.ownerVertexId.equals(other.ownerVertexId) && this.direction == other.direction && this.edgeLabelId.equals(other.edgeLabelId) && + this.subLabelId.equals(other.subLabelId) && this.sortValues.equals(other.sortValues) && this.otherVertexId.equals(other.otherVertexId); } else { return this.sourceVertexId().equals(other.sourceVertexId()) && this.edgeLabelId.equals(other.edgeLabelId) && + this.subLabelId.equals(other.subLabelId) && this.sortValues.equals(other.sortValues) && this.targetVertexId().equals(other.targetVertexId()); } @@ -235,31 +253,33 @@ public static EdgeId parse(String id) throws NotFoundException { public static EdgeId parse(String id, boolean returnNullIfError) throws NotFoundException { String[] idParts = SplicingIdGenerator.split(id); - if (!(idParts.length == 4 || idParts.length == 5)) { + if (!(idParts.length == 5 || idParts.length == 6)) { if (returnNullIfError) { return null; } - throw new NotFoundException("Edge id must be formatted as 4~5 " + + throw new NotFoundException("Edge id must be formatted as 5~6 " + "parts, but got %s parts: '%s'", idParts.length, id); } try { - if (idParts.length == 4) { + if (idParts.length == 5) { Id ownerVertexId = IdUtil.readString(idParts[0]); Id edgeLabelId = IdUtil.readLong(idParts[1]); - String sortValues = idParts[2]; - Id otherVertexId = IdUtil.readString(idParts[3]); - return new EdgeId(ownerVertexId, Directions.OUT, edgeLabelId, + Id subLabelId = IdUtil.readLong(idParts[2]); + String sortValues = idParts[3]; + Id otherVertexId = IdUtil.readString(idParts[4]); + return new EdgeId(ownerVertexId, Directions.OUT, edgeLabelId, subLabelId, sortValues, otherVertexId); } else { - assert idParts.length == 5; + assert idParts.length == 6; Id ownerVertexId = IdUtil.readString(idParts[0]); HugeType direction = HugeType.fromString(idParts[1]); Id edgeLabelId = IdUtil.readLong(idParts[2]); - String sortValues = idParts[3]; - Id otherVertexId = IdUtil.readString(idParts[4]); + Id subLabelId = IdUtil.readLong(idParts[3]); + String sortValues = idParts[4]; + Id otherVertexId = IdUtil.readString(idParts[5]); return new EdgeId(ownerVertexId, Directions.convert(direction), - edgeLabelId, sortValues, otherVertexId); + edgeLabelId, subLabelId, sortValues, otherVertexId); } } catch (Throwable e) { if (returnNullIfError) { @@ -272,12 +292,13 @@ public static EdgeId parse(String id, boolean returnNullIfError) public static Id parseStoredString(String id) { String[] idParts = split(id); - E.checkArgument(idParts.length == 4, "Invalid id format: %s", id); + E.checkArgument(idParts.length == 5, "Invalid id format: %s", id); Id ownerVertexId = IdUtil.readStoredString(idParts[0]); Id edgeLabelId = IdGenerator.ofStoredString(idParts[1], IdType.LONG); - String sortValues = idParts[2]; - Id otherVertexId = IdUtil.readStoredString(idParts[3]); - return new EdgeId(ownerVertexId, Directions.OUT, edgeLabelId, + Id subLabelId = IdGenerator.ofStoredString(idParts[2], IdType.LONG); + String sortValues = idParts[3]; + Id otherVertexId = IdUtil.readStoredString(idParts[4]); + return new EdgeId(ownerVertexId, Directions.OUT, edgeLabelId, subLabelId, sortValues, otherVertexId); } @@ -286,6 +307,7 @@ public static String asStoredString(Id id) { return SplicingIdGenerator.concat( IdUtil.writeStoredString(eid.sourceVertexId()), IdGenerator.asStoredString(eid.edgeLabelId()), + IdGenerator.asStoredString(eid.subLabelId()), eid.sortValues(), IdUtil.writeStoredString(eid.targetVertexId())); } diff --git a/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/backend/query/EdgesQueryIterator.java b/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/backend/query/EdgesQueryIterator.java index a3edd28c42..d8bce082c6 100644 --- a/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/backend/query/EdgesQueryIterator.java +++ b/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/backend/query/EdgesQueryIterator.java @@ -52,7 +52,7 @@ public Query next() { Id sourceId = this.sources.next(); ConditionQuery query = GraphTransaction.constructEdgesQuery(sourceId, this.directions, - this.labels); + this.labels.toArray(new Id[0])); if (this.limit != Query.NO_LIMIT) { query.limit(this.limit); query.capacity(this.limit); diff --git a/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/backend/query/Query.java b/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/backend/query/Query.java index 4d8e39ccb7..3a024283ff 100644 --- a/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/backend/query/Query.java +++ b/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/backend/query/Query.java @@ -139,6 +139,10 @@ public Query originQuery() { return this.originQuery; } + public void setOriginQuery(Query query) { + this.originQuery = query; + } + public Query rootOriginQuery() { Query root = this; while (root.originQuery != null) { diff --git a/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/backend/serializer/BinarySerializer.java b/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/backend/serializer/BinarySerializer.java index 0dd4c9d3ee..677ed165f1 100644 --- a/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/backend/serializer/BinarySerializer.java +++ b/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/backend/serializer/BinarySerializer.java @@ -59,6 +59,7 @@ import org.apache.hugegraph.type.define.Cardinality; import org.apache.hugegraph.type.define.DataType; import org.apache.hugegraph.type.define.Directions; +import org.apache.hugegraph.type.define.EdgeLabelType; import org.apache.hugegraph.type.define.Frequency; import org.apache.hugegraph.type.define.HugeKeys; import org.apache.hugegraph.type.define.IdStrategy; @@ -73,6 +74,8 @@ import org.apache.hugegraph.util.StringEncoding; import org.apache.tinkerpop.gremlin.structure.Edge; +import static org.apache.hugegraph.schema.SchemaElement.UNDEF; + public class BinarySerializer extends AbstractSerializer { /* @@ -277,15 +280,26 @@ protected void parseEdge(BackendColumn col, HugeVertex vertex, } byte type = buffer.read(); Id labelId = buffer.readId(); + Id subLabelId = buffer.readId(); String sortValues = buffer.readStringWithEnding(); Id otherVertexId = buffer.readId(); boolean direction = EdgeId.isOutDirectionFromCode(type); - EdgeLabel edgeLabel = graph.edgeLabelOrNone(labelId); - // Construct edge - HugeEdge edge = HugeEdge.constructEdge(vertex, direction, edgeLabel, - sortValues, otherVertexId); + HugeEdge edge; + if (graph == null) { /* when calculation sinking */ + EdgeLabel edgeLabel = new EdgeLabel(null, subLabelId, UNDEF); + if (subLabelId != labelId) { + edgeLabel.edgeLabelType(EdgeLabelType.SUB); + edgeLabel.fatherId(labelId); + } + edge = HugeEdge.constructEdgeWithoutGraph(vertex, direction, edgeLabel, + sortValues, otherVertexId); + } else { + EdgeLabel edgeLabel = graph.edgeLabelOrNone(subLabelId); + edge = HugeEdge.constructEdge(vertex, direction, edgeLabel, + sortValues, otherVertexId); + } // Parse edge-id + edge-properties buffer = BytesBuffer.wrap(col.value); @@ -667,6 +681,11 @@ private Query writeQueryEdgeRangeCondition(ConditionQuery cq) { start.write(direction.type().code()); start.writeId(label); + Id subLabel = cq.condition(HugeKeys.SUB_LABEL); + if (subLabel != null) { + start.writeId(subLabel); + } + BytesBuffer end = BytesBuffer.allocate(BytesBuffer.BUF_EDGE_ID); end.copyFrom(start); @@ -725,6 +744,9 @@ private Query writeQueryEdgePrefixCondition(ConditionQuery cq) { } else if (key == HugeKeys.LABEL) { assert value instanceof Id; buffer.writeId((Id) value); + } else if (key == HugeKeys.SUB_LABEL) { + assert value instanceof Id; + buffer.writeId((Id) value); } else if (key == HugeKeys.SORT_VALUES) { assert value instanceof String; buffer.writeStringWithEnding((String) value); @@ -1104,8 +1126,7 @@ public VertexLabel readVertexLabel(HugeGraph graph, public BinaryBackendEntry writeEdgeLabel(EdgeLabel schema) { this.entry = newBackendEntry(schema); writeString(HugeKeys.NAME, schema.name()); - writeId(HugeKeys.SOURCE_LABEL, schema.sourceLabel()); - writeId(HugeKeys.TARGET_LABEL, schema.targetLabel()); + writeIds(HugeKeys.LINKS, schema.linksIds()); writeEnum(HugeKeys.FREQUENCY, schema.frequency()); writeIds(HugeKeys.PROPERTIES, schema.properties()); writeIds(HugeKeys.SORT_KEYS, schema.sortKeys()); @@ -1127,8 +1148,7 @@ public EdgeLabel readEdgeLabel(HugeGraph graph, String name = readString(HugeKeys.NAME); EdgeLabel edgeLabel = new EdgeLabel(graph, id, name); - edgeLabel.sourceLabel(readId(HugeKeys.SOURCE_LABEL)); - edgeLabel.targetLabel(readId(HugeKeys.TARGET_LABEL)); + edgeLabel.linksIds(readIds(HugeKeys.LINKS)); edgeLabel.frequency(readEnum(HugeKeys.FREQUENCY, Frequency.class)); edgeLabel.properties(readIds(HugeKeys.PROPERTIES)); edgeLabel.sortKeys(readIds(HugeKeys.SORT_KEYS)); diff --git a/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/backend/serializer/BytesBuffer.java b/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/backend/serializer/BytesBuffer.java index c13d611082..2c890ebe46 100644 --- a/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/backend/serializer/BytesBuffer.java +++ b/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/backend/serializer/BytesBuffer.java @@ -697,11 +697,12 @@ public Id readId() { } public BytesBuffer writeEdgeId(Id id) { - // owner-vertex + dir + edge-label + sort-values + other-vertex + // owner-vertex + dir + edge-label + sub-edge-label + sort-values + other-vertex EdgeId edge = (EdgeId) id; this.writeId(edge.ownerVertexId()); this.write(edge.directionCode()); this.writeId(edge.edgeLabelId()); + this.writeId(edge.subLabelId()); this.writeStringWithEnding(edge.sortValues()); this.writeId(edge.otherVertexId()); return this; @@ -709,7 +710,7 @@ public BytesBuffer writeEdgeId(Id id) { public Id readEdgeId() { return new EdgeId(this.readId(), EdgeId.directionFromCode(this.read()), - this.readId(), this.readStringWithEnding(), + this.readId(), this.readId(), this.readStringWithEnding(), this.readId()); } diff --git a/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/backend/serializer/TableSerializer.java b/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/backend/serializer/TableSerializer.java index f5c31d26fd..a2e3279976 100644 --- a/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/backend/serializer/TableSerializer.java +++ b/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/backend/serializer/TableSerializer.java @@ -61,6 +61,9 @@ import org.apache.hugegraph.util.E; import org.apache.hugegraph.util.JsonUtil; +/** + * without father and sub edge label + */ public abstract class TableSerializer extends AbstractSerializer { public TableSerializer(HugeConfig config) { diff --git a/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/backend/serializer/TableSerializerV2.java b/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/backend/serializer/TableSerializerV2.java new file mode 100644 index 0000000000..54f9e4cf54 --- /dev/null +++ b/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/backend/serializer/TableSerializerV2.java @@ -0,0 +1,129 @@ +/* + * 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.backend.serializer; + +import org.apache.hugegraph.HugeGraph; +import org.apache.hugegraph.backend.id.EdgeId; +import org.apache.hugegraph.backend.id.Id; +import org.apache.hugegraph.backend.id.IdGenerator; +import org.apache.hugegraph.backend.store.BackendEntry; +import org.apache.hugegraph.config.HugeConfig; +import org.apache.hugegraph.schema.EdgeLabel; +import org.apache.hugegraph.schema.VertexLabel; +import org.apache.hugegraph.structure.HugeEdge; +import org.apache.hugegraph.structure.HugeEdgeProperty; +import org.apache.hugegraph.structure.HugeVertex; +import org.apache.hugegraph.type.define.HugeKeys; + +/** + * with father and sub edge label + */ +public abstract class TableSerializerV2 extends TableSerializer { + + public TableSerializerV2(HugeConfig config) { + super(config); + } + + @Override + protected TableBackendEntry.Row formatEdge(HugeEdge edge) { + EdgeId id = edge.idWithDirection(); + TableBackendEntry.Row row = new TableBackendEntry.Row(edge.type(), id); + if (edge.hasTtl()) { + row.ttl(edge.ttl()); + row.column(HugeKeys.EXPIRED_TIME, edge.expiredTime()); + } + // Id: ownerVertex + direction + edge-label + sub-edge-label + sortValues + otherVertex + row.column(HugeKeys.OWNER_VERTEX, this.writeId(id.ownerVertexId())); + row.column(HugeKeys.DIRECTION, id.directionCode()); + row.column(HugeKeys.LABEL, id.edgeLabelId().asLong()); + row.column(HugeKeys.SUB_LABEL, id.subLabelId().asLong()); + row.column(HugeKeys.SORT_VALUES, id.sortValues()); + row.column(HugeKeys.OTHER_VERTEX, this.writeId(id.otherVertexId())); + + this.formatProperties(edge, row); + return row; + } + + /** + * Parse an edge from a entry row + * + * @param row edge entry + * @param vertex null or the source vertex + * @param graph the HugeGraph context object + * @return the source vertex + */ + @Override + protected HugeEdge parseEdge(TableBackendEntry.Row row, + HugeVertex vertex, HugeGraph graph) { + Object ownerVertexId = row.column(HugeKeys.OWNER_VERTEX); + Number dir = row.column(HugeKeys.DIRECTION); + boolean direction = EdgeId.isOutDirectionFromCode(dir.byteValue()); + Number label = row.column(HugeKeys.LABEL); + Number subLabel = row.column(HugeKeys.SUB_LABEL); + String sortValues = row.column(HugeKeys.SORT_VALUES); + Object otherVertexId = row.column(HugeKeys.OTHER_VERTEX); + Number expiredTime = row.column(HugeKeys.EXPIRED_TIME); + + if (vertex == null) { + Id ownerId = this.readId(ownerVertexId); + vertex = new HugeVertex(graph, ownerId, VertexLabel.NONE); + } + + EdgeLabel edgeLabel = graph.edgeLabelOrNone(this.toId(label)); + EdgeLabel subEdgeLabel = graph.edgeLabelOrNone(this.toId(subLabel)); + Id otherId = this.readId(otherVertexId); + + // Construct edge + HugeEdge edge = HugeEdge.constructEdge(vertex, direction, subEdgeLabel, + sortValues, otherId); + + // Parse edge properties + this.parseProperties(edge, row); + + // The expired time is null when the edge is non-ttl + long expired = edge.hasTtl() ? expiredTime.longValue() : 0L; + edge.expiredTime(expired); + + return edge; + } + + @Override + public BackendEntry writeEdgeProperty(HugeEdgeProperty prop) { + HugeEdge edge = prop.element(); + EdgeId id = edge.idWithDirection(); + TableBackendEntry.Row row = new TableBackendEntry.Row(edge.type(), id); + if (edge.hasTtl()) { + row.ttl(edge.ttl()); + row.column(HugeKeys.EXPIRED_TIME, edge.expiredTime()); + } + // Id: ownerVertex + direction + edge-label + sub-edge-label + sortValues + otherVertex + row.column(HugeKeys.OWNER_VERTEX, this.writeId(id.ownerVertexId())); + row.column(HugeKeys.DIRECTION, id.directionCode()); + row.column(HugeKeys.LABEL, id.edgeLabelId().asLong()); + row.column(HugeKeys.SUB_LABEL, id.subLabelId().asLong()); + row.column(HugeKeys.SORT_VALUES, id.sortValues()); + row.column(HugeKeys.OTHER_VERTEX, this.writeId(id.otherVertexId())); + + // Format edge property + this.formatProperty(prop, row); + + TableBackendEntry entry = newBackendEntry(row); + entry.subId(IdGenerator.of(prop.key())); + return entry; + } +} diff --git a/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/backend/serializer/TextSerializer.java b/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/backend/serializer/TextSerializer.java index 5a39aeaeaf..2d5cb81ec1 100644 --- a/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/backend/serializer/TextSerializer.java +++ b/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/backend/serializer/TextSerializer.java @@ -223,11 +223,12 @@ private void parseEdge(String colName, String colValue, HugeGraph graph = vertex.graph(); boolean direction = colParts[0].equals(EDGE_OUT_TYPE); - String sortValues = readEdgeName(colParts[2]); + String sortValues = readEdgeName(colParts[3]); EdgeLabel edgeLabel = graph.edgeLabelOrNone(readId(colParts[1])); - Id otherVertexId = readEntryId(colParts[3]); + EdgeLabel subEdgeLabel = graph.edgeLabelOrNone(readId(colParts[2])); + Id otherVertexId = readEntryId(colParts[4]); // Construct edge - HugeEdge edge = HugeEdge.constructEdge(vertex, direction, edgeLabel, + HugeEdge edge = HugeEdge.constructEdge(vertex, direction, subEdgeLabel, sortValues, otherVertexId); String[] valParts = colValue.split(VALUE_SPLITOR); @@ -772,6 +773,7 @@ private String writeEdgeId(Id id, boolean withOwnerVertex) { // Edge name: type + edge-label-name + sortKeys + targetVertex list.add(writeType(edgeId.direction().type())); list.add(writeId(edgeId.edgeLabelId())); + list.add(writeId(edgeId.subLabelId())); list.add(writeEdgeName(edgeId.sortValues())); list.add(writeEntryId(edgeId.otherVertexId())); diff --git a/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/backend/store/AbstractBackendStore.java b/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/backend/store/AbstractBackendStore.java index 2ef9a6db73..adb3c8c400 100644 --- a/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/backend/store/AbstractBackendStore.java +++ b/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/backend/store/AbstractBackendStore.java @@ -17,8 +17,24 @@ package org.apache.hugegraph.backend.store; +import org.apache.hugegraph.HugeGraph; +import org.apache.hugegraph.backend.query.Condition; +import org.apache.hugegraph.backend.query.ConditionQuery; +import org.apache.hugegraph.backend.query.ConditionQueryFlatten; +import org.apache.hugegraph.backend.query.Query; import org.apache.hugegraph.exception.ConnectionException; +import org.apache.hugegraph.iterator.ExtendableIterator; +import org.apache.hugegraph.iterator.FlatMapperIterator; import org.apache.hugegraph.type.HugeType; +import org.apache.hugegraph.type.define.Directions; +import org.apache.hugegraph.type.define.HugeKeys; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Set; +import java.util.function.Function; public abstract class AbstractBackendStore implements BackendStore { @@ -36,6 +52,57 @@ protected MetaDispatcher metaDispatcher() { return this.dispatcher; } + protected List getHugeTypes(Query sampleQuery) { + Set typeSet = new HashSet<>(); + for (Condition c : sampleQuery.conditions()) { + if (c.isRelation() && c.isSysprop()) { + Condition.SyspropRelation sr = (Condition.SyspropRelation) c; + if (sr.relation() == Condition.RelationType.EQ) { + if (sr.key().equals(HugeKeys.DIRECTION)) { + typeSet.add(((Directions) sr.value()).type()); + } + } + } else if (c.type() == Condition.ConditionType.OR && c.isSysprop()) { + for (Condition.Relation r : c.relations()) { + if (r.relation() == Condition.RelationType.EQ) { + if (r.key().equals(HugeKeys.DIRECTION)) { + typeSet.add(((Directions) r.value()).type()); + } + } + } + } + } + return new ArrayList<>(typeSet); + } + + @Override + public Iterator> query(Iterator queries, + Function queryWriter, + HugeGraph hugeGraph) { + List> result = new ArrayList<>(); + + FlatMapperIterator it = + new FlatMapperIterator<>(queries, query -> { + assert query instanceof ConditionQuery; + List flattenQueryList = + ConditionQueryFlatten.flatten((ConditionQuery) query); + + if (flattenQueryList.size() > 1) { + ExtendableIterator itExtend + = new ExtendableIterator<>(); + flattenQueryList.forEach(cq -> { + Query cQuery = queryWriter.apply(cq); + itExtend.extend(this.query(cQuery)); + }); + return itExtend; + } else { + return this.query(queryWriter.apply(query)); + } + }); + result.add(it); + return result.iterator(); + } + public void registerMetaHandler(String name, MetaHandler handler) { this.dispatcher.registerMetaHandler(name, handler); } diff --git a/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/backend/store/BackendFeatures.java b/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/backend/store/BackendFeatures.java index 90001cc1e7..c6060944f0 100644 --- a/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/backend/store/BackendFeatures.java +++ b/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/backend/store/BackendFeatures.java @@ -31,7 +31,13 @@ default boolean supportsSnapshot() { return false; } - default boolean supportsTaskAndServerVertex() { return false; } + default boolean supportsTaskAndServerVertex() { + return false; + } + + default boolean supportsFatherAndSubEdgeLabel() { + return true; + } boolean supportsScanToken(); diff --git a/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/backend/store/BackendStore.java b/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/backend/store/BackendStore.java index 5c05e37c5a..6486d353c1 100644 --- a/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/backend/store/BackendStore.java +++ b/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/backend/store/BackendStore.java @@ -19,7 +19,9 @@ import java.util.Iterator; import java.util.Map; +import java.util.function.Function; +import org.apache.hugegraph.HugeGraph; import org.apache.hugegraph.backend.id.Id; import org.apache.hugegraph.backend.id.IdGenerator; import org.apache.hugegraph.backend.query.Query; @@ -70,6 +72,11 @@ public interface BackendStore { // Query data Iterator query(Query query); + // TODO: unused now + Iterator> query(Iterator queries, + Function queryWriter, + HugeGraph hugeGraph); + Number queryNumber(Query query); // Transaction diff --git a/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/backend/store/memory/InMemoryDBTables.java b/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/backend/store/memory/InMemoryDBTables.java index 19321a5514..445c28a5d6 100644 --- a/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/backend/store/memory/InMemoryDBTables.java +++ b/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/backend/store/memory/InMemoryDBTables.java @@ -237,23 +237,62 @@ protected Map queryByFilter( } // Only support querying edge by label - E.checkState(conditions.size() == 1, - "Not support querying edge by %s", conditions); - Condition cond = conditions.iterator().next(); - E.checkState(cond.isRelation(), - "Not support querying edge by %s", conditions); - Condition.Relation relation = (Condition.Relation) cond; - E.checkState(relation.key().equals(HugeKeys.LABEL), - "Not support querying edge by %s", conditions); - String label = (String) relation.serialValue(); + if (conditions.size() == 1) { + E.checkState(conditions.size() == 1, + "Not support querying edge by %s", conditions); + Condition cond = conditions.iterator().next(); + E.checkState(cond.isRelation(), + "Not support querying edge by %s", conditions); + Condition.Relation relation = (Condition.Relation) cond; + E.checkState(relation.key().equals(HugeKeys.LABEL), + "Not support querying edge by %s", conditions); + String label = (String) relation.serialValue(); + String out = EdgeId.concat(HugeType.EDGE_OUT.string(), label); + String in = EdgeId.concat(HugeType.EDGE_IN.string(), label); + return queryByFilterInternal(out, in, entries); + } else { + E.checkState(conditions.size() == 2, + "Not support querying edge by %s", conditions); + Iterator conditionIterator = conditions.iterator(); + + Condition cond1 = conditionIterator.next(); + E.checkState(cond1.isRelation(), + "Not support querying edge by %s", conditions); + Condition.Relation relation1 = (Condition.Relation) cond1; + Condition cond2 = conditionIterator.next(); + E.checkState(cond2.isRelation(), + "Not support querying edge by %s", conditions); + Condition.Relation relation2 = (Condition.Relation) cond2; + + if (relation1.key().equals(HugeKeys.LABEL) && relation2.key().equals(HugeKeys.SUB_LABEL)) { + String label = (String) relation1.serialValue(); + String subLabel = (String) relation2.serialValue(); + String out = EdgeId.concat(HugeType.EDGE_OUT.string(), label, subLabel); + String in = EdgeId.concat(HugeType.EDGE_IN.string(), label, subLabel); + return queryByFilterInternal(out, in, entries); + } else if (relation2.key().equals(HugeKeys.LABEL) && relation1.key().equals(HugeKeys.SUB_LABEL)) { + String label = (String) relation2.serialValue(); + String subLabel = (String) relation1.serialValue(); + String out = EdgeId.concat(HugeType.EDGE_OUT.string(), label, subLabel); + String in = EdgeId.concat(HugeType.EDGE_IN.string(), label, subLabel); + return queryByFilterInternal(out, in, entries); + } else { + E.checkState(false, "Not support querying edge by %s", conditions); + } + } + return entries; + } + + private Map queryByFilterInternal( + String out, String in, + Map entries) { Map rs = InsertionOrderUtil.newMap(); for (BackendEntry value : entries.values()) { // TODO: Compatible with BackendEntry TextBackendEntry entry = (TextBackendEntry) value; - String out = EdgeId.concat(HugeType.EDGE_OUT.string(), label); - String in = EdgeId.concat(HugeType.EDGE_IN.string(), label); + if (entry.containsPrefix(out)) { BackendEntry edges = new TextBackendEntry(HugeType.VERTEX, entry.id()); diff --git a/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/backend/store/raft/RaftBackendStore.java b/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/backend/store/raft/RaftBackendStore.java index bf89c6b17a..1765562127 100644 --- a/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/backend/store/raft/RaftBackendStore.java +++ b/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/backend/store/raft/RaftBackendStore.java @@ -22,6 +22,7 @@ import java.util.List; import java.util.function.Function; +import org.apache.hugegraph.HugeGraph; import org.apache.hugegraph.backend.BackendException; import org.apache.hugegraph.backend.query.Query; import org.apache.hugegraph.backend.store.BackendEntry; @@ -152,6 +153,13 @@ public Iterator query(Query query) { this.queryByRaft(query, o -> this.store.query(query)); } + @Override + public Iterator> query(Iterator queries, + Function queryWriter, + HugeGraph hugeGraph) { + throw new UnsupportedOperationException("unimplemented for RaftBackendStore"); + } + @Override public Number queryNumber(Query query) { return (Number) diff --git a/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/backend/tx/GraphIndexTransaction.java b/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/backend/tx/GraphIndexTransaction.java index 3905ebb0cc..ddf942ec32 100644 --- a/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/backend/tx/GraphIndexTransaction.java +++ b/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/backend/tx/GraphIndexTransaction.java @@ -67,6 +67,7 @@ import org.apache.hugegraph.job.EphemeralJob; import org.apache.hugegraph.job.system.DeleteExpiredJob; import org.apache.hugegraph.perf.PerfUtil.Watched; +import org.apache.hugegraph.schema.EdgeLabel; import org.apache.hugegraph.schema.IndexLabel; import org.apache.hugegraph.schema.PropertyKey; import org.apache.hugegraph.schema.SchemaLabel; @@ -147,6 +148,17 @@ public void updateLabelIndex(HugeElement element, boolean removed) { } else { this.doAppend(this.serializer.writeIndex(index)); } + + if (element instanceof HugeEdge && ((EdgeLabel) label).hasFather()) { + HugeIndex fatherIndex = new HugeIndex(this.graph(), IndexLabel.label(element.type())); + fatherIndex.fieldValues(((EdgeLabel) label).fatherId()); + fatherIndex.elementIds(element.id(), element.expiredTime()); + if (removed) { + this.doEliminate(this.serializer.writeIndex(fatherIndex)); + } else { + this.doAppend(this.serializer.writeIndex(fatherIndex)); + } + } } @Watched(prefix = "index") @@ -167,6 +179,13 @@ public void updateEdgeIndex(HugeEdge edge, boolean removed) { for (Id id : edge.schemaLabel().indexLabels()) { this.updateIndex(id, edge, removed); } + + EdgeLabel label = edge.schemaLabel(); + if (label.hasFather()) { + for (Id id : graph().edgeLabel(label.fatherId()).indexLabels()) { + this.updateIndex(id, edge, removed); + } + } } private void updateVertexOlapIndex(HugeVertex vertex, boolean removed) { diff --git a/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/backend/tx/GraphTransaction.java b/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/backend/tx/GraphTransaction.java index 10aa73f719..ccd2b5405f 100644 --- a/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/backend/tx/GraphTransaction.java +++ b/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/backend/tx/GraphTransaction.java @@ -20,16 +20,21 @@ import java.util.ArrayList; import java.util.Arrays; 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.function.BiFunction; import java.util.function.Consumer; import java.util.function.Function; import java.util.stream.Collectors; +import java.util.stream.Stream; + +import com.google.common.collect.Iterators; import org.apache.commons.collections.CollectionUtils; import org.apache.hugegraph.HugeException; @@ -70,6 +75,7 @@ import org.apache.hugegraph.schema.EdgeLabel; import org.apache.hugegraph.schema.IndexLabel; import org.apache.hugegraph.schema.PropertyKey; +import org.apache.hugegraph.schema.SchemaElement; import org.apache.hugegraph.schema.SchemaLabel; import org.apache.hugegraph.schema.VertexLabel; import org.apache.hugegraph.structure.HugeEdge; @@ -370,7 +376,7 @@ protected void prepareDeletions(Map removedVertices, continue; } // Query all edges of the vertex and remove them - Query query = constructEdgesQuery(v.id(), Directions.BOTH); + Query query = constructEdgesQuery(v.id(), Directions.BOTH, new Id[0]); Iterator vedges = this.queryEdgesFromBackend(query); try { while (vedges.hasNext()) { @@ -913,7 +919,7 @@ public void removeEdge(HugeEdge edge) { } public Iterator queryEdgesByVertex(Id id) { - return this.queryEdges(constructEdgesQuery(id, Directions.BOTH)); + return this.queryEdges(constructEdgesQuery(id, Directions.BOTH, new Id[0])); } public Iterator queryEdges(Object... edgeIds) { @@ -1044,6 +1050,38 @@ public Iterator queryEdges(Query query) { protected Iterator queryEdgesFromBackend(Query query) { assert query.resultType().isEdge(); + if (query instanceof ConditionQuery && !query.paging()) { + // TODO: support: paging + parent label + boolean supportIn = this.storeFeatures().supportsQueryWithInCondition(); + // consider multi labels + properties, see org.apache.hugegraph.core.EdgeCoreTest.testQueryInEdgesOfVertexByLabels + Stream flattenedQueries = ConditionQueryFlatten.flatten((ConditionQuery) query, supportIn).stream(); + + Stream> edgeIterators = flattenedQueries.map(cq -> { + Id label = cq.condition(HugeKeys.LABEL); + if (this.storeFeatures().supportsFatherAndSubEdgeLabel() && + label != null && + graph().edgeLabel(label).isFather() && + cq.condition(HugeKeys.SUB_LABEL) == null && + cq.condition(HugeKeys.OWNER_VERTEX) != null && + cq.condition(HugeKeys.DIRECTION) != null && + matchEdgeSortKeys(cq, false, this.graph())) { + // g.V("V.id").outE("parentLabel").has("sortKey","value") + return parentElQueryWithSortKeys( + graph().edgeLabel(label), graph().edgeLabels(), cq); + } else { + return queryEdgesFromBackendInternal(cq); + } + }); + + return edgeIterators.reduce(ExtendableIterator::concat).orElse(Collections.emptyIterator()); + } + + return queryEdgesFromBackendInternal(query); + } + + private Iterator queryEdgesFromBackendInternal(Query query) { + assert query.resultType().isEdge(); + QueryResults results = this.query(query); Iterator entries = results.iterator(); @@ -1070,6 +1108,20 @@ protected Iterator queryEdgesFromBackend(Query query) { return edges; } + private Iterator parentElQueryWithSortKeys(EdgeLabel label, + Collection allEls, + ConditionQuery cq) { + return allEls.stream() + .filter(el -> el.edgeLabelType().sub() && el.fatherId().equals(label.id())) + .map(el -> { + ConditionQuery tempQuery = cq.copy(); + tempQuery.eq(HugeKeys.SUB_LABEL, el.id()); + return this.queryEdgesFromBackend(tempQuery); + }) + .reduce(Iterators::concat) + .orElse(Collections.emptyIterator()); + } + @Watched(prefix = "graph") public void addVertexProperty(HugeVertexProperty prop) { // NOTE: this method can also be used to update property @@ -1238,6 +1290,13 @@ public void removeEdgeProperty(HugeEdgeProperty prop) { public static ConditionQuery constructEdgesQuery(Id sourceVertex, Directions direction, Id... edgeLabels) { + return constructEdgesQuery(sourceVertex, direction, List.of(edgeLabels)); + } + + @Watched + public static ConditionQuery constructEdgesQuery(Id sourceVertex, + Directions direction, + EdgeLabel... edgeLabels) { E.checkState(sourceVertex != null, "The edge query must contain source vertex"); E.checkState(direction != null, @@ -1260,16 +1319,25 @@ public static ConditionQuery constructEdgesQuery(Id sourceVertex, // Edge labels if (edgeLabels.length == 1) { - query.eq(HugeKeys.LABEL, edgeLabels[0]); - } else if (edgeLabels.length > 1) { - query.query(Condition.in(HugeKeys.LABEL, - Arrays.asList(edgeLabels))); + EdgeLabel edgeLabel = edgeLabels[0]; + if (edgeLabel.hasFather()) { + query.eq(HugeKeys.LABEL, edgeLabel.fatherId()); + query.eq(HugeKeys.SUB_LABEL, edgeLabel.id()); + } else { + query.eq(HugeKeys.LABEL, edgeLabel.id()); + } + } else if (edgeLabels.length >= 1) { + query.query( + Condition.in(HugeKeys.LABEL, + Arrays.stream(edgeLabels) + .map(SchemaElement::id) + .collect(Collectors.toList()))); } return query; } - public static ConditionQuery constructEdgesQuery(Id sourceVertex, + private static ConditionQuery constructEdgesQuery(Id sourceVertex, Directions direction, List edgeLabels) { E.checkState(sourceVertex != null, @@ -1522,7 +1590,8 @@ private Query optimizeQuery(ConditionQuery query) { query.optimized(OptimizedType.SORT_KEYS); query = query.copy(); // Serialize sort-values - List keys = this.graph().edgeLabel(label).sortKeys(); + EdgeLabel el = this.graph().edgeLabel(label); + List keys = el.sortKeys(); List conditions = GraphIndexTransaction .constructShardConditions(query, keys, HugeKeys.SORT_VALUES); query.query(conditions); @@ -1532,6 +1601,9 @@ private Query optimizeQuery(ConditionQuery query) { */ query.resetUserpropConditions(); + if (this.storeFeatures().supportsFatherAndSubEdgeLabel() && query.condition(HugeKeys.SUB_LABEL) == null) { + query.eq(HugeKeys.SUB_LABEL, el.id()); + } LOG.debug("Query edges by sortKeys: {}", query); return query; } @@ -1546,6 +1618,19 @@ private Query optimizeQuery(ConditionQuery query) { if (query.resultType().isVertex()) { verifyVerticesConditionQuery(query); } else if (query.resultType().isEdge()) { + // fix org.apache.hugegraph.api.traverser.EdgeExistenceAPITest#testEdgeExistenceGet + // add sub label if only the sub label is missing + ConditionQuery finalQuery = query; + if (this.storeFeatures().supportsFatherAndSubEdgeLabel() && + query.condition(HugeKeys.SUB_LABEL) == null && + Arrays.stream(EdgeId.KEYS) + .filter(key -> !Objects.equals(key, HugeKeys.SUB_LABEL)) + .allMatch(key -> finalQuery.condition(key) != null)) { + EdgeLabel el = this.graph().edgeLabel(label); + if (!el.isFather()) { + query.eq(HugeKeys.SUB_LABEL, el.id()); + } + } verifyEdgesConditionQuery(query); } /* @@ -1555,6 +1640,15 @@ private Query optimizeQuery(ConditionQuery query) { */ boolean byLabel = (label != null && query.conditionsSize() == 1); if (!byLabel || this.store().features().supportsQueryByLabel()) { + if (this.storeFeatures().supportsFatherAndSubEdgeLabel() && byLabel && query.resultType().isEdge()) { + // for memory backend + EdgeLabel edgeLabel = graph().edgeLabel(label); + if (edgeLabel.hasFather()) { + query.resetConditions(); + query.eq(HugeKeys.LABEL, edgeLabel.fatherId()); + query.eq(HugeKeys.SUB_LABEL, edgeLabel.id()); + } + } return query; } } @@ -1815,6 +1909,24 @@ private boolean rightResultFromIndexQuery(Query query, HugeElement elem) { } ConditionQuery cq = (ConditionQuery) query; + if (cq.condition(HugeKeys.LABEL) != null && cq.resultType().isEdge()) { + if (cq.conditions().size() == 1) { + // g.E().hasLabel(xxx) + return true; + } + if (cq.optimized() == OptimizedType.INDEX) { + // g.E().hasLabel(xxx).has(yyy) + // consider OptimizedType.INDEX_FILTER occurred in org.apache.hugegraph.core.EdgeCoreTest.testQueryCount + try { + this.indexTx.asyncRemoveIndexLeft(cq, elem); + } catch (Throwable e) { + LOG.warn("Failed to remove left index for query '{}', " + + "element '{}'", cq, elem, e); + } + return true; + } + } + if (cq.optimized() == OptimizedType.NONE || cq.test(elem)) { if (cq.existLeftIndex(elem.id())) { /* diff --git a/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/backend/tx/SchemaTransaction.java b/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/backend/tx/SchemaTransaction.java index 1d7f595f0d..d58bfcdc0e 100644 --- a/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/backend/tx/SchemaTransaction.java +++ b/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/backend/tx/SchemaTransaction.java @@ -247,9 +247,21 @@ public EdgeLabel getEdgeLabel(String name) { @Watched(prefix = "schema") public Id removeEdgeLabel(Id id) { LOG.debug("SchemaTransaction remove edge label '{}'", id); - SchemaJob callable = new EdgeLabelRemoveJob(); EdgeLabel schema = this.getEdgeLabel(id); - return asyncRun(this.graph(), schema, callable); + if (schema.edgeLabelType().parent()) { + List edgeLabels = this.getEdgeLabels(); + for (EdgeLabel edgeLabel : edgeLabels) { + if (edgeLabel.edgeLabelType().sub() && + edgeLabel.fatherId() == id) { + throw new NotAllowException( + "Not allowed to remove a parent edge label: '%s' " + + "because the sub edge label '%s' is still existing", + schema.name(), edgeLabel.name()); + } + } + } + SchemaJob job = new EdgeLabelRemoveJob(); + return asyncRun(this.graph(), schema, job); } @Watched(prefix = "schema") diff --git a/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/backend/tx/SchemaTransactionV2.java b/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/backend/tx/SchemaTransactionV2.java index 8bf0a68b20..903bfade68 100644 --- a/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/backend/tx/SchemaTransactionV2.java +++ b/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/backend/tx/SchemaTransactionV2.java @@ -288,19 +288,18 @@ public Id removeEdgeLabel(Id id) { */ LOG.debug("SchemaTransaction remove edge label '{}'", id); EdgeLabel schema = this.getEdgeLabel(id); - // TODO: uncomment later - sub edge labels - //if (schema.edgeLabelType().parent()) { - // List edgeLabels = this.getEdgeLabels(); - // for (EdgeLabel edgeLabel : edgeLabels) { - // if (edgeLabel.edgeLabelType().sub() && - // edgeLabel.fatherId() == id) { - // throw new NotAllowException( - // "Not allowed to remove a parent edge label: '%s' " + - // "because the sub edge label '%s' is still existing", - // schema.name(), edgeLabel.name()); - // } - // } - //} + if (schema.edgeLabelType().parent()) { + List edgeLabels = this.getEdgeLabels(); + for (EdgeLabel edgeLabel : edgeLabels) { + if (edgeLabel.edgeLabelType().sub() && + edgeLabel.fatherId() == id) { + throw new NotAllowException( + "Not allowed to remove a parent edge label: '%s' " + + "because the sub edge label '%s' is still existing", + schema.name(), edgeLabel.name()); + } + } + } SchemaJob job = new EdgeLabelRemoveJob(); return asyncRun(this.graph(), schema, job); } diff --git a/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/io/GraphSONSchemaSerializer.java b/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/io/GraphSONSchemaSerializer.java index b92d506532..31d197c099 100644 --- a/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/io/GraphSONSchemaSerializer.java +++ b/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/io/GraphSONSchemaSerializer.java @@ -20,12 +20,15 @@ import java.util.LinkedHashMap; import java.util.Map; +import com.google.common.collect.ImmutableList; + import org.apache.hugegraph.HugeGraph; import org.apache.hugegraph.schema.EdgeLabel; import org.apache.hugegraph.schema.IndexLabel; import org.apache.hugegraph.schema.PropertyKey; import org.apache.hugegraph.schema.VertexLabel; import org.apache.hugegraph.type.HugeType; +import org.apache.hugegraph.type.define.EdgeLabelType; import org.apache.hugegraph.type.define.HugeKeys; public class GraphSONSchemaSerializer { @@ -64,25 +67,43 @@ public Map writeEdgeLabel(EdgeLabel edgeLabel) { Map map = new LinkedHashMap<>(); map.put(HugeKeys.ID, edgeLabel.id().asLong()); map.put(HugeKeys.NAME, edgeLabel.name()); - map.put(HugeKeys.SOURCE_LABEL, edgeLabel.sourceLabelName()); - map.put(HugeKeys.TARGET_LABEL, edgeLabel.targetLabelName()); - map.put(HugeKeys.FREQUENCY, edgeLabel.frequency()); - map.put(HugeKeys.SORT_KEYS, - graph.mapPkId2Name(edgeLabel.sortKeys())); - map.put(HugeKeys.NULLABLE_KEYS, - graph.mapPkId2Name(edgeLabel.nullableKeys())); - map.put(HugeKeys.INDEX_LABELS, - graph.mapIlId2Name(edgeLabel.indexLabels())); - map.put(HugeKeys.PROPERTIES, - graph.mapPkId2Name(edgeLabel.properties())); - map.put(HugeKeys.STATUS, edgeLabel.status()); - map.put(HugeKeys.TTL, edgeLabel.ttl()); - String ttlStartTimeName = edgeLabel.ttlStartTimeName(); - if (ttlStartTimeName != null) { - map.put(HugeKeys.TTL_START_TIME, ttlStartTimeName); + if (edgeLabel.isFather()) { + map.put(HugeKeys.EDGELABEL_TYPE, EdgeLabelType.PARENT); + if (edgeLabel.links().size() > 0) { + map.put(HugeKeys.LINKS, + graph.mapPairId2Name(edgeLabel.links())); + } + } else if (edgeLabel.hasFather()) { + map.put(HugeKeys.EDGELABEL_TYPE, EdgeLabelType.SUB); + map.put(HugeKeys.PARENT_LABEL, + graph.mapElId2Name(ImmutableList.of(edgeLabel.fatherId())) + .get(0)); + } else { + map.put(HugeKeys.EDGELABEL_TYPE, edgeLabel.edgeLabelType()); } - map.put(HugeKeys.ENABLE_LABEL_INDEX, edgeLabel.enableLabelIndex()); - map.put(HugeKeys.USER_DATA, edgeLabel.userdata()); + + if (!edgeLabel.isFather()) { + map.put(HugeKeys.LINKS, + graph.mapPairId2Name(edgeLabel.links())); + map.put(HugeKeys.FREQUENCY, edgeLabel.frequency()); + map.put(HugeKeys.SORT_KEYS, + graph.mapPkId2Name(edgeLabel.sortKeys())); + map.put(HugeKeys.NULLABLE_KEYS, + graph.mapPkId2Name(edgeLabel.nullableKeys())); + map.put(HugeKeys.INDEX_LABELS, + graph.mapIlId2Name(edgeLabel.indexLabels())); + map.put(HugeKeys.PROPERTIES, + graph.mapPkId2Name(edgeLabel.properties())); + map.put(HugeKeys.STATUS, edgeLabel.status()); + map.put(HugeKeys.TTL, edgeLabel.ttl()); + String ttlStartTimeName = edgeLabel.ttlStartTimeName(); + if (ttlStartTimeName != null) { + map.put(HugeKeys.TTL_START_TIME, ttlStartTimeName); + } + map.put(HugeKeys.ENABLE_LABEL_INDEX, edgeLabel.enableLabelIndex()); + map.put(HugeKeys.USER_DATA, edgeLabel.userdata()); + } + return map; } diff --git a/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/schema/EdgeLabel.java b/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/schema/EdgeLabel.java index b9fac4643a..da572321c1 100644 --- a/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/schema/EdgeLabel.java +++ b/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/schema/EdgeLabel.java @@ -21,11 +21,13 @@ import java.util.Arrays; import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.stream.Collectors; +import org.apache.commons.lang3.tuple.Pair; import org.apache.hugegraph.HugeGraph; import org.apache.hugegraph.backend.id.Id; import org.apache.hugegraph.backend.id.IdGenerator; @@ -43,11 +45,14 @@ public class EdgeLabel extends SchemaLabel { public static final EdgeLabel NONE = new EdgeLabel(null, NONE_ID, UNDEF); + private Set> links = new HashSet<>(); private Id sourceLabel = NONE_ID; private Id targetLabel = NONE_ID; private Frequency frequency; private List sortKeys; - private EdgeLabelType edgeLabelType; + + private EdgeLabelType edgeLabelType = EdgeLabelType.NORMAL; + private Id fatherId; public EdgeLabel(final HugeGraph graph, Id id, String name) { super(graph, id, name); @@ -60,14 +65,34 @@ public HugeType type() { return HugeType.EDGE_LABEL; } - public Frequency frequency() { - return this.frequency; + public boolean isFather() { + return this.edgeLabelType.parent(); } public void edgeLabelType(EdgeLabelType type) { this.edgeLabelType = type; } + public EdgeLabelType edgeLabelType() { + return this.edgeLabelType; + } + + public boolean hasFather() { + return this.edgeLabelType.sub(); + } + + public Id fatherId() { + return this.fatherId; + } + + public void fatherId(Id fatherId) { + this.fatherId = fatherId; + } + + public Frequency frequency() { + return this.frequency; + } + public void frequency(Frequency frequency) { this.frequency = frequency; } @@ -78,53 +103,112 @@ public boolean directed() { } public String sourceLabelName() { - return this.graph.vertexLabelOrNone(this.sourceLabel).name(); + 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.graph.vertexLabelOrNone(this.links.iterator().next().getLeft()).name(); + } + + public List linksIds() { + List ids = new ArrayList<>(this.links.size() * 2); + for (Pair link : this.links) { + ids.add(link.getLeft()); + ids.add(link.getRight()); + } + return ids; + } + + public void linksIds(Id[] ids) { + this.links = new HashSet<>(ids.length / 2); + for (int i = 0; i < ids.length; i += 2) { + this.links.add(Pair.of(ids[i], ids[i + 1])); + } } public Id sourceLabel() { - return this.sourceLabel; + if (links.size() == 1) { + return links.iterator().next().getLeft(); + } + return NONE_ID; } public void sourceLabel(Id id) { - E.checkArgument(this.sourceLabel == NONE_ID, - "Not allowed to set source label multi times " + - "of edge label '%s'", this.name()); - this.sourceLabel = id; + E.checkArgument(this.links.isEmpty(), + "Not allowed add source label to an edge label which " + + "already has links"); + if (this.targetLabel != NONE_ID) { + this.links.add(Pair.of(id, this.targetLabel)); + this.targetLabel = NONE_ID; + } else { + this.sourceLabel = id; + } } public String targetLabelName() { - return this.graph.vertexLabelOrNone(this.targetLabel).name(); + 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.graph.vertexLabelOrNone(this.links.iterator().next().getRight()).name(); } public Id targetLabel() { - return this.targetLabel; + if (links.size() == 1) { + return links.iterator().next().getRight(); + } + return NONE_ID; } public void targetLabel(Id id) { - E.checkArgument(this.targetLabel == NONE_ID, - "Not allowed to set target label multi times " + - "of edge label '%s'", this.name()); - this.targetLabel = id; + E.checkArgument(this.links.isEmpty(), + "Not allowed add source label to an edge label which " + + "already has links"); + if (this.sourceLabel != NONE_ID) { + this.links.add(Pair.of(this.sourceLabel, id)); + this.sourceLabel = NONE_ID; + } else { + this.targetLabel = id; + } } public boolean linkWithLabel(Id id) { - return this.sourceLabel.equals(id) || this.targetLabel.equals(id); + for (Pair link : this.links) { + if (link.getLeft().equals(id) || link.getRight().equals(id)) { + return true; + } + } + return false; } public boolean linkWithVertexLabel(Id label, Directions dir) { - if (dir.equals(Directions.IN)) { - return this.targetLabel.equals(label); - } else if (dir.equals(Directions.OUT)) { - return this.sourceLabel.equals(label); - } else if (dir.equals(Directions.BOTH)) { - return this.targetLabel.equals(label) || this.sourceLabel.equals(label); - } - return false; + return this.links.stream().anyMatch(pair -> { + Id sourceLabel = pair.getLeft(); + Id targetLabel = pair.getRight(); + if (dir.equals(Directions.IN)) { + return targetLabel.equals(label); + } else if (dir.equals(Directions.OUT)) { + return sourceLabel.equals(label); + } else if (dir.equals(Directions.BOTH)) { + return targetLabel.equals(label) || sourceLabel.equals(label); + } + return false; + }); } public boolean checkLinkEqual(Id sourceLabel, Id targetLabel) { - return this.sourceLabel.equals(sourceLabel) && - this.targetLabel.equals(targetLabel); + return this.links.contains(Pair.of(sourceLabel, targetLabel)); + } + + public Set> links() { + return this.links; + } + + public void links(Pair link) { + if (this.links == null) { + this.links = new HashSet<>(); + } + this.links.add(link); } public boolean existSortKeys() { @@ -160,10 +244,16 @@ public interface Builder extends SchemaBuilder { Id rebuildIndex(); + Builder asBase(); + + Builder withBase(String fatherLabel); + Builder link(String sourceLabel, String targetLabel); + @Deprecated Builder sourceLabel(String label); + @Deprecated Builder targetLabel(String label); Builder singleTime(); @@ -221,13 +311,13 @@ public Map asMap() { map.put(P.SORT_KEYS, this.sortKeys); } - //map.put(P.EDGELABEL_TYPE, this.edgeLabelType); - //if (this.fatherId() != null) { - // map.put(P.FATHER_ID, this.fatherId().asString()); - //} + map.put(P.EDGELABEL_TYPE, this.edgeLabelType); + if (this.fatherId() != null) { + map.put(P.FATHER_ID, this.fatherId().asString()); + } map.put(P.ENABLE_LABEL_INDEX, this.enableLabelIndex()); map.put(P.TTL, String.valueOf(this.ttl())); - //map.put(P.LINKS, this.links()); + map.put(P.LINKS, this.links()); map.put(P.FREQUENCY, this.frequency().toString()); return super.asMap(map); @@ -278,17 +368,17 @@ public static EdgeLabel fromMap(Map map, HugeGraph graph) { Long.parseLong((String) entry.getValue()); edgeLabel.ttlStartTime(IdGenerator.of(ttlStartTime)); break; - //case P.LINKS: - // // TODO: serialize and deserialize - // List list = (List) entry.getValue(); - // for (Map m : list) { - // for (Object key : m.keySet()) { - // Id sid = IdGenerator.of(Long.parseLong((String) key)); - // Id tid = IdGenerator.of(Long.parseLong(String.valueOf(m.get(key)))); - // edgeLabel.links(Pair.of(sid, tid)); - // } - // } - // break; + case P.LINKS: + // TODO: serialize and deserialize + List list = (List) entry.getValue(); + for (Map m : list) { + for (Object key : m.keySet()) { + Id sid = IdGenerator.of(Long.parseLong((String) key)); + Id tid = IdGenerator.of(Long.parseLong(String.valueOf(m.get(key)))); + edgeLabel.links(Pair.of(sid, tid)); + } + } + break; case P.SOURCE_LABEL: long sourceLabel = Long.parseLong((String) entry.getValue()); @@ -299,17 +389,17 @@ public static EdgeLabel fromMap(Map map, HugeGraph graph) { Long.parseLong((String) entry.getValue()); edgeLabel.targetLabel(IdGenerator.of(targetLabel)); break; - //case P.FATHER_ID: - // long fatherId = - // Long.parseLong((String) entry.getValue()); - // edgeLabel.fatherId(IdGenerator.of(fatherId)); - // break; - //case P.EDGELABEL_TYPE: - // EdgeLabelType edgeLabelType = - // EdgeLabelType.valueOf( - // ((String) entry.getValue()).toUpperCase()); - // edgeLabel.edgeLabelType(edgeLabelType); - // break; + case P.FATHER_ID: + long fatherId = + Long.parseLong((String) entry.getValue()); + edgeLabel.fatherId(IdGenerator.of(fatherId)); + break; + case P.EDGELABEL_TYPE: + EdgeLabelType edgeLabelType = + EdgeLabelType.valueOf( + ((String) entry.getValue()).toUpperCase()); + edgeLabel.edgeLabelType(edgeLabelType); + break; case P.FREQUENCY: Frequency frequency = Frequency.valueOf(((String) entry.getValue()).toUpperCase()); diff --git a/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/schema/builder/EdgeLabelBuilder.java b/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/schema/builder/EdgeLabelBuilder.java index f7aa460e1e..410b094fb9 100644 --- a/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/schema/builder/EdgeLabelBuilder.java +++ b/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/schema/builder/EdgeLabelBuilder.java @@ -26,11 +26,11 @@ import java.util.Set; import org.apache.commons.collections.CollectionUtils; +import org.apache.commons.lang3.tuple.Pair; import org.apache.hugegraph.HugeGraph; import org.apache.hugegraph.backend.id.Id; import org.apache.hugegraph.backend.id.IdGenerator; import org.apache.hugegraph.backend.tx.ISchemaTransaction; -import org.apache.hugegraph.backend.tx.SchemaTransaction; import org.apache.hugegraph.exception.ExistedException; import org.apache.hugegraph.exception.NotAllowException; import org.apache.hugegraph.exception.NotFoundException; @@ -40,6 +40,7 @@ import org.apache.hugegraph.schema.VertexLabel; import org.apache.hugegraph.type.HugeType; import org.apache.hugegraph.type.define.Action; +import org.apache.hugegraph.type.define.EdgeLabelType; import org.apache.hugegraph.type.define.Frequency; import org.apache.hugegraph.util.CollectionUtil; import org.apache.hugegraph.util.E; @@ -51,6 +52,9 @@ public class EdgeLabelBuilder extends AbstractBuilder private Id id; private String name; + private Set> links; + private EdgeLabelType edgeLabelType; + private String fatherLabel; private String sourceLabel; private String targetLabel; private Frequency frequency; @@ -69,8 +73,9 @@ public EdgeLabelBuilder(ISchemaTransaction transaction, E.checkNotNull(name, "name"); this.id = null; this.name = name; - this.sourceLabel = null; - this.targetLabel = null; + this.links = new HashSet<>(); + this.edgeLabelType = EdgeLabelType.NORMAL; + this.fatherLabel = null; this.frequency = Frequency.DEFAULT; this.properties = new HashSet<>(); this.sortKeys = new ArrayList<>(); @@ -89,8 +94,11 @@ public EdgeLabelBuilder(ISchemaTransaction transaction, HugeGraph origin = copy.graph(); this.id = null; this.name = copy.name(); - this.sourceLabel = copy.sourceLabelName(); - this.targetLabel = copy.targetLabelName(); + this.links = mapPairId2Name(origin, copy.links()); + this.edgeLabelType = copy.edgeLabelType(); + if (copy.edgeLabelType().sub()) { + this.fatherLabel = mapElId2Name(origin, copy.fatherId()); + } this.frequency = copy.frequency(); this.properties = mapPkId2Name(origin, copy.properties()); this.sortKeys = mapPkId2Name(origin, copy.sortKeys()); @@ -108,8 +116,10 @@ public EdgeLabel build() { this.id, this.name); HugeGraph graph = this.graph(); EdgeLabel edgeLabel = new EdgeLabel(graph, id, this.name); - edgeLabel.sourceLabel(graph.vertexLabel(this.sourceLabel).id()); - edgeLabel.targetLabel(graph.vertexLabel(this.targetLabel).id()); + for (Pair link : this.links) { + edgeLabel.links(Pair.of(graph.vertexLabel(link.getLeft()).id(), + graph.vertexLabel(link.getRight()).id())); + } edgeLabel.frequency(this.frequency == Frequency.DEFAULT ? Frequency.SINGLE : this.frequency); edgeLabel.ttl(this.ttl); @@ -132,9 +142,52 @@ public EdgeLabel build() { edgeLabel.nullableKey(propertyKey.id()); } edgeLabel.userdata(this.userdata); + if (this.edgeLabelType.sub()) { + edgeLabel.edgeLabelType(EdgeLabelType.SUB); + EdgeLabel fatherEl = graph.edgeLabel(this.fatherLabel); + edgeLabel.fatherId(fatherEl.id()); + registerInfoToFatherEl(fatherEl, edgeLabel); + } else { + edgeLabel.edgeLabelType(this.edgeLabelType); + } return edgeLabel; } + private void registerInfoToFatherEl(EdgeLabel fatherEl, EdgeLabel subEl) { + HugeGraph graph = this.graph(); + // When the new edge label is a subtype, register the links information + // of the sub edgelabel to the parent edge Label + for (Pair link : this.links) { + fatherEl.links(Pair.of(this.graph().vertexLabel(link.getLeft()).id(), + graph.vertexLabel(link.getRight()).id())); + } + + List fatherSortKeys = fatherEl.sortKeys(); + List subSortKeys = subEl.sortKeys(); + if (fatherSortKeys == null || fatherSortKeys.size() == 0) { + for (Id sortKey : subSortKeys) { + fatherEl.sortKeys(sortKey); + } + } else { + E.checkArgument(fatherSortKeys.size() == subSortKeys.size(), + "The sortKeys of each sub edgelabel need to be " + + "consistent. " + "Currently, the sortKeys of already exist edgelabel " + + "are " + "'%s', " + "and the sortKeys of newly added " + + "sub edgelabel are '%s'", + fatherSortKeys, subSortKeys); + for (int i = 0; i < fatherSortKeys.size(); i++) { + E.checkArgument(fatherSortKeys.get(i).equals(subSortKeys.get(i)), + "The sortKeys of each sub edgelabel need to be " + + "consistent. " + + "Currently, the sortKeys of already exist edgelabel " + + "are " + "'%s', " + "and the sortKeys of newly added " + + "sub edgelabel are '%s'", + fatherSortKeys, subSortKeys); + } + } + this.graph().updateEdgeLabel(fatherEl); + } + @Override public EdgeLabel create() { HugeType type = HugeType.EDGE_LABEL; @@ -177,14 +230,12 @@ public EdgeLabel create() { */ private boolean hasSameProperties(EdgeLabel existedEdgeLabel) { HugeGraph graph = this.graph(); - Id sourceId = graph.vertexLabel(this.sourceLabel).id(); - if (!existedEdgeLabel.sourceLabel().equals(sourceId)) { - return false; - } - - Id targetId = graph.vertexLabel(this.targetLabel).id(); - if (!existedEdgeLabel.targetLabel().equals(targetId)) { - return false; + for (Pair link : existedEdgeLabel.links()) { + String sourceName = graph.vertexLabel(link.getLeft()).name(); + String targetName = graph.vertexLabel(link.getRight()).name(); + if (!this.links.contains(Pair.of(sourceName, targetName))) { + return false; + } } if ((this.frequency == Frequency.DEFAULT && @@ -302,6 +353,28 @@ public Id rebuildIndex() { return this.graph().rebuildIndex(edgeLabel); } + @Override + public EdgeLabel.Builder asBase() { + this.edgeLabelType = EdgeLabelType.PARENT; + return this; + } + + @Override + public EdgeLabel.Builder withBase(String fatherLabel) { + // Check if fatherLabel is reasonable (if it exists or not) + E.checkArgumentNotNull(fatherLabel, "When creating a subtype edgeLabel, " + + "the edgeLabel name of the parent type edgeLabel must" + + " be entered"); + EdgeLabel edgeLabel = this.edgeLabelOrNull(fatherLabel); + if (edgeLabel == null) { + throw new NotFoundException("Can't create subtype edge label '%s' " + + "since it's parent edge label doesn't exist", this.name); + } + this.edgeLabelType = EdgeLabelType.SUB; + this.fatherLabel = fatherLabel; + return this; + } + @Override public EdgeLabelBuilder id(long id) { E.checkArgument(id != 0L, "Not allowed to assign 0 as edge label id"); @@ -340,20 +413,38 @@ public EdgeLabelBuilder sortKeys(String... keys) { @Override public EdgeLabelBuilder link(String sourceLabel, String targetLabel) { - this.sourceLabel(sourceLabel); - this.targetLabel(targetLabel); + if (this.links == null) { + this.links = new HashSet<>(); + } + this.links.add(Pair.of(sourceLabel, targetLabel)); return this; } @Override public EdgeLabelBuilder sourceLabel(String label) { - this.sourceLabel = label; + E.checkArgument(this.links.isEmpty(), + "Not allowed add source label to an edge label which " + + "already has links"); + if (this.targetLabel != null) { + this.links.add(Pair.of(label, this.targetLabel)); + this.targetLabel = null; + } else { + this.sourceLabel = label; + } return this; } @Override public EdgeLabelBuilder targetLabel(String label) { - this.targetLabel = label; + E.checkArgument(this.links.isEmpty(), + "Not allowed add source label to an edge label which " + + "already has links"); + if (this.sourceLabel != null) { + this.links.add(Pair.of(this.sourceLabel, label)); + this.sourceLabel = null; + } else { + this.targetLabel = label; + } return this; } @@ -518,19 +609,32 @@ private void checkSortKeys() { } private void checkRelation() { - String srcLabel = this.sourceLabel; - String tgtLabel = this.targetLabel; - - E.checkArgument(srcLabel != null && tgtLabel != null, - "Must set source and target label " + - "for edge label '%s'", this.name); - - E.checkArgumentNotNull(this.vertexLabelOrNull(srcLabel), - "Undefined source vertex label '%s' " + - "in edge label '%s'", srcLabel, this.name); - E.checkArgumentNotNull(this.vertexLabelOrNull(tgtLabel), - "Undefined target vertex label '%s' " + - "in edge label '%s'", tgtLabel, this.name); + if (this.edgeLabelType.parent()) { + E.checkArgument(this.links.isEmpty(), + "The links of the parent edge label must be empty"); + } else { + E.checkArgument(!this.links.isEmpty(), + "The links of standard and subtype edge label " + + "can't be empty"); + E.checkArgument(this.links.size() == 1, + "The links size of standard and subtype edge " + + "label must be 1"); + for (Pair link : this.links) { + String srcLabel = link.getLeft(); + String tgtLabel = link.getRight(); + E.checkArgument(srcLabel != null && tgtLabel != null, + "Must set source and target label " + + "for edge label '%s'", this.name); + E.checkArgumentNotNull(this.vertexLabelOrNull(srcLabel), + "Undefined source vertex label '%s' " + + "in edge label '%s'", srcLabel, + this.name); + E.checkArgumentNotNull(this.vertexLabelOrNull(tgtLabel), + "Undefined target vertex label '%s' " + + "in edge label '%s'", tgtLabel, + this.name); + } + } } private void checkStableVars() { @@ -544,6 +648,11 @@ private void checkStableVars() { "Not allowed to update target label " + "for edge label '%s', it must be null", this.name); } + if (this.links != null && !this.links.isEmpty()) { + throw new NotAllowException( + "Not allowed to update source/target label " + + "for edge label '%s', it must be null", this.name); + } if (this.frequency != Frequency.DEFAULT) { throw new NotAllowException( "Not allowed to update frequency " + @@ -569,15 +678,17 @@ private void checkTtl() { "Can't set ttl start time if ttl is not set"); return; } - VertexLabel source = this.graph().vertexLabel(this.sourceLabel); - VertexLabel target = this.graph().vertexLabel(this.targetLabel); - E.checkArgument((source.ttl() == 0L || this.ttl <= source.ttl()) && - (target.ttl() == 0L || this.ttl <= target.ttl()), - "The ttl(%s) of edge label '%s' should less than " + - "ttl(%s) of source label '%s' and ttl(%s) of target " + - "label '%s'", this.ttl, this.name, - source.ttl(), this.sourceLabel, - target.ttl(), this.targetLabel); + for (Pair link : this.links) { + VertexLabel source = this.graph().vertexLabel(link.getLeft()); + VertexLabel target = this.graph().vertexLabel(link.getRight()); + E.checkArgument((source.ttl() == 0L || this.ttl <= source.ttl()) && + (target.ttl() == 0L || this.ttl <= target.ttl()), + "The ttl(%s) of edge label '%s' should less than " + + "ttl(%s) of source label '%s' and ttl(%s) of target " + + "label '%s'", this.ttl, this.name, + source.ttl(), link.getLeft(), + target.ttl(), link.getRight()); + } if (this.ttlStartTime == null) { return; } @@ -624,4 +735,13 @@ private static Set mapPkId2Name(HugeGraph graph, Set ids) { private static List mapPkId2Name(HugeGraph graph, List ids) { return graph.mapPkId2Name(ids); } + + private static String mapElId2Name(HugeGraph graph, Id fatherId) { + return graph.mapElId2Name(ImmutableList.of(fatherId)).get(0); + } + + private static Set> mapPairId2Name(HugeGraph graph, + Set> pairs) { + return graph.mapPairId2Name(pairs); + } } diff --git a/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/structure/HugeEdge.java b/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/structure/HugeEdge.java index f38c2b067f..5bf7011a6f 100644 --- a/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/structure/HugeEdge.java +++ b/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/structure/HugeEdge.java @@ -21,6 +21,7 @@ import java.util.Iterator; import java.util.List; +import org.apache.commons.lang3.tuple.Pair; import org.apache.hugegraph.HugeException; import org.apache.hugegraph.HugeGraph; import org.apache.hugegraph.backend.id.EdgeId; @@ -141,9 +142,18 @@ public boolean isDirection(Directions direction) { @Watched(prefix = "edge") public void assignId() { // Generate an id and assign - this.id = new EdgeId(this.ownerVertex(), this.direction(), - this.schemaLabel().id(), this.name(), - this.otherVertex()); + if (this.schemaLabel().hasFather()) { + this.id = new EdgeId(this.ownerVertex(), this.direction(), + this.schemaLabel().fatherId(), + this.schemaLabel().id(), + this.name(), + this.otherVertex()); + } else { + this.id = new EdgeId(this.ownerVertex(), this.direction(), + this.schemaLabel().id(), + this.schemaLabel().id(), + this.name(), this.otherVertex()); + } if (this.fresh()) { int len = this.id.length(); @@ -315,9 +325,15 @@ public Object sysprop(HugeKeys key) { case OWNER_VERTEX: return this.ownerVertex().id(); case LABEL: - return this.schemaLabel().id(); + if (this.schemaLabel().hasFather()) { + return this.schemaLabel().fatherId(); + } else { + return this.schemaLabel().id(); + } case DIRECTION: return this.direction(); + case SUB_LABEL: + return this.schemaLabel().id(); case OTHER_VERTEX: return this.otherVertex().id(); case SORT_VALUES: @@ -363,11 +379,15 @@ public Vertex inVertex() { public void vertices(HugeVertex owner, HugeVertex other) { Id ownerLabel = owner.schemaLabel().id(); - if (ownerLabel.equals(this.label.sourceLabel())) { - this.vertices(true, owner, other); - } else { - assert ownerLabel.equals(this.label.targetLabel()); - this.vertices(false, owner, other); + Id otherLabel = other.schemaLabel().id(); + for (Pair link : this.label.links()) { + if (ownerLabel.equals(link.getLeft()) && + otherLabel.equals(link.getRight())) { + this.vertices(true, owner, other); + } else if (ownerLabel.equals(link.getRight()) && + otherLabel.equals(link.getLeft())) { + this.vertices(false, owner, other); + } } } diff --git a/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/structure/HugeVertex.java b/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/structure/HugeVertex.java index 4726e88e5b..389b0f0e8c 100644 --- a/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/structure/HugeVertex.java +++ b/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/structure/HugeVertex.java @@ -303,6 +303,8 @@ public HugeEdge constructEdge(String label, HugeVertex vertex, E.checkArgument(label != null && !label.isEmpty(), "Edge label can't be null or empty"); EdgeLabel edgeLabel = this.graph().edgeLabel(label); + E.checkArgument(!edgeLabel.isFather(), "Adding an edge of parent type " + + "is not allowed"); // Check link E.checkArgument(edgeLabel.checkLinkEqual(this.schemaLabel().id(), vertex.schemaLabel().id()), diff --git a/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/algorithm/HugeTraverser.java b/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/algorithm/HugeTraverser.java index 57c7c0a31d..8122c79080 100644 --- a/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/algorithm/HugeTraverser.java +++ b/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/algorithm/HugeTraverser.java @@ -479,7 +479,7 @@ public EdgesIterator edgesOfVertices(Iterator sources, public Iterator edgesOfVertex(Id source, Steps steps) { List edgeLabels = steps.edgeLabels(); ConditionQuery cq = GraphTransaction.constructEdgesQuery( - source, steps.direction(), edgeLabels); + source, steps.direction(), edgeLabels.toArray(new Id[0])); cq.capacity(Query.NO_CAPACITY); if (steps.limit() != NO_LIMIT) { cq.limit(steps.limit()); diff --git a/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/optimize/HugeVertexStep.java b/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/optimize/HugeVertexStep.java index c35305e2b8..bd2e1388c8 100644 --- a/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/optimize/HugeVertexStep.java +++ b/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/optimize/HugeVertexStep.java @@ -29,6 +29,7 @@ import org.apache.hugegraph.backend.query.Query; import org.apache.hugegraph.backend.query.QueryResults; import org.apache.hugegraph.backend.tx.GraphTransaction; +import org.apache.hugegraph.schema.EdgeLabel; import org.apache.hugegraph.type.define.Directions; import org.apache.hugegraph.util.Log; import org.apache.tinkerpop.gremlin.process.traversal.Traverser; @@ -132,16 +133,15 @@ protected ConditionQuery constructEdgesQuery( Id vertex = (Id) traverser.get().id(); Directions direction = Directions.convert(this.getDirection()); - Id[] edgeLabels = graph.mapElName2Id(this.getEdgeLabels()); + EdgeLabel[] els = graph.mapElName2El(this.getEdgeLabels()); LOG.debug("HugeVertexStep.edges(): vertex={}, direction={}, " + "edgeLabels={}, has={}", - vertex, direction, edgeLabels, this.hasContainers); + vertex, direction, els, this.hasContainers); - ConditionQuery query = GraphTransaction.constructEdgesQuery( - vertex, direction, edgeLabels); + ConditionQuery query = GraphTransaction.constructEdgesQuery(vertex, direction, els); // Query by sort-keys - if (withEdgeCond && edgeLabels.length == 1) { + if (withEdgeCond && els.length == 1) { TraversalUtil.fillConditionQuery(query, this.hasContainers, graph); if (!GraphTransaction.matchPartialEdgeSortKeys(query, graph)) { // Can't query by sysprop and by index (HugeGraph-749) diff --git a/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/type/define/EdgeLabelType.java b/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/type/define/EdgeLabelType.java index 912ed43d55..2857c6e3b3 100644 --- a/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/type/define/EdgeLabelType.java +++ b/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/type/define/EdgeLabelType.java @@ -19,14 +19,11 @@ public enum EdgeLabelType implements SerialEnum { - NORMAL(1, "NORMAL"), PARENT(2, "PARENT"), SUB(3, "SUB"), - - GENERAL(4, "GENERAL"), ; static { @@ -62,9 +59,4 @@ public boolean parent() { public boolean sub() { return this == SUB; } - - public boolean general() { - return this == GENERAL; - } - } diff --git a/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/type/define/HugeKeys.java b/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/type/define/HugeKeys.java index 5ffa3997ff..0e3536b011 100644 --- a/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/type/define/HugeKeys.java +++ b/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/type/define/HugeKeys.java @@ -48,6 +48,8 @@ public enum HugeKeys { SORT_KEYS(84, "sort_keys"), TTL(85, "ttl"), TTL_START_TIME(86, "ttl_start_time"), + EDGELABEL_TYPE(87, "edgelabel_type"), + PARENT_LABEL(89, "parent_label"), /* Column names of schema type (PropertyKey) */ DATA_TYPE(120, "data_type"), @@ -77,6 +79,7 @@ public enum HugeKeys { SORT_VALUES(206, "sort_values"), PRIMARY_VALUES(207, "primary_values"), EXPIRED_TIME(208, "expired_time"), + SUB_LABEL(211, "sub_label"), PROPERTY_TYPE(249, "property_type"), AGGREGATE_PROPERTIES(250, "aggregate_properties"); diff --git a/hugegraph-server/hugegraph-example/src/main/java/org/apache/hugegraph/example/Example4.java b/hugegraph-server/hugegraph-example/src/main/java/org/apache/hugegraph/example/Example4.java new file mode 100644 index 0000000000..0416dd757f --- /dev/null +++ b/hugegraph-server/hugegraph-example/src/main/java/org/apache/hugegraph/example/Example4.java @@ -0,0 +1,363 @@ +/* + * 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.example; + +import java.util.Arrays; +import java.util.List; + +import org.apache.hugegraph.HugeFactory; +import org.apache.hugegraph.HugeGraph; +import org.apache.hugegraph.backend.BackendException; +import org.apache.hugegraph.backend.tx.GraphTransaction; +import org.apache.hugegraph.meta.MetaManager; +import org.apache.hugegraph.schema.EdgeLabel; +import org.apache.hugegraph.schema.SchemaManager; +import org.apache.hugegraph.testutil.Whitebox; +import org.apache.hugegraph.util.Log; +import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversal; +import org.apache.tinkerpop.gremlin.structure.Edge; +import org.apache.tinkerpop.gremlin.structure.T; +import org.apache.tinkerpop.gremlin.structure.Vertex; +import org.slf4j.Logger; + +import com.google.common.collect.ImmutableList; + +public class Example4 { + + /* This example serves a simple test of a parent-child type of EdgeLabel */ + private static final Logger LOG = Log.logger(Example4.class); + + private static final MetaManager metaManager = MetaManager.instance(); + + public static void main(String[] args) { + LOG.info( + "Example4 start! This example serves a simple test of a parent-child type of " + + "EdgeLabel"); + metaManager.connect("hg", MetaManager.MetaDriverType.PD, + null, null, null, + ImmutableList.of("127.0.0.1:8686")); + + HugeGraph graph = ExampleUtil.loadGraph(); + Example4.showFeatures(graph); + Example4.loadSchema(graph); + Example4.loadData(graph); + Example4.testQueryEdge(graph); + + try { + Example4.thread(graph); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + + try { + graph.close(); + } catch (Exception e) { + throw new RuntimeException(e); + } + + HugeFactory.shutdown(30L); + } + + private static void thread(HugeGraph graph) throws InterruptedException { + Thread t = new Thread(() -> { + // Default tx + graph.addVertex(T.label, "book", "name", "java-11"); + graph.addVertex(T.label, "book", "name", "java-12"); + graph.tx().commit(); + + // New tx + GraphTransaction tx = Whitebox.invoke(graph.getClass(), + "openGraphTransaction", + graph); + + tx.addVertex(T.label, "book", "name", "java-21"); + tx.addVertex(T.label, "book", "name", "java-22"); + tx.commit(); + tx.close(); + + // This will close the schema tx + Whitebox.invoke(graph.getClass(), "closeTx", graph); + }); + t.start(); + t.join(); + } + + public static void showFeatures(final HugeGraph graph) { + LOG.info("SupportsPersistence: {}", + graph.features().graph().supportsPersistence()); + } + + public static void loadSchema(final HugeGraph graph) { + + SchemaManager schema = graph.schema(); + // Schema changes will be commit directly into the back-end + LOG.info("=============== propertyKey ================"); + schema.propertyKey("id").asInt().create(); + schema.propertyKey("name").asText().create(); + schema.propertyKey("gender").asText().create(); + schema.propertyKey("instructions").asText().create(); + schema.propertyKey("category").asText().create(); + schema.propertyKey("year").asInt().create(); + schema.propertyKey("time").asText().create(); + schema.propertyKey("timestamp").asDate().create(); + schema.propertyKey("ISBN").asText().create(); + schema.propertyKey("calories").asInt().create(); + schema.propertyKey("amount").asText().create(); + schema.propertyKey("stars").asInt().create(); + schema.propertyKey("age").asInt().valueSingle().create(); + schema.propertyKey("comment").asText().valueSet().create(); + schema.propertyKey("contribution").asText().valueSet().create(); + schema.propertyKey("nickname").asText().valueList().create(); + schema.propertyKey("lived").asText().create(); + schema.propertyKey("country").asText().valueSet().create(); + schema.propertyKey("city").asText().create(); + schema.propertyKey("sensor_id").asUUID().create(); + schema.propertyKey("versions").asInt().valueList().create(); + + LOG.info("=============== vertexLabel ================"); + + schema.vertexLabel("person") + .properties("name", "age", "city") + .primaryKeys("name") + .create(); + schema.vertexLabel("author") + .properties("id", "name", "age", "lived") + .primaryKeys("id").create(); + schema.vertexLabel("language").properties("name", "versions") + .primaryKeys("name").create(); + schema.vertexLabel("recipe").properties("name", "instructions") + .primaryKeys("name").create(); + schema.vertexLabel("book").properties("name") + .primaryKeys("name").create(); + schema.vertexLabel("reviewer").properties("name", "timestamp") + .primaryKeys("name").create(); + + schema.vertexLabel("company") + .properties("name") + .primaryKeys("name").create(); + + // vertex label must have the properties that specified in primary key + schema.vertexLabel("FridgeSensor").properties("city") + .primaryKeys("city").create(); + + LOG.info("=============== vertexLabel & index ================"); + schema.indexLabel("personByCity") + .onV("person").secondary().by("city").create(); + schema.indexLabel("personByAge") + .onV("person").range().by("age").create(); + + schema.indexLabel("authorByLived") + .onV("author").search().by("lived").create(); + + LOG.info("=============== edgeLabel ================"); + + schema.edgeLabel("authored").singleTime() + .link("author", "book") + .properties("contribution", "comment") + .nullableKeys("comment") + .create(); + + // Create a parent EdgeLabel + EdgeLabel elFather = schema.edgeLabel("transfer").asBase().create(); + + LOG.info(String.format("Parent type created: %s, its id is: %s", + elFather.name(), elFather.id().asString())); + + // Create two child EdgeLabels + EdgeLabel son1 = + schema.edgeLabel("transfer-1").withBase("transfer").multiTimes() + .link("author", "person") + .properties("time").sortKeys("time").create(); + + LOG.info(String.format("Child type created: %s, its id is: %s, its parent id is %s", + son1.name(), son1.id().asString(), + son1.fatherId().asString())); + + EdgeLabel son2 = + schema.edgeLabel("transfer-2").withBase("transfer").multiTimes() + .link("author", "company") + .properties("time").sortKeys("time").create(); + + LOG.info(String.format("Child type created: %s, its id is: %s, its parent id is %s", + son2.name(), son2.id().asString(), + son2.fatherId().asString())); + + schema.edgeLabel("write").multiTimes().properties("time") + .link("author", "book") + .sortKeys("time") + .create(); + + schema.edgeLabel("look").multiTimes().properties("timestamp") + .link("person", "book") + .sortKeys("timestamp") + .create(); + + schema.edgeLabel("created").singleTime() + .link("author", "language") + .create(); + + schema.edgeLabel("rated") + .link("reviewer", "recipe") + .create(); + } + + public static void loadData(final HugeGraph graph) { + // will auto open tx (would not auto commit) + graph.addVertex(T.label, "book", "name", "java-3"); + + graph.addVertex(T.label, "person", "name", "Baby", + "city", "Hongkong", "age", 3); + graph.addVertex(T.label, "person", "name", "James", + "city", "Beijing", "age", 19); + graph.addVertex(T.label, "person", "name", "Tom Cat", + "city", "Beijing", "age", 20); + graph.addVertex(T.label, "person", "name", "Lisa", + "city", "Beijing", "age", 20); + graph.addVertex(T.label, "person", "name", "Hebe", + "city", "Taipei", "age", 21); + + graph.tx().commit(); + + // must commit manually with new backend tx (independent of tinkerpop) + GraphTransaction tx = Whitebox.invoke(graph.getClass(), + "openGraphTransaction", + graph); + + LOG.info("=============== addVertex ================"); + Vertex james = tx.addVertex(T.label, "author", "id", 1, + "name", "James Gosling", "age", 62, + "lived", "San Francisco Bay Area"); + + Vertex java = tx.addVertex(T.label, "language", "name", "java", + "versions", Arrays.asList(6, 7, 8)); + Vertex book1 = tx.addVertex(T.label, "book", "name", "java-1"); + Vertex book2 = tx.addVertex(T.label, "book", "name", "java-2"); + Vertex book3 = tx.addVertex(T.label, "book", "name", "java-3"); + + Vertex baidu = tx.addVertex(T.label, "company", "name", "baidu"); + Vertex yanHong = tx.addVertex(T.label, "person", "name", "yanHong", + "city", "Beijing", "age", 45); + + Edge edgeTransfer1 = + james.addEdge("transfer-1", yanHong, "time", "2022-1-1"); + james.addEdge("transfer-1", yanHong, "time", "2022-1-2"); + james.addEdge("transfer-1", yanHong, "time", "2022-1-3"); + + Edge edgeTransfer2 = + james.addEdge("transfer-2", baidu, "time", "2022-2-2"); + james.addEdge("transfer-2", baidu, "time", "2022-2-1"); + james.addEdge("transfer-2", baidu, "time", "2022-2-2"); + james.addEdge("transfer-2", baidu, "time", "2022-2-3"); + james.addEdge("created", java); + james.addEdge("authored", book1, + "contribution", "1990-1-1", + "comment", "it's a good book", + "comment", "it's a good book", + "comment", "it's a good book too"); + james.addEdge("authored", book2, "contribution", "2017-4-28"); + + Edge edge1 = james.addEdge("write", book2, "time", "2017-4-28"); + Edge edge2 = james.addEdge("write", book3, "time", "2016-1-1"); + Edge edge3 = james.addEdge("write", book3, "time", "2017-4-28"); + + // commit data changes + try { + tx.commit(); + } catch (BackendException e) { + e.printStackTrace(); + try { + tx.rollback(); + } catch (BackendException e2) { + e2.printStackTrace(); + } + } finally { + tx.close(); + } + + // use the manually open transaction (tinkerpop tx) + graph.tx().open(); + graph.addVertex(T.label, "book", "name", "java-3"); + graph.addVertex(T.label, "book", "name", "java-4"); + Vertex vertex1 = graph.addVertex(T.label, "book", "name", "java-5"); + System.out.println(vertex1.id().toString()); + + graph.tx().commit(); + } + + public static void testQueryEdge(final HugeGraph graph) { + GraphTraversal edges = graph.traversal().E(); + List list = edges.toList(); + + GraphTraversal vertexs = graph.traversal().V(); + List list1 = vertexs.toList(); + + GraphTraversal vertexEdgeGraphTraversal = + graph.traversal().V("2:11").outE("write"); + LOG.info("The number of write edges is: " + vertexEdgeGraphTraversal.toList().size()); + + // Three types of queries + // First, query edges for person-to-person transfers + GraphTraversal transfer1 = + graph.traversal().V("2:11").outE("transfer-1") + .has("time", "2022-1-2"); + // transfer_1.toList().size(); + LOG.info("The number of person-to-person transfer edges (transfer1) for james is: " + transfer1.toList().size()); + + // Second, query edges for person-to-company transfers + GraphTraversal transfer2 = + graph.traversal().V("2:11").outE("transfer-2"); + // transfer_2.toList().size(); + LOG.info("The number of person-to-company transfer edges (transfer2) for james is: " + transfer2.toList().size()); + + // Third, query transfer edges + GraphTraversal transfer = + graph.traversal().V("2:11").outE("transfer"); + // transfer.toList().size(); + LOG.info("The number of transfer edges (transfer) is: " + transfer.toList().size()); + + GraphTraversal writeAndTransfer1 = + graph.traversal().V("2:11").outE("write", "transfer-1"); + LOG.info( + "Mixed query: graph.traversal().V(\"2:11\").outE(\"write\", \"transfer-1\") => The total number of write and transfer1 edges is: " + + writeAndTransfer1.toList().size()); + + GraphTraversal writeAndTransfer1WithLimit = + graph.traversal().V("2:11") + .outE("write", "transfer-1") + .limit(2); + LOG.info( + "Limited mixed query: graph.traversal().V(\"2:11\").outE(\"write\", \"transfer-1\").limit(2); => " + + "The total number of write and transfer1 edges is: " + + writeAndTransfer1WithLimit.toList().size()); + + GraphTraversal res = graph.traversal().V("2:11") + .outE("write", "transfer-1", + "transfer-2", "transfer"); + LOG.info( + "Mixed query: graph.traversal().V(\"2:11\").outE(\"write\", \"transfer-1\", " + + "\"transfer-2\", \"transfer\") The total number of edges is: " + + res.toList().size()); + + System.out.println("graph.traversal().E().hasLabel(\"write\").toList" + + "().size():" + + graph.traversal().E().hasLabel("write").toList() + .size()); + + } +} + diff --git a/hugegraph-server/hugegraph-hstore/src/main/java/org/apache/hugegraph/backend/store/hstore/HstoreStore.java b/hugegraph-server/hugegraph-hstore/src/main/java/org/apache/hugegraph/backend/store/hstore/HstoreStore.java index 954fe35076..6439096674 100644 --- a/hugegraph-server/hugegraph-hstore/src/main/java/org/apache/hugegraph/backend/store/hstore/HstoreStore.java +++ b/hugegraph-server/hugegraph-hstore/src/main/java/org/apache/hugegraph/backend/store/hstore/HstoreStore.java @@ -19,6 +19,7 @@ import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedHashSet; @@ -29,12 +30,18 @@ import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; +import java.util.function.Function; import java.util.function.Supplier; import java.util.stream.Collectors; +import com.google.common.collect.Lists; + import org.apache.commons.collections.CollectionUtils; +import org.apache.hugegraph.HugeGraph; import org.apache.hugegraph.backend.id.Id; import org.apache.hugegraph.backend.id.IdGenerator; +import org.apache.hugegraph.backend.query.ConditionQuery; +import org.apache.hugegraph.backend.query.ConditionQueryFlatten; import org.apache.hugegraph.backend.query.IdPrefixQuery; import org.apache.hugegraph.backend.query.IdQuery; import org.apache.hugegraph.backend.query.Query; @@ -52,10 +59,12 @@ import org.apache.hugegraph.config.CoreOptions; import org.apache.hugegraph.config.HugeConfig; import org.apache.hugegraph.iterator.CIter; +import org.apache.hugegraph.schema.EdgeLabel; import org.apache.hugegraph.type.HugeTableType; import org.apache.hugegraph.type.HugeType; import org.apache.hugegraph.type.define.Action; import org.apache.hugegraph.type.define.GraphMode; +import org.apache.hugegraph.type.define.HugeKeys; import org.apache.hugegraph.util.E; import org.apache.hugegraph.util.Log; import org.slf4j.Logger; @@ -339,161 +348,160 @@ public Iterator query(Query query) { } } - // TODO: uncomment later - sub edge labels - //@Override - //public Iterator> query(Iterator queries, - // Function queryWriter, - // HugeGraph hugeGraph) { - // if (queries == null || !queries.hasNext()) { - // return Collections.emptyIterator(); - // } - // - // class QueryWrapper implements Iterator { - // Query first; - // final Iterator queries; - // Iterator subEls; - // Query preQuery; - // Iterator queryListIterator; - // - // QueryWrapper(Iterator queries, Query first) { - // this.queries = queries; - // this.first = first; - // } - // - // @Override - // public boolean hasNext() { - // return first != null || (this.subEls != null && this.subEls.hasNext()) - // || (queryListIterator != null && queryListIterator.hasNext()) || - // queries.hasNext(); - // } - // - // @Override - // public IdPrefixQuery next() { - // if (queryListIterator != null && queryListIterator.hasNext()) { - // return queryListIterator.next(); - // } - // - // Query q; - // if (first != null) { - // q = first; - // preQuery = q.copy(); - // first = null; - // } else { - // if (this.subEls == null || !this.subEls.hasNext()) { - // q = queries.next(); - // preQuery = q.copy(); - // } else { - // q = preQuery.copy(); - // } - // } - // - // assert q instanceof ConditionQuery; - // ConditionQuery cq = (ConditionQuery) q; - // ConditionQuery originQuery = (ConditionQuery) q.copy(); - // - // List queryList = Lists.newArrayList(); - // if (hugeGraph != null) { - // for (ConditionQuery conditionQuery : - // ConditionQueryFlatten.flatten(cq)) { - // Id label = conditionQuery.condition(HugeKeys.LABEL); - // /* Parent type + sortKeys: g.V("V.id").outE("parentLabel").has - // ("sortKey","value") converted to all subtypes + sortKeys */ - // if ((this.subEls == null || - // !this.subEls.hasNext()) && label != null && - // hugeGraph.edgeLabel(label).isFather() && - // conditionQuery.condition(HugeKeys.SUB_LABEL) == - // null && - // conditionQuery.condition(HugeKeys.OWNER_VERTEX) != - // null && - // conditionQuery.condition(HugeKeys.DIRECTION) != - // null && - // matchEdgeSortKeys(conditionQuery, false, - // hugeGraph)) { - // this.subEls = - // getSubLabelsOfParentEl( - // hugeGraph.edgeLabels(), - // label); - // } - // - // if (this.subEls != null && - // this.subEls.hasNext()) { - // conditionQuery.eq(HugeKeys.SUB_LABEL, - // subEls.next()); - // } - // - // HugeType hugeType = conditionQuery.resultType(); - // if (hugeType != null && hugeType.isEdge() && - // !conditionQuery.conditions().isEmpty()) { - // IdPrefixQuery idPrefixQuery = - // (IdPrefixQuery) queryWriter.apply( - // conditionQuery); - // idPrefixQuery.setOriginQuery(originQuery); - // queryList.add(idPrefixQuery); - // } - // } - // - // queryListIterator = queryList.iterator(); - // if (queryListIterator.hasNext()) { - // return queryListIterator.next(); - // } - // } - // - // Id ownerId = cq.condition(HugeKeys.OWNER_VERTEX); - // assert ownerId != null; - // BytesBuffer buffer = - // BytesBuffer.allocate(BytesBuffer.BUF_EDGE_ID); - // buffer.writeId(ownerId); - // return new IdPrefixQuery(cq, new BinaryBackendEntry.BinaryId( - // buffer.bytes(), ownerId)); - // } - // - // private boolean matchEdgeSortKeys(ConditionQuery query, - // boolean matchAll, - // HugeGraph graph) { - // assert query.resultType().isEdge(); - // Id label = query.condition(HugeKeys.LABEL); - // if (label == null) { - // return false; - // } - // List sortKeys = graph.edgeLabel(label).sortKeys(); - // if (sortKeys.isEmpty()) { - // return false; - // } - // Set queryKeys = query.userpropKeys(); - // for (int i = sortKeys.size(); i > 0; i--) { - // List subFields = sortKeys.subList(0, i); - // if (queryKeys.containsAll(subFields)) { - // if (queryKeys.size() == subFields.size() || !matchAll) { - // /* - // * Return true if: - // * matchAll=true and all queryKeys are in sortKeys - // * or - // * partial queryKeys are in sortKeys - // */ - // return true; - // } - // } - // } - // return false; - // } - // } - // Query first = queries.next(); - // List typeList = getHugeTypes(first); - // QueryWrapper idPrefixQueries = new QueryWrapper(queries, first); - // - // return query(typeList, idPrefixQueries); - //} - - //private Iterator getSubLabelsOfParentEl(Collection allEls, - // Id label) { - // List list = new ArrayList<>(); - // for (EdgeLabel el : allEls) { - // if (el.edgeLabelType().sub() && el.fatherId().equals(label)) { - // list.add(el.id()); - // } - // } - // return list.iterator(); - //} + @Override + public Iterator> query(Iterator queries, + Function queryWriter, + HugeGraph hugeGraph) { + if (queries == null || !queries.hasNext()) { + return Collections.emptyIterator(); + } + + class QueryWrapper implements Iterator { + Query first; + final Iterator queries; + Iterator subEls; + Query preQuery; + Iterator queryListIterator; + + QueryWrapper(Iterator queries, Query first) { + this.queries = queries; + this.first = first; + } + + @Override + public boolean hasNext() { + return first != null || (this.subEls != null && this.subEls.hasNext()) + || (queryListIterator != null && queryListIterator.hasNext()) || + queries.hasNext(); + } + + @Override + public IdPrefixQuery next() { + if (queryListIterator != null && queryListIterator.hasNext()) { + return queryListIterator.next(); + } + + Query q; + if (first != null) { + q = first; + preQuery = q.copy(); + first = null; + } else { + if (this.subEls == null || !this.subEls.hasNext()) { + q = queries.next(); + preQuery = q.copy(); + } else { + q = preQuery.copy(); + } + } + + assert q instanceof ConditionQuery; + ConditionQuery cq = (ConditionQuery) q; + ConditionQuery originQuery = (ConditionQuery) q.copy(); + + List queryList = Lists.newArrayList(); + if (hugeGraph != null) { + for (ConditionQuery conditionQuery : + ConditionQueryFlatten.flatten(cq)) { + Id label = conditionQuery.condition(HugeKeys.LABEL); + /* Parent type + sortKeys: g.V("V.id").outE("parentLabel") + .has("sortKey","value") converted to all subtypes + sortKeys */ + if ((this.subEls == null || + !this.subEls.hasNext()) && label != null && + hugeGraph.edgeLabel(label).isFather() && + conditionQuery.condition(HugeKeys.SUB_LABEL) == + null && + conditionQuery.condition(HugeKeys.OWNER_VERTEX) != + null && + conditionQuery.condition(HugeKeys.DIRECTION) != + null && + matchEdgeSortKeys(conditionQuery, false, + hugeGraph)) { + this.subEls = + getSubLabelsOfParentEl( + hugeGraph.edgeLabels(), + label); + } + + if (this.subEls != null && + this.subEls.hasNext()) { + conditionQuery.eq(HugeKeys.SUB_LABEL, + subEls.next()); + } + + HugeType hugeType = conditionQuery.resultType(); + if (hugeType != null && hugeType.isEdge() && + !conditionQuery.conditions().isEmpty()) { + IdPrefixQuery idPrefixQuery = + (IdPrefixQuery) queryWriter.apply( + conditionQuery); + idPrefixQuery.setOriginQuery(originQuery); + queryList.add(idPrefixQuery); + } + } + + queryListIterator = queryList.iterator(); + if (queryListIterator.hasNext()) { + return queryListIterator.next(); + } + } + + Id ownerId = cq.condition(HugeKeys.OWNER_VERTEX); + assert ownerId != null; + BytesBuffer buffer = + BytesBuffer.allocate(BytesBuffer.BUF_EDGE_ID); + buffer.writeId(ownerId); + return new IdPrefixQuery(cq, new BinaryBackendEntry.BinaryId( + buffer.bytes(), ownerId)); + } + + private boolean matchEdgeSortKeys(ConditionQuery query, + boolean matchAll, + HugeGraph graph) { + assert query.resultType().isEdge(); + Id label = query.condition(HugeKeys.LABEL); + if (label == null) { + return false; + } + List sortKeys = graph.edgeLabel(label).sortKeys(); + if (sortKeys.isEmpty()) { + return false; + } + Set queryKeys = query.userpropKeys(); + for (int i = sortKeys.size(); i > 0; i--) { + List subFields = sortKeys.subList(0, i); + if (queryKeys.containsAll(subFields)) { + if (queryKeys.size() == subFields.size() || !matchAll) { + /* + * Return true if: + * matchAll=true and all queryKeys are in sortKeys + * or + * partial queryKeys are in sortKeys + */ + return true; + } + } + } + return false; + } + } + Query first = queries.next(); + List typeList = getHugeTypes(first); + QueryWrapper idPrefixQueries = new QueryWrapper(queries, first); + + return query(typeList, idPrefixQueries); + } + + private Iterator getSubLabelsOfParentEl(Collection allEls, + Id label) { + List list = new ArrayList<>(); + for (EdgeLabel el : allEls) { + if (el.edgeLabelType().sub() && el.fatherId().equals(label)) { + list.add(el.id()); + } + } + return list.iterator(); + } public List> query(List typeList, List queries) { diff --git a/hugegraph-server/hugegraph-mysql/src/main/java/org/apache/hugegraph/backend/store/mysql/MysqlSerializer.java b/hugegraph-server/hugegraph-mysql/src/main/java/org/apache/hugegraph/backend/store/mysql/MysqlSerializer.java index 1557902bd2..595ab609bd 100644 --- a/hugegraph-server/hugegraph-mysql/src/main/java/org/apache/hugegraph/backend/store/mysql/MysqlSerializer.java +++ b/hugegraph-server/hugegraph-mysql/src/main/java/org/apache/hugegraph/backend/store/mysql/MysqlSerializer.java @@ -27,7 +27,7 @@ import org.apache.hugegraph.backend.id.Id; import org.apache.hugegraph.backend.id.IdGenerator; import org.apache.hugegraph.backend.serializer.TableBackendEntry; -import org.apache.hugegraph.backend.serializer.TableSerializer; +import org.apache.hugegraph.backend.serializer.TableSerializerV2; import org.apache.hugegraph.backend.store.BackendEntry; import org.apache.hugegraph.config.HugeConfig; import org.apache.hugegraph.schema.SchemaElement; @@ -39,7 +39,7 @@ import org.apache.hugegraph.util.InsertionOrderUtil; import org.apache.hugegraph.util.JsonUtil; -public class MysqlSerializer extends TableSerializer { +public class MysqlSerializer extends TableSerializerV2 { public MysqlSerializer(HugeConfig config) { super(config); diff --git a/hugegraph-server/hugegraph-mysql/src/main/java/org/apache/hugegraph/backend/store/mysql/MysqlTables.java b/hugegraph-server/hugegraph-mysql/src/main/java/org/apache/hugegraph/backend/store/mysql/MysqlTables.java index cf0a1e08e3..981369f56d 100644 --- a/hugegraph-server/hugegraph-mysql/src/main/java/org/apache/hugegraph/backend/store/mysql/MysqlTables.java +++ b/hugegraph-server/hugegraph-mysql/src/main/java/org/apache/hugegraph/backend/store/mysql/MysqlTables.java @@ -333,12 +333,13 @@ public Edge(String store, Directions direction, this.define.column(HugeKeys.OWNER_VERTEX, SMALL_TEXT); this.define.column(HugeKeys.DIRECTION, TINYINT); this.define.column(HugeKeys.LABEL, DATATYPE_SL); + this.define.column(HugeKeys.SUB_LABEL, DATATYPE_SL); this.define.column(HugeKeys.SORT_VALUES, SMALL_TEXT); this.define.column(HugeKeys.OTHER_VERTEX, SMALL_TEXT); this.define.column(HugeKeys.PROPERTIES, LARGE_JSON); this.define.column(HugeKeys.EXPIRED_TIME, BIGINT); this.define.keys(HugeKeys.OWNER_VERTEX, HugeKeys.DIRECTION, - HugeKeys.LABEL, HugeKeys.SORT_VALUES, + HugeKeys.LABEL, HugeKeys.SUB_LABEL, HugeKeys.SORT_VALUES, HugeKeys.OTHER_VERTEX); } @@ -365,6 +366,7 @@ public List idColumnValue(Id id) { list.add(IdUtil.writeStoredString(edgeId.ownerVertexId())); list.add(edgeId.directionCode()); list.add(edgeId.edgeLabelId().asLong()); + list.add(edgeId.subLabelId().asLong()); list.add(edgeId.sortValues()); list.add(IdUtil.writeStoredString(edgeId.otherVertexId())); return list; diff --git a/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/core/BaseCoreTest.java b/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/core/BaseCoreTest.java index a042533843..fabd622b8a 100644 --- a/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/core/BaseCoreTest.java +++ b/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/core/BaseCoreTest.java @@ -17,6 +17,7 @@ package org.apache.hugegraph.core; +import java.util.List; import java.util.Random; import org.apache.hugegraph.HugeGraph; @@ -25,6 +26,7 @@ import org.apache.hugegraph.backend.store.BackendFeatures; import org.apache.hugegraph.dist.RegisterUtil; import org.apache.hugegraph.masterelection.GlobalMasterInfo; +import org.apache.hugegraph.schema.EdgeLabel; import org.apache.hugegraph.schema.SchemaManager; import org.apache.hugegraph.testutil.Utils; import org.apache.hugegraph.testutil.Whitebox; @@ -137,7 +139,21 @@ private void clearSchema() { schema.indexLabel(elem.name()).remove(); }); - schema.getEdgeLabels().forEach(elem -> { + final List edgeLabels = schema.getEdgeLabels(); + // remove father edge label after sub edge label + edgeLabels.sort((lhs, rhs) -> { + if (lhs.isFather() && rhs.isFather()) { + return 0; + } + if (lhs.isFather()) { + return 1; + } + if (rhs.isFather()) { + return -1; + } + return 0; + }); + edgeLabels.forEach(elem -> { schema.edgeLabel(elem.name()).remove(); }); diff --git a/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/core/CoreTestSuite.java b/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/core/CoreTestSuite.java index 17e01a4fab..4c832b544c 100644 --- a/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/core/CoreTestSuite.java +++ b/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/core/CoreTestSuite.java @@ -30,6 +30,7 @@ IndexLabelCoreTest.class, VertexCoreTest.class, EdgeCoreTest.class, + ParentAndSubEdgeCoreTest.class, VertexPropertyCoreTest.class, EdgePropertyCoreTest.class, RestoreCoreTest.class, diff --git a/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/core/ParentAndSubEdgeCoreTest.java b/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/core/ParentAndSubEdgeCoreTest.java new file mode 100644 index 0000000000..4d7cc3eea3 --- /dev/null +++ b/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/core/ParentAndSubEdgeCoreTest.java @@ -0,0 +1,375 @@ +/* + * 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.core; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.hugegraph.HugeGraph; +import org.apache.hugegraph.schema.EdgeLabel; +import org.apache.hugegraph.schema.SchemaManager; +import org.apache.hugegraph.testutil.Assert; +import org.apache.tinkerpop.gremlin.structure.Edge; +import org.apache.tinkerpop.gremlin.structure.T; +import org.apache.tinkerpop.gremlin.structure.Vertex; +import org.junit.Assume; +import org.junit.Before; +import org.junit.Test; + +public class ParentAndSubEdgeCoreTest extends BaseCoreTest { + + @Before + public void initSchema() { + Assume.assumeTrue("Not support father and sub edge label", + this.storeFeatures().supportsFatherAndSubEdgeLabel()); + + SchemaManager schema = graph().schema(); + + LOG.debug("=============== propertyKey ================"); + + schema.propertyKey("id").asInt().create(); + schema.propertyKey("name").asText().create(); + schema.propertyKey("time").asText().create(); + schema.propertyKey("timestamp").asLong().create(); + schema.propertyKey("age").asInt().valueSingle().create(); + schema.propertyKey("comment").asText().valueSet().create(); + schema.propertyKey("contribution").asText().create(); + schema.propertyKey("score").asInt().create(); + schema.propertyKey("lived").asText().create(); + schema.propertyKey("city").asText().create(); + schema.propertyKey("amount").asFloat().create(); + + LOG.debug("=============== vertexLabel ================"); + + schema.vertexLabel("person") + .properties("name", "age", "city") + .primaryKeys("name") + .enableLabelIndex(false) + .create(); + + schema.vertexLabel("company") + .properties("name", "city") + .primaryKeys("name") + .enableLabelIndex(false) + .create(); + + schema.vertexLabel("author") + .properties("id", "name", "age", "lived") + .primaryKeys("id") + .enableLabelIndex(false) + .create(); + + LOG.debug("=============== edgeLabel ================"); + + EdgeLabel elFather = + schema.edgeLabel("transfer").asBase() + .properties("time", "amount") + .multiTimes() + .sortKeys("time") + .create(); + + EdgeLabel transfer1 = schema.edgeLabel("transfer-1") + .withBase("transfer").multiTimes() + .link("person", "person") + .properties("time", "amount") + .sortKeys("time") + .create(); + + EdgeLabel transfer2 = schema.edgeLabel("transfer-2") + .withBase("transfer").multiTimes() + .link("person", "company") + .properties("time", "amount") + .sortKeys("time") + .create(); + + EdgeLabel transfer3 = schema.edgeLabel("transfer-3") + .withBase("transfer").multiTimes() + .link("person", "author") + .properties("time", "amount") + .sortKeys("time") + .create(); + + graph().schema().indexLabel("transferByAmount").onE("transfer") + .by("amount").secondary().ifNotExist().create(); + graph().schema().indexLabel("transfer-1ByAmount").onE("transfer-1") + .by("amount").secondary().ifNotExist().create(); + graph().schema().indexLabel("transfer-2ByAmount").onE("transfer-2") + .by("amount").secondary().ifNotExist().create(); + graph().schema().indexLabel("transfer-3ByAmount").onE("transfer-3") + .by("amount").secondary().ifNotExist().create(); + + schema.edgeLabel("know").multiTimes() + .sourceLabel("person") + .targetLabel("person") + .enableLabelIndex(true) + .properties("time") + .sortKeys("time") + .create(); + + graph().schema().indexLabel("knowByTime").onE("know") + .by("time").secondary().ifNotExist().create(); + } + + private List init10Edges() { + HugeGraph graph = graph(); + + Vertex person1 = graph.addVertex(T.label, "person", + "age", 19, + "city", "Beijing", + "name", "person1"); + Vertex person2 = graph.addVertex(T.label, "person", + "age", 20, + "city", "Shanghai", + "name", "person2"); + Vertex person3 = graph.addVertex(T.label, "person", + "age", 19, + "city", "Nanjing", + "name", "person3"); + + Vertex baidu = graph.addVertex(T.label, "company", + "name", "Baidu", + "city", "Beijing"); + Vertex huawei = graph.addVertex(T.label, "company", + "name", "Huawei", + "city", "Shanghai"); + Vertex tencent = graph.addVertex(T.label, "company", + "name", "Tencent", + "city", "Shenzhen"); + + Vertex james = graph.addVertex(T.label, "author", "id", 1, + "name", "James Gosling", "age", 62, + "lived", "Canadian"); + Vertex guido = graph.addVertex(T.label, "author", "id", 2, + "name", "Guido van Rossum", "age", 61, + "lived", "California"); + + person1.addEdge("transfer-1", person2, "time", "2021-11-11", + "amount", 9.00); + person1.addEdge("transfer-1", person3, "time", "2021-11-22", + "amount", 10.00); + + person2.addEdge("transfer-1", person3, "time", "2022-1-1", + "amount", 10.00); + person2.addEdge("transfer-1", person3, "time", "2022-1-2", + "amount", 10.00); + + person2.addEdge("transfer-2", baidu, "time", "2022-1-1", + "amount", 10.00); + person2.addEdge("transfer-2", baidu, "time", "2022-1-2", + "amount", 10.00); + person2.addEdge("transfer-2", baidu, "time", "2022-1-3", + "amount", 10.00); + + person3.addEdge("transfer-2", baidu, "time", "2022-1-4", + "amount", 10.00); + person3.addEdge("transfer-2", tencent, "time", "2022-1-1", + "amount", 10.00); + person3.addEdge("transfer-2", tencent, "time", "2022-1-2", + "amount", 9.00); + + person1.addEdge("know", person2, "time", "2022-1-1"); + + graph.tx().commit(); + List list = new ArrayList<>(); + list.add(person1); + list.add(person2); + list.add(person3); + return list; + } + + @Test + public void testQueryParentAndSubEdgesWithHasLabel() { + Assume.assumeTrue("Not support father and sub edge label", + this.storeFeatures().supportsFatherAndSubEdgeLabel()); + + // Simple hasLabel Type Query + init10Edges(); + + // normal edge + List edges = graph().traversal().E().hasLabel("know").toList(); + Assert.assertEquals(1, edges.size()); + + // father edge + edges = graph().traversal().E().hasLabel("transfer").toList(); + Assert.assertEquals(10, edges.size()); + + // sub edge + edges = graph().traversal().E().hasLabel("transfer-1").toList(); + Assert.assertEquals(4, edges.size()); + + edges = graph().traversal().E().hasLabel("transfer-2").toList(); + Assert.assertEquals(6, edges.size()); + } + + @Test + public void testQueryParentAndSubEdgesWithHasLabelAndConditions() { + Assume.assumeTrue("Not support father and sub edge label", + this.storeFeatures().supportsFatherAndSubEdgeLabel()); + + // hasLabel + Conditional Filtering Type Query + init10Edges(); + + // normal edge + List edges = graph().traversal().E().hasLabel("know") + .has("time", "2022-1-1") + .toList(); + + Assert.assertEquals(1, edges.size()); + + // father edge + edges = graph().traversal().E().hasLabel("transfer").has("amount", + 10.00).toList(); + + Assert.assertEquals(8, edges.size()); + + // sub edge + edges = graph().traversal().E().hasLabel("transfer-1") + .has("amount", 10.00) + .toList(); + Assert.assertEquals(3, edges.size()); + + edges = graph().traversal().E().hasLabel("transfer-2") + .has("amount", 10.00) + .toList(); + Assert.assertEquals(5, edges.size()); + } + + @Test + public void testQueryParentAndSubEdgesWithVertexOut() { + Assume.assumeTrue("Not support father and sub edge label", + this.storeFeatures().supportsFatherAndSubEdgeLabel()); + + // g.V("id").outE("label") + List list = init10Edges(); + Vertex person1 = list.get(0); + Vertex person2 = list.get(1); + Vertex person3 = list.get(2); + + List edges; + edges = graph().traversal().V(person1.id()) + .outE("transfer").toList(); + Assert.assertEquals(2, edges.size()); + + edges = graph().traversal().V(person1.id()) + .outE("transfer-1").toList(); + Assert.assertEquals(2, edges.size()); + + edges = graph().traversal().V(person1.id()) + .outE("transfer-2").toList(); + Assert.assertEquals(0, edges.size()); + + edges = graph().traversal().V(person1.id()) + .outE("transfer", "know").toList(); + Assert.assertEquals(2 + 1, edges.size()); + + edges = graph().traversal().V(person2.id()) + .outE("transfer-1").toList(); + Assert.assertEquals(2, edges.size()); + + edges = graph().traversal().V(person2.id()) + .outE("transfer").toList(); + Assert.assertEquals(5, edges.size()); + + edges = graph().traversal().V(person2.id()) + .outE("transfer-1").toList(); + Assert.assertEquals(2, edges.size()); + + edges = graph().traversal().V(person2.id()) + .outE("transfer-2").toList(); + Assert.assertEquals(3, edges.size()); + } + + @Test + public void testQueryParentAndSubEdgesWithVertexOutAndConditions() { + Assume.assumeTrue("Not support father and sub edge label", + this.storeFeatures().supportsFatherAndSubEdgeLabel()); + + // g.V("id").outE("label").has("amount",10.00) + List list = init10Edges(); + Vertex person1 = list.get(0); + Vertex person2 = list.get(1); + Vertex person3 = list.get(2); + + List edges; + edges = graph().traversal().V(person1.id()) + .outE("transfer") + .has("amount", 10.00).toList(); + Assert.assertEquals(1, edges.size()); + + edges = graph().traversal().V(person1.id()) + .outE("transfer-1") + .has("amount", 10.00).toList(); + Assert.assertEquals(1, edges.size()); + } + + @Test + public void testQueryParentAndSubEdgesWithVertexOutAndSortKeys() { + Assume.assumeTrue("Not support father and sub edge label", + this.storeFeatures().supportsFatherAndSubEdgeLabel()); + + // g.V("id").outE("label").has("sortKeys","value") + List list = init10Edges(); + Vertex person1 = list.get(0); + Vertex person2 = list.get(1); + Vertex person3 = list.get(2); + + int transferEdges = graph().traversal().V(person2.id()) + .outE("transfer") + .has("time", "2022-1-1").toList().size(); + Assert.assertEquals(2, transferEdges); + + int transfer1Edges = graph().traversal().V(person2.id()) + .outE("transfer-1") + .has("time", "2022-1-1").toList().size(); + int transfer2Edges = graph().traversal().V(person2.id()) + .outE("transfer-2") + .has("time", "2022-1-1").toList().size(); + Assert.assertEquals(transferEdges, transfer1Edges + transfer2Edges); + } + + @Test + public void testQueryParentAndSubEdgesWithVertexOutAndSortKeysAndConditions() { + Assume.assumeTrue("Not support father and sub edge label", + this.storeFeatures().supportsFatherAndSubEdgeLabel()); + + // g.V("id").outE("label").has("sortKeys","value").has(K,V) + List list = init10Edges(); + Vertex person1 = list.get(0); + Vertex person2 = list.get(1); + Vertex person3 = list.get(2); + + int transferEdges = graph().traversal().V(person2.id()) + .outE("transfer") + .has("time", "2022-1-1") + .has("amount", 10.00) + .toList().size(); + Assert.assertEquals(2, transferEdges); + + int transfer1Edges = graph().traversal().V(person2.id()) + .outE("transfer-1") + .has("time", "2022-1-1") + .has("amount", 10.00) + .toList().size(); + int transfer2Edges = graph().traversal().V(person2.id()) + .outE("transfer-2") + .has("time", "2022-1-1") + .has("amount", 10.00) + .toList().size(); + Assert.assertEquals(transferEdges, transfer1Edges + transfer2Edges); + } +} diff --git a/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/core/TaskCoreTest.java b/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/core/TaskCoreTest.java index da059ee85c..212ccc0588 100644 --- a/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/core/TaskCoreTest.java +++ b/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/core/TaskCoreTest.java @@ -348,13 +348,13 @@ public void testGremlinJobWithSerializedResults() throws TimeoutException { String expected = String.format("[{\"labels\":[[],[],[]],\"objects\":[" + "{\"id\":1,\"label\":\"char\",\"type\":\"vertex\"," + "\"properties\":{\"name\":\"A\"}}," + - "{\"id\":\"L1>%s>>L2\",\"label\":\"next\"," + + "{\"id\":\"L1>%s>%s>>L2\",\"label\":\"next\"," + "\"type\":\"edge\",\"outV\":1," + "\"outVLabel\":\"char\",\"inV\":2,\"" + "inVLabel\":\"char\",\"properties\":{\"name\":\"ab\"}}," + "{\"id\":2,\"label\":\"char\",\"type\":\"vertex\"," + "\"properties\":{\"name\":\"B\"}}" + - "]}]", edgeLabelId); + "]}]", edgeLabelId, edgeLabelId); Assert.assertEquals(expected, task.result()); script = "g.V(1).out().out().path()"; @@ -384,14 +384,14 @@ public void testGremlinJobWithSerializedResults() throws TimeoutException { expected = String.format("[[{\"key\":{\"id\":1,\"label\":\"char\",\"type\":\"vertex\"," + "\"properties\":{\"name\":\"A\"}}," + "\"value\":[" + - "{\"key\":{\"id\":\"L1>%s>>L2\",\"label\":\"next\"," + + "{\"key\":{\"id\":\"L1>%s>%s>>L2\",\"label\":\"next\"," + "\"type\":\"edge\",\"outV\":1," + "\"outVLabel\":\"char\",\"inV\":2,\"inVLabel\":\"char\"," + "\"properties\":{\"name\":\"ab\"}}," + "\"value\":[{\"key\":{\"id\":2,\"label\":\"char\"," + "\"type\":\"vertex\"," + "\"properties\":{\"name\":\"B\"}},\"value\":[]}]}]}]]", - edgeLabelId); + edgeLabelId, edgeLabelId); Assert.assertEquals(expected, task.result()); script = "g.V(1).out().out().tree()"; diff --git a/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/unit/FakeObjects.java b/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/unit/FakeObjects.java index 67f5c3781f..9dabd72a3a 100644 --- a/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/unit/FakeObjects.java +++ b/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/unit/FakeObjects.java @@ -156,7 +156,7 @@ public HugeEdge newEdge(long sourceVertexId, long targetVertexId) { target.addProperty(age, 20); target.addProperty(city, "Shanghai"); - Id id = EdgeId.parse("L123456>1>>L987654"); + Id id = EdgeId.parse("L123456>1>1>>L987654"); HugeEdge edge = new HugeEdge(this.graph(), id, el); Whitebox.setInternalState(edge, "sourceVertex", source); @@ -199,7 +199,7 @@ public HugeEdge newEdge(String sourceVertexId, String targetVertexId) { target.addProperty(age, 20); target.addProperty(city, "Shanghai"); - Id id = EdgeId.parse("L123456>1>>L987654"); + Id id = EdgeId.parse("L123456>1>1>>L987654"); HugeEdge edge = new HugeEdge(this.graph(), id, el); Whitebox.setInternalState(edge, "sourceVertex", source); diff --git a/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/unit/id/EdgeIdTest.java b/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/unit/id/EdgeIdTest.java index 47a6428080..0c917832f8 100644 --- a/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/unit/id/EdgeIdTest.java +++ b/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/unit/id/EdgeIdTest.java @@ -46,12 +46,15 @@ public void teardown() { @Test public void testEdgeIdEqual() { EdgeId edgeId1 = new EdgeId(IdGenerator.of("1:marko"), Directions.OUT, + IdGenerator.of(1), IdGenerator.of(1), "", IdGenerator.of("1:josh")); EdgeId edgeId2 = new EdgeId(IdGenerator.of("1:marko"), Directions.OUT, + IdGenerator.of(1), IdGenerator.of(1), "", IdGenerator.of("1:josh")); EdgeId edgeId3 = new EdgeId(IdGenerator.of("1:josh"), Directions.IN, + IdGenerator.of(1), IdGenerator.of(1), "", IdGenerator.of("1:marko")); Assert.assertTrue(edgeId1.equals(edgeId2)); @@ -63,12 +66,15 @@ public void testEdgeIdEqual() { @Test public void testEdgeIdEqualWithDirection() { EdgeId edgeId1 = new EdgeId(IdGenerator.of("1:marko"), Directions.OUT, + IdGenerator.of(1), IdGenerator.of(1), "", IdGenerator.of("1:josh"), true); EdgeId edgeId2 = new EdgeId(IdGenerator.of("1:marko"), Directions.OUT, + IdGenerator.of(1), IdGenerator.of(1), "", IdGenerator.of("1:josh"), true); EdgeId edgeId3 = new EdgeId(IdGenerator.of("1:josh"), Directions.IN, + IdGenerator.of(1), IdGenerator.of(1), "", IdGenerator.of("1:marko"), true); Assert.assertTrue(edgeId1.equals(edgeId2)); @@ -80,17 +86,20 @@ public void testEdgeIdEqualWithDirection() { @Test public void testCollectionContainsEdgeId() { EdgeId edgeId1 = new EdgeId(IdGenerator.of("1:marko"), Directions.OUT, + IdGenerator.of(1), IdGenerator.of(1), "", IdGenerator.of("1:josh")); Set edgeIds = ImmutableSet.of(edgeId1); Assert.assertTrue(edgeIds.contains(edgeId1)); EdgeId edgeId2 = new EdgeId(IdGenerator.of("1:marko"), Directions.OUT, + IdGenerator.of(1), IdGenerator.of(1), "", IdGenerator.of("1:josh")); Assert.assertTrue(edgeIds.contains(edgeId2)); EdgeId edgeId3 = new EdgeId(IdGenerator.of("1:josh"), Directions.IN, + IdGenerator.of(1), IdGenerator.of(1), "", IdGenerator.of("1:marko")); Assert.assertTrue(edgeIds.contains(edgeId3)); @@ -99,17 +108,20 @@ public void testCollectionContainsEdgeId() { @Test public void testCollectionContainsEdgeIdWithDirection() { EdgeId edgeId1 = new EdgeId(IdGenerator.of("1:marko"), Directions.OUT, + IdGenerator.of(1), IdGenerator.of(1), "", IdGenerator.of("1:josh"), true); Set edgeIds = ImmutableSet.of(edgeId1); Assert.assertTrue(edgeIds.contains(edgeId1)); EdgeId edgeId2 = new EdgeId(IdGenerator.of("1:marko"), Directions.OUT, + IdGenerator.of(1), IdGenerator.of(1), "", IdGenerator.of("1:josh"), true); Assert.assertTrue(edgeIds.contains(edgeId2)); EdgeId edgeId3 = new EdgeId(IdGenerator.of("1:josh"), Directions.IN, + IdGenerator.of(1), IdGenerator.of(1), "", IdGenerator.of("1:marko"), true); Assert.assertFalse(edgeIds.contains(edgeId3)); diff --git a/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/unit/id/IdUtilTest.java b/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/unit/id/IdUtilTest.java index fe9925b2ba..473dc61891 100644 --- a/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/unit/id/IdUtilTest.java +++ b/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/unit/id/IdUtilTest.java @@ -43,13 +43,13 @@ public void testWriteReadString() { Assert.assertEquals("U" + uuid, IdUtil.writeString(id)); Assert.assertEquals(id, IdUtil.readString("U" + uuid)); - id = EdgeId.parse("S1>2>3>L4"); - Assert.assertEquals("ES1>2>3>L4", IdUtil.writeString(id)); - Assert.assertEquals(id, IdUtil.readString("ES1>2>3>L4")); + id = EdgeId.parse("S1>2>3>4>L6"); + Assert.assertEquals("ES1>2>3>4>L6", IdUtil.writeString(id)); + Assert.assertEquals(id, IdUtil.readString("ES1>2>3>4>L6")); - id = EdgeId.parse("S1111>2222>3>L4444"); - Assert.assertEquals("ES1111>2222>3>L4444", IdUtil.writeString(id)); - Assert.assertEquals(id, IdUtil.readString("ES1111>2222>3>L4444")); + id = EdgeId.parse("S1111>2222>3>4>L6666"); + Assert.assertEquals("ES1111>2222>3>4>L6666", IdUtil.writeString(id)); + Assert.assertEquals(id, IdUtil.readString("ES1111>2222>3>4>L6666")); } @Test @@ -70,23 +70,23 @@ public void testWriteReadBinString() { Assert.assertEquals(bytes, IdUtil.writeBinString(id)); Assert.assertEquals(id, IdUtil.readBinString(bytes)); - id = EdgeId.parse("S1>2>3>L4"); - bytes = ByteBuffer.wrap(genBytes("7e803182080233000804")); + id = EdgeId.parse("S1>2>2>4>L6"); + bytes = ByteBuffer.wrap(genBytes("7e8031820802080234000806")); Assert.assertEquals(bytes, IdUtil.writeBinString(id)); Assert.assertEquals(id, IdUtil.readBinString(bytes)); - id = EdgeId.parse("S1111>2222>3>L4444"); - bytes = ByteBuffer.wrap(genBytes("7e8331313131821808ae330018115c")); + id = EdgeId.parse("S1111>2222>3>4>L6666"); + bytes = ByteBuffer.wrap(genBytes("7e8331313131821808ae08033400181a0a")); Assert.assertEquals(bytes, IdUtil.writeBinString(id)); Assert.assertEquals(id, IdUtil.readBinString(bytes)); - id = EdgeId.parse("L11111111>2222>3>L44444444"); - bytes = ByteBuffer.wrap(genBytes("7e28a98ac7821808ae33002aa62b1c")); + id = EdgeId.parse("L11111111>2222>3>4>L66666666"); + bytes = ByteBuffer.wrap(genBytes("7e28a98ac7821808ae080334002bf940aa")); Assert.assertEquals(bytes, IdUtil.writeBinString(id)); Assert.assertEquals(id, IdUtil.readBinString(bytes)); - id = EdgeId.parse("L-1111>2222>33>L4444"); - bytes = ByteBuffer.wrap(genBytes("7e03a9821808ae33330018115c")); + id = EdgeId.parse("L-1111>2222>33>55>L7777"); + bytes = ByteBuffer.wrap(genBytes("7e03a9821808ae0821353500181e61")); Assert.assertEquals(bytes, IdUtil.writeBinString(id)); Assert.assertEquals(id, IdUtil.readBinString(bytes)); } @@ -106,25 +106,25 @@ public void testWriteReadStoredString() { Assert.assertEquals(uuid, IdUtil.writeStoredString(id)); Assert.assertEquals(id, IdUtil.readStoredString(uuid)); - id = EdgeId.parse("S1>2>3>L4"); - Assert.assertEquals("ES1>2>3>L4", IdUtil.writeStoredString(id)); - Assert.assertEquals(id, IdUtil.readStoredString("ES1>2>3>L4")); + id = EdgeId.parse("S1>2>3>4>L6"); + Assert.assertEquals("ES1>2>3>4>L6", IdUtil.writeStoredString(id)); + Assert.assertEquals(id, IdUtil.readStoredString("ES1>2>3>4>L6")); - id = EdgeId.parse("S1111>2222>3>L4444"); - Assert.assertEquals("ES1111>Yj>3>L15S", IdUtil.writeStoredString(id)); - Assert.assertEquals(id, IdUtil.readStoredString("ES1111>Yj>3>L15S")); + id = EdgeId.parse("S1111>2222>3>6>L4444"); + Assert.assertEquals("ES1111>Yj>3>6>L15S", IdUtil.writeStoredString(id)); + Assert.assertEquals(id, IdUtil.readStoredString("ES1111>Yj>3>6>L15S")); - id = EdgeId.parse("L1111>2222>3>L4444"); - Assert.assertEquals("ELHN>Yj>3>L15S", IdUtil.writeStoredString(id)); - Assert.assertEquals(id, IdUtil.readStoredString("ELHN>Yj>3>L15S")); + id = EdgeId.parse("L1111>2222>3>6>L4444"); + Assert.assertEquals("ELHN>Yj>3>6>L15S", IdUtil.writeStoredString(id)); + Assert.assertEquals(id, IdUtil.readStoredString("ELHN>Yj>3>6>L15S")); - id = EdgeId.parse("L11111111>2222>3>L44444444"); - String eid = "ELfOg7>Yj>3>L2eYhS"; + id = EdgeId.parse("L11111111>2222>3>6>L44444444"); + String eid = "ELfOg7>Yj>3>6>L2eYhS"; Assert.assertEquals(eid, IdUtil.writeStoredString(id)); Assert.assertEquals(id, IdUtil.readStoredString(eid)); - id = EdgeId.parse("L-1111>2222>33>L4444"); - eid = "EL-HN>Yj>33>L15S"; + id = EdgeId.parse("L-1111>2222>6>7>L4444"); + eid = "EL-HN>Yj>6>7>L15S"; Assert.assertEquals(eid, IdUtil.writeStoredString(id)); Assert.assertEquals(id, IdUtil.readStoredString(eid)); } diff --git a/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/unit/util/JsonUtilTest.java b/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/unit/util/JsonUtilTest.java index b311651953..6c5159d377 100644 --- a/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/unit/util/JsonUtilTest.java +++ b/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/unit/util/JsonUtilTest.java @@ -18,10 +18,13 @@ package org.apache.hugegraph.unit.util; import java.util.Arrays; +import java.util.Collections; import java.util.Date; +import java.util.HashSet; import java.util.List; import java.util.UUID; +import org.apache.commons.lang3.tuple.Pair; import org.apache.hugegraph.backend.id.EdgeId; import org.apache.hugegraph.backend.id.Id; import org.apache.hugegraph.backend.id.IdGenerator; @@ -82,10 +85,11 @@ public void testSerializeUuidId() { @Test public void testSerializeEdgeId() { Id id = new EdgeId(IdGenerator.of("1:marko"), Directions.OUT, + IdGenerator.of(1), IdGenerator.of(1), "", IdGenerator.of("1:josh")); String json = JsonUtil.toJson(id); - Assert.assertEquals("\"S1:marko>1>>S1:josh\"", json); + Assert.assertEquals("\"S1:marko>1>1>>S1:josh\"", json); } @Test @@ -167,11 +171,13 @@ public void testSerializeEdgeLabel() { Mockito.when(fakeObject.graph().vertexLabel(vl.id())).thenReturn(vl); Mockito.when(fakeObject.graph().mapPkId2Name(el.properties())) .thenReturn(Arrays.asList(date.name(), weight.name())); + Mockito.when(fakeObject.graph().mapPairId2Name(el.links())) + .thenReturn(Collections.singleton(Pair.of(name.name(), name.name()))); String json = JsonUtil.toJson(el); Assert.assertEquals("{\"id\":1,\"name\":\"knows\"," + - "\"source_label\":\"person\"," + - "\"target_label\":\"person\"," + + "\"edgelabel_type\":\"NORMAL\"," + + "\"links\":[{\"name\":\"name\"}]," + "\"frequency\":\"SINGLE\",\"sort_keys\":[]," + "\"nullable_keys\":[],\"index_labels\":[]," + "\"properties\":[\"date\",\"weight\"]," + @@ -277,7 +283,7 @@ public void testSerializeEdge() { HugeVertex target = new HugeVertex(fakeObject.graph(), IdGenerator.of(987654), vl); - Id id = EdgeId.parse("L123456>1>>L987654"); + Id id = EdgeId.parse("L123456>1>1>>L987654"); HugeEdge edge = new HugeEdge(fakeObject.graph(), id, el); Whitebox.setInternalState(edge, "sourceVertex", source); Whitebox.setInternalState(edge, "targetVertex", target); @@ -291,7 +297,7 @@ public void testSerializeEdge() { Whitebox.setInternalState(edge, "properties", properties); String json = JsonUtil.toJson(edge); - Assert.assertEquals("{\"id\":\"L123456>1>>L987654\"," + + Assert.assertEquals("{\"id\":\"L123456>1>1>>L987654\"," + "\"label\":\"knows\",\"type\":\"edge\"," + "\"outV\":123456,\"outVLabel\":\"person\"," + "\"inV\":987654,\"inVLabel\":\"person\"," +