From 2453aad6ff4aa902471dd9a61793c6dc9c23210c Mon Sep 17 00:00:00 2001 From: JisoLya <523420504@qq.com> Date: Wed, 9 Jul 2025 13:33:04 +0800 Subject: [PATCH 01/35] init repo --- .../apache/hugegraph/rocksdb/access/SessionOperatorImpl.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/hugegraph-store/hg-store-rocksdb/src/main/java/org/apache/hugegraph/rocksdb/access/SessionOperatorImpl.java b/hugegraph-store/hg-store-rocksdb/src/main/java/org/apache/hugegraph/rocksdb/access/SessionOperatorImpl.java index eca6a83a2a..0dd58dc7b7 100644 --- a/hugegraph-store/hg-store-rocksdb/src/main/java/org/apache/hugegraph/rocksdb/access/SessionOperatorImpl.java +++ b/hugegraph-store/hg-store-rocksdb/src/main/java/org/apache/hugegraph/rocksdb/access/SessionOperatorImpl.java @@ -219,7 +219,8 @@ public void prepare() { } /** - * commit throws an exception, you must call rollback, otherwise it will cause cfHandleReadLock not to be released. + * commit throws an exception, you must call rollback, otherwise it will cause + * cfHandleReadLock not to be released. */ @Override public Integer commit() throws DBStoreException { @@ -398,6 +399,7 @@ public byte[] position() { public long keyCount(byte[] start, byte[] end, String tableName) { ScanIterator it = scan(tableName, start, end, ScanIterator.Trait.SCAN_LT_END); return it.count(); + //test push } @Override From 986dfef60007b04871df6922104cdb43f2ff02e2 Mon Sep 17 00:00:00 2001 From: JisoLya <523420504@qq.com> Date: Thu, 10 Jul 2025 17:29:53 +0800 Subject: [PATCH 02/35] feat(store):integrate hg-store-common(depend on server module for now) --- hugegraph-store/hg-store-common/pom.xml | 12 + .../store/constant/HugeServerTables.java | 52 ++ .../store/query/BaseElementComparator.java | 71 +++ .../hugegraph/store/query/KvSerializer.java | 306 ++++++++++ .../hugegraph/store/query/PropertyList.java | 76 +++ .../hugegraph/store/query/QueryTypeParam.java | 265 +++++++++ .../store/query/StoreQueryParam.java | 215 +++++++ .../hugegraph/store/query/StoreQueryType.java | 40 ++ .../apache/hugegraph/store/query/Tuple2.java | 38 ++ .../store/query/concurrent/AtomicFloat.java | 165 ++++++ .../func/AbstractAggregationFunction.java | 29 + .../store/query/func/AggregationFunction.java | 67 +++ .../query/func/AggregationFunctionParam.java | 90 +++ .../query/func/AggregationFunctions.java | 532 ++++++++++++++++++ .../query/func/UnaryAggregationFunction.java | 122 ++++ .../hugegraph/store/query/util/KeyUtil.java | 68 +++ .../hugegraph/store/util/Base58Encoder.java | 1 + .../store/util/DefaultThreadFactory.java | 49 ++ .../hugegraph/store/util/ExecutorUtil.java | 68 +++ 19 files changed, 2266 insertions(+) create mode 100644 hugegraph-store/hg-store-common/src/main/java/org/apache/hugegraph/store/constant/HugeServerTables.java create mode 100644 hugegraph-store/hg-store-common/src/main/java/org/apache/hugegraph/store/query/BaseElementComparator.java create mode 100644 hugegraph-store/hg-store-common/src/main/java/org/apache/hugegraph/store/query/KvSerializer.java create mode 100644 hugegraph-store/hg-store-common/src/main/java/org/apache/hugegraph/store/query/PropertyList.java create mode 100644 hugegraph-store/hg-store-common/src/main/java/org/apache/hugegraph/store/query/QueryTypeParam.java create mode 100644 hugegraph-store/hg-store-common/src/main/java/org/apache/hugegraph/store/query/StoreQueryParam.java create mode 100644 hugegraph-store/hg-store-common/src/main/java/org/apache/hugegraph/store/query/StoreQueryType.java create mode 100644 hugegraph-store/hg-store-common/src/main/java/org/apache/hugegraph/store/query/Tuple2.java create mode 100644 hugegraph-store/hg-store-common/src/main/java/org/apache/hugegraph/store/query/concurrent/AtomicFloat.java create mode 100644 hugegraph-store/hg-store-common/src/main/java/org/apache/hugegraph/store/query/func/AbstractAggregationFunction.java create mode 100644 hugegraph-store/hg-store-common/src/main/java/org/apache/hugegraph/store/query/func/AggregationFunction.java create mode 100644 hugegraph-store/hg-store-common/src/main/java/org/apache/hugegraph/store/query/func/AggregationFunctionParam.java create mode 100644 hugegraph-store/hg-store-common/src/main/java/org/apache/hugegraph/store/query/func/AggregationFunctions.java create mode 100644 hugegraph-store/hg-store-common/src/main/java/org/apache/hugegraph/store/query/func/UnaryAggregationFunction.java create mode 100644 hugegraph-store/hg-store-common/src/main/java/org/apache/hugegraph/store/query/util/KeyUtil.java create mode 100644 hugegraph-store/hg-store-common/src/main/java/org/apache/hugegraph/store/util/DefaultThreadFactory.java create mode 100644 hugegraph-store/hg-store-common/src/main/java/org/apache/hugegraph/store/util/ExecutorUtil.java diff --git a/hugegraph-store/hg-store-common/pom.xml b/hugegraph-store/hg-store-common/pom.xml index 7746c76155..30435b8fc3 100644 --- a/hugegraph-store/hg-store-common/pom.xml +++ b/hugegraph-store/hg-store-common/pom.xml @@ -30,4 +30,16 @@ hg-store-common + + + org.projectlombok + lombok + provided + + + com.google.guava + guava + 32.0.1-android + + diff --git a/hugegraph-store/hg-store-common/src/main/java/org/apache/hugegraph/store/constant/HugeServerTables.java b/hugegraph-store/hg-store-common/src/main/java/org/apache/hugegraph/store/constant/HugeServerTables.java new file mode 100644 index 0000000000..20cb83fd8f --- /dev/null +++ b/hugegraph-store/hg-store-common/src/main/java/org/apache/hugegraph/store/constant/HugeServerTables.java @@ -0,0 +1,52 @@ +/* + * 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.store.constant; + +import java.util.Objects; +import java.util.concurrent.ConcurrentHashMap; + +public class HugeServerTables { + + public static final String UNKNOWN_TABLE = "unknown"; + public static final String VERTEX_TABLE = "g+v"; + public static final String OUT_EDGE_TABLE = "g+oe"; + public static final String IN_EDGE_TABLE = "g+ie"; + public static final String INDEX_TABLE = "g+index"; + public static final String TASK_TABLE = "g+task"; + public static final String OLAP_TABLE = "g+olap"; + + public static final String[] TABLES = new String[]{UNKNOWN_TABLE, VERTEX_TABLE, + OUT_EDGE_TABLE, IN_EDGE_TABLE, + INDEX_TABLE, TASK_TABLE, OLAP_TABLE}; + + public static final ConcurrentHashMap TABLES_MAP = new ConcurrentHashMap<>() { + { + put(UNKNOWN_TABLE, 0); + put(VERTEX_TABLE, 1); + put(OUT_EDGE_TABLE, 2); + put(IN_EDGE_TABLE, 3); + put(INDEX_TABLE, 4); + put(TASK_TABLE, 5); + put(OLAP_TABLE, 6); + } + }; + + public static boolean isEdgeTable(String table) { + return Objects.equals(IN_EDGE_TABLE, table) || Objects.equals(OUT_EDGE_TABLE, table); + } +} diff --git a/hugegraph-store/hg-store-common/src/main/java/org/apache/hugegraph/store/query/BaseElementComparator.java b/hugegraph-store/hg-store-common/src/main/java/org/apache/hugegraph/store/query/BaseElementComparator.java new file mode 100644 index 0000000000..f1e0fe5884 --- /dev/null +++ b/hugegraph-store/hg-store-common/src/main/java/org/apache/hugegraph/store/query/BaseElementComparator.java @@ -0,0 +1,71 @@ +/* + * 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.store.query; + +import java.util.Comparator; +import java.util.List; + +import org.apache.hugegraph.id.Id; +import org.apache.hugegraph.structure.BaseElement; + +public class BaseElementComparator implements Comparator { + + private final List ids; + + private boolean isAsc; + + public BaseElementComparator(List list, boolean isAsc) { + this.ids = list; + this.isAsc = isAsc; + } + + public void reverseOrder() { + this.isAsc = !this.isAsc; + } + + @Override + public int compare(BaseElement o1, BaseElement o2) { + if (o1 == null || o2 == null) { + if (o1 == null && o2 == null) { + return 0; + } + return (o1 == null ? -1 : 1) * (this.isAsc ? 1 : -1); + } + + for (var id : ids) { + var ret = compareProperty(o1.getPropertyValue(id), o2.getPropertyValue(id)); + if (ret != 0) { + return ret; + } + } + return 0; + } + + private int compareProperty(Comparable a, Comparable b) { + + if (a != null && b != null) { + return (a.compareTo(b)) * (this.isAsc ? 1 : -1); + } + + if (a == null && b == null) { + return 0; + } + + return (a == null ? -1 : 1) * (this.isAsc ? 1 : -1); + } +} diff --git a/hugegraph-store/hg-store-common/src/main/java/org/apache/hugegraph/store/query/KvSerializer.java b/hugegraph-store/hg-store-common/src/main/java/org/apache/hugegraph/store/query/KvSerializer.java new file mode 100644 index 0000000000..f864fa9e0a --- /dev/null +++ b/hugegraph-store/hg-store-common/src/main/java/org/apache/hugegraph/store/query/KvSerializer.java @@ -0,0 +1,306 @@ +/* + * 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.store.query; + +import java.math.BigDecimal; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; + +import org.apache.hugegraph.store.query.concurrent.AtomicFloat; + +import com.google.common.util.concurrent.AtomicDouble; + +/** + * todo: 修改成类型二进制的存储 + */ +public class KvSerializer { + + private static final byte TYPE_INT = 0; + + private static final byte TYPE_LONG = 1; + + private static final byte TYPE_FLOAT = 2; + + private static final byte TYPE_DOUBLE = 3; + + private static final byte TYPE_STRING = 4; + + private static final byte TYPE_BIG_DECIMAL = 5; + + /** + * for avg function + */ + private static final byte TYPE_TUPLE2 = 6; + + private static final byte TYPE_AT_INT = 7; + + private static final byte TYPE_AT_LONG = 8; + + private static final byte TYPE_AT_FLOAT = 9; + + private static final byte TYPE_AT_DOUBLE = 10; + + private static final byte TYPE_NULL = 127; + + public static byte[] toBytes(List list) { + ByteBuffer buffer = ByteBuffer.allocate(list == null ? 4 : list.size() * 4 + 4); + if (list == null) { + buffer.putInt(-1); + } else { + buffer.putInt(list.size()); + for (Object o : list) { + buffer = write(buffer, o); + } + } + + byte[] bytes = buffer.array(); + int position = buffer.position(); + if (position == bytes.length) { + return bytes; + } else { + return Arrays.copyOf(bytes, position); + } + } + + public static List fromBytes(byte[] bytes) { + List list = new ArrayList<>(); + ByteBuffer buffer = ByteBuffer.wrap(bytes); + int n = buffer.getInt(); + for (int i = 0; i < n; i++) { + list.add((Comparable) read(buffer)); + } + return list; + } + + public static List fromObjectBytes(byte[] bytes) { + ByteBuffer buffer = ByteBuffer.wrap(bytes); + int n = buffer.getInt(); + if (n == -1) { + return null; + } + + List list = new ArrayList<>(); + for (int i = 0; i < n; i++) { + list.add(read(buffer)); + } + return list; + } + + /** + * 从ByteBuffer中读取对象并返回。 + * + * @param buffer 包含要读取对象的ByteBuffer + * @return 返回从ByteBuffer中读取的对象。如果类型为null,则返回null。 + * @throws RuntimeException 当无法支持的类型时抛出异常。 + */ + private static Object read(ByteBuffer buffer) { + var b = buffer.get(); + switch (b) { + case TYPE_INT: + return readInt(buffer); + case TYPE_AT_INT: + return new AtomicInteger(readInt(buffer)); + case TYPE_LONG: + return readLong(buffer); + case TYPE_AT_LONG: + return new AtomicLong(readLong(buffer)); + case TYPE_FLOAT: + return readFloat(buffer); + case TYPE_AT_FLOAT: + return new AtomicFloat(readFloat(buffer)); + case TYPE_DOUBLE: + return readDouble(buffer); + case TYPE_AT_DOUBLE: + return new AtomicDouble(readDouble(buffer)); + case TYPE_STRING: + return readString(buffer); + case TYPE_BIG_DECIMAL: + return readBigDecimal(buffer); + case TYPE_TUPLE2: + return readTuple2(buffer); + case TYPE_NULL: + return null; + default: + throw new RuntimeException("unsupported type " + b); + } + } + + /** + * 写入数据到ByteBuffer中,支持以下类型的写入: + *
    + *
  • null
  • + *
  • {@link Long}
  • + *
  • {@link AtomicInteger}
  • + *
  • {@link Float}
  • + *
  • {@link AtomicFloat}
  • + *
  • {@link Double}
  • + *
  • {@link AtomicDouble}
  • + *
  • {@link String}
  • + *
+ * + * @param buffer 当前写入的ByteBuffer + * @param o 需要写入的数据对象 + * @return 返回更新后的ByteBuffer + */ + private static ByteBuffer write(ByteBuffer buffer, Object o) { + if (o == null) { + buffer = writeByte(buffer, TYPE_NULL); + return buffer; + } + + switch (o.getClass().getName()) { + case "java.lang.Long": + buffer = writeByte(buffer, TYPE_LONG); + buffer = writeLong(buffer, (Long) o); + break; + case "java.util.concurrent.atomic.AtomicLong": + buffer = writeByte(buffer, TYPE_AT_LONG); + buffer = writeLong(buffer, ((AtomicLong) o).get()); + break; + case "java.lang.Integer": + buffer = writeByte(buffer, TYPE_INT); + buffer = writeInt(buffer, (Integer) o); + break; + case "java.util.concurrent.atomic.AtomicInteger": + buffer = writeByte(buffer, TYPE_AT_INT); + buffer = writeInt(buffer, ((AtomicInteger) o).get()); + break; + case "java.lang.Float": + buffer = writeByte(buffer, TYPE_FLOAT); + buffer = writeFloat(buffer, (Float) o); + break; + case "org.apache.hugegraph.store.query.concurrent.AtomicFloat": + buffer = writeByte(buffer, TYPE_AT_FLOAT); + buffer = writeFloat(buffer, ((AtomicFloat) o).get()); + break; + case "java.lang.Double": + buffer = writeByte(buffer, TYPE_DOUBLE); + buffer = writeDouble(buffer, (Double) o); + break; + case "com.google.common.util.concurrent.AtomicDouble": + buffer = writeByte(buffer, TYPE_AT_DOUBLE); + buffer = writeDouble(buffer, ((AtomicDouble) o).get()); + break; + case "java.lang.String": + buffer = writeByte(buffer, TYPE_STRING); + buffer = writeString(buffer, (String) o); + break; + case "java.math.BigDecimal": + buffer = writeByte(buffer, TYPE_BIG_DECIMAL); + buffer = writeBigDecimal(buffer, (BigDecimal) o); + break; + case "org.apache.hugegraph.store.query.Tuple2": + buffer = writeByte(buffer, TYPE_TUPLE2); + buffer = write(buffer, ((Tuple2) o).getV1()); + buffer = write(buffer, ((Tuple2) o).getV2()); + break; + default: + throw new RuntimeException("unsupported type " + o.getClass().getName()); + } + + return buffer; + } + + private static ByteBuffer writeByte(ByteBuffer buffer, byte b) { + buffer = ensureCapacity(buffer, 1); + buffer.put(b); + return buffer; + } + + private static ByteBuffer writeInt(ByteBuffer buffer, int i) { + buffer = ensureCapacity(buffer, Integer.BYTES); + buffer.putInt(i); + return buffer; + } + + private static int readInt(ByteBuffer buffer) { + return buffer.getInt(); + } + + private static ByteBuffer writeLong(ByteBuffer buffer, long l) { + buffer = ensureCapacity(buffer, Long.BYTES); + buffer.putLong(l); + return buffer; + } + + private static long readLong(ByteBuffer buffer) { + return buffer.getLong(); + } + + private static ByteBuffer writeFloat(ByteBuffer buffer, float f) { + buffer = ensureCapacity(buffer, Float.BYTES); + buffer.putFloat(f); + return buffer; + } + + private static float readFloat(ByteBuffer buffer) { + return buffer.getFloat(); + } + + private static ByteBuffer writeDouble(ByteBuffer buffer, double d) { + buffer = ensureCapacity(buffer, Double.BYTES); + buffer.putDouble(d); + return buffer; + } + + private static double readDouble(ByteBuffer buffer) { + return buffer.getDouble(); + } + + private static ByteBuffer writeString(ByteBuffer buffer, String s) { + byte[] bytes = s.getBytes(); + buffer = ensureCapacity(buffer, bytes.length + Integer.BYTES); + buffer.putInt(bytes.length); + buffer.put(bytes); + return buffer; + } + + private static String readString(ByteBuffer buffer) { + int len = buffer.getInt(); + byte[] bytes = new byte[len]; + buffer.get(bytes); + return new String(bytes); + } + + private static ByteBuffer writeBigDecimal(ByteBuffer buffer, BigDecimal d) { + return writeString(buffer, d.toString()); + } + + private static BigDecimal readBigDecimal(ByteBuffer buffer) { + return new BigDecimal(readString(buffer)); + } + + private static Tuple2 readTuple2(ByteBuffer buffer) { + return Tuple2.of(read(buffer), read(buffer)); + } + + private static ByteBuffer ensureCapacity(ByteBuffer buffer, int capacity) { + if (buffer.remaining() < capacity) { + // 防止 capacity 大于 现在的2倍 + var newBuffer = ByteBuffer.allocate(buffer.capacity() * 2 + capacity); + buffer.flip(); + newBuffer.put(buffer); + buffer = newBuffer; + } + return buffer; + } +} diff --git a/hugegraph-store/hg-store-common/src/main/java/org/apache/hugegraph/store/query/PropertyList.java b/hugegraph-store/hg-store-common/src/main/java/org/apache/hugegraph/store/query/PropertyList.java new file mode 100644 index 0000000000..e932fa3ccb --- /dev/null +++ b/hugegraph-store/hg-store-common/src/main/java/org/apache/hugegraph/store/query/PropertyList.java @@ -0,0 +1,76 @@ +/* + * 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.store.query; + +import java.util.List; + +import org.apache.hugegraph.id.Id; + +public class PropertyList { + + /** + * 如果为空,或者 size 为 0,则不过滤 + */ + private final List propertyIds; + /** + * 不返回属性 + */ + private final boolean emptyId; + + private PropertyList(List propertyIds, boolean emptyId) { + this.propertyIds = propertyIds; + this.emptyId = emptyId; + } + + public static PropertyList empty() { + return new PropertyList(List.of(), true); + } + + /** + * default, return all properties + * + * @return + */ + public static PropertyList of() { + return new PropertyList(List.of(), false); + } + + public static PropertyList of(List propertyIds) { + return new PropertyList(propertyIds, false); + } + + public List getPropertyIds() { + return propertyIds; + } + + public boolean isEmptyId() { + return emptyId; + } + + public boolean needSerialize() { + return emptyId || (propertyIds != null && propertyIds.size() > 0); + } + + @Override + public String toString() { + return "PropertyList{" + + "propertyIds=" + propertyIds + + ", isEmpty=" + emptyId + + '}'; + } +} diff --git a/hugegraph-store/hg-store-common/src/main/java/org/apache/hugegraph/store/query/QueryTypeParam.java b/hugegraph-store/hg-store-common/src/main/java/org/apache/hugegraph/store/query/QueryTypeParam.java new file mode 100644 index 0000000000..229657381f --- /dev/null +++ b/hugegraph-store/hg-store-common/src/main/java/org/apache/hugegraph/store/query/QueryTypeParam.java @@ -0,0 +1,265 @@ +/* + * 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.store.query; + +import java.util.Arrays; + +import lombok.Data; + +/** + * primary index scan: + * range scan: start + end + * id scan: start + isPrefix (false) + * prefix scan: start + isPrefix (true) + *

+ * secondary index scan: + * default range: start + end + isSecondaryIndex (true) + */ +@Data +public class QueryTypeParam { + + public static final QueryTypeParam EMPTY = new QueryTypeParam(); + /** + * id scan, the hash code of the key. + * this code would be calculated by KeyUtil.getOwnerKey + * default : -1, scan all partitions. if set, would affect scan partitions of prefix scan and + * range scan. + */ + int code = -1; + /** + * range scan - prefix start, prefix scan, id scan + * class: org.apache.hugegraph.id.Id + */ + private byte[] start; + /** + * range scan - prefix end, prefix scan (null) + * class: org.apache.hugegraph.id.Id + */ + private byte[] end; + /** + * the boundary of range/prefix scan (gt/lt/eq/gte/lte) + */ + private int boundary = 0; + /** + * whether the start key is id or prefix + */ + private boolean isPrefix = false; + /** + * whether lookup index table (g+index) + */ + private boolean isSecondaryIndex = false; + /** + * todo: 从索引反序列化成ID的时候,用于检查id.asBytes()的前缀 + */ + private byte[] idPrefix; + + private QueryTypeParam() { + + } + + public QueryTypeParam(byte[] start, byte[] end, int boundary, boolean isPrefix, + boolean isSecondaryIndex, int code) { + this.start = start; + this.end = end; + this.boundary = boundary; + this.isPrefix = isPrefix; + this.isSecondaryIndex = isSecondaryIndex; + this.code = code; + } + + public QueryTypeParam(byte[] start, byte[] end, int boundary, boolean isPrefix, + boolean isSecondaryIndex, + int code, byte[] idPrefix) { + this.start = start; + this.end = end; + this.boundary = boundary; + this.isPrefix = isPrefix; + this.isSecondaryIndex = isSecondaryIndex; + this.code = code; + this.idPrefix = idPrefix; + } + + @Deprecated + public static QueryTypeParam ofIdScanParam(byte[] start) { + assert (start != null); + return new QueryTypeParam(start, null, 0, false, false, -1); + } + + /** + * primary : id scan + * + * @param start id key + * @param code owner code + * @return param + */ + public static QueryTypeParam ofIdScanParam(byte[] start, int code) { + assert (start != null); + return new QueryTypeParam(start, null, 0, false, false, code); + } + + /** + * primary : prefix scan + * + * @param start prefix + * @param boundary boundary + * @return param + */ + public static QueryTypeParam ofPrefixScanParam(byte[] start, int boundary) { + assert (start != null); + return new QueryTypeParam(start, null, boundary, true, false, -1); + } + + /** + * primary : prefix scan + * + * @param start prefix + * @param boundary boundary + * @param code used for specify partition + * @return param + */ + public static QueryTypeParam ofPrefixScanParam(byte[] start, int boundary, int code) { + assert (start != null); + return new QueryTypeParam(start, null, boundary, true, false, code); + } + + /** + * primary : range scan + * + * @param start start key + * @param end end key + * @param boundary boundary + * @return param + */ + public static QueryTypeParam ofRangeScanParam(byte[] start, byte[] end, int boundary) { + assert (start != null && end != null); + return new QueryTypeParam(start, end, boundary, false, false, -1); + } + + /** + * primary : range scan + * + * @param start start key + * @param end end key + * @param boundary boundary + * @param code use for specify partition + * @return param + */ + public static QueryTypeParam ofRangeScanParam(byte[] start, byte[] end, int boundary, + int code) { + assert (start != null && end != null); + return new QueryTypeParam(start, end, boundary, false, false, code); + } + + /** + * index scan: range scan + * + * @param start range start + * @param end range end + * @param boundary boundary + * @return param + */ + public static QueryTypeParam ofIndexScanParam(byte[] start, byte[] end, int boundary) { + return new QueryTypeParam(start, end, boundary, false, true, -1); + } + + /** + * index scan: range scan with id prefix check + * + * @param start range start + * @param end range end + * @param boundary boundary + * @param idPrefix id prefix + * @return param + */ + public static QueryTypeParam ofIndexScanParam(byte[] start, byte[] end, int boundary, + byte[] idPrefix) { + return new QueryTypeParam(start, end, boundary, false, true, -1, idPrefix); + } + + /** + * index scan : prefix + * + * @param start prefix + * @param boundary boundary + * @return param + */ + public static QueryTypeParam ofIndexScanParam(byte[] start, int boundary) { + return new QueryTypeParam(start, null, boundary, true, true, -1); + } + + /** + * index scan : prefix with id prefix check + * + * @param start prefix + * @param boundary boundary + * @param idPrefix idPrefix + * @return param + */ + public static QueryTypeParam ofIndexScanParam(byte[] start, int boundary, byte[] idPrefix) { + return new QueryTypeParam(start, null, boundary, true, true, -1, idPrefix); + } + + public byte[] getIdPrefix() { + return idPrefix; + } + + public void setIdPrefix(byte[] idPrefix) { + this.idPrefix = idPrefix; + } + + public boolean isIdScan() { + return !isPrefix && start != null && start.length > 0 && (end == null || end.length == 0) && + !isSecondaryIndex; + } + + public boolean isRangeScan() { + return !isPrefix && start != null && start.length > 0 && end != null && end.length > 0 && + !isSecondaryIndex; + } + + public boolean isPrefixScan() { + return isPrefix && start != null && start.length > 0 && (end == null || end.length == 0) && + !isSecondaryIndex; + } + + public boolean isIndexScan() { + return isRangeIndexScan() || isPrefixIndexScan(); + } + + public boolean isRangeIndexScan() { + return isSecondaryIndex && !isPrefix && start != null && start.length > 0 && end != null && + end.length > 0; + } + + public boolean isPrefixIndexScan() { + return isSecondaryIndex && isPrefix && start != null && start.length > 0; + } + + @Override + public String toString() { + return "QueryTypeParam{" + + (isSecondaryIndex ? "[S - " : "[P - ") + + (end != null ? "Range]" : (isPrefix ? "Prefix]" : "ID]")) + + " start=" + Arrays.toString(start) + + (end != null ? ", end=" + Arrays.toString(end) : "") + + ", boundary=" + boundary + + (isIdScan() ? ", code=" + code : "") + + (idPrefix != null ? ", idPrefix=" + Arrays.toString(idPrefix) : "") + + '}'; + } +} diff --git a/hugegraph-store/hg-store-common/src/main/java/org/apache/hugegraph/store/query/StoreQueryParam.java b/hugegraph-store/hg-store-common/src/main/java/org/apache/hugegraph/store/query/StoreQueryParam.java new file mode 100644 index 0000000000..c34a3f26a8 --- /dev/null +++ b/hugegraph-store/hg-store-common/src/main/java/org/apache/hugegraph/store/query/StoreQueryParam.java @@ -0,0 +1,215 @@ +/* + * 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.store.query; + +import java.util.HashSet; +import java.util.List; + +import org.apache.hugegraph.id.Id; +import org.apache.hugegraph.query.ConditionQuery; +import org.apache.hugegraph.store.query.func.AggregationFunctionParam; + +import lombok.Data; + +@Data +public class StoreQueryParam { + + /** + * 针对非 Agg: + * 如果为空,或者size为0,则不过滤 + */ + private final PropertyList properties = PropertyList.of(); + private final boolean groupBySchemaLabel = false; + private final SORT_ORDER sortOrder = SORT_ORDER.ASC; + /** + * 是否需要对key消重,针对多个query param或者index查询 + */ + private final DEDUP_OPTION dedupOption = DEDUP_OPTION.NONE; + /** + * 结果条数的限制 + */ + private final Integer limit = 0; + /** + * offset目前由server托管,理论上都应该是0 + */ + private final Integer offset = 0; + /** + * 抽样频率 + */ + private final double sampleFactor = 1.0; + /** + * 从index id中构建 base element。在No scan的case下 + */ + private final boolean loadPropertyFromIndex = false; + /** + * 是否解析ttl + */ + private final boolean checkTTL = false; + /** + * 客户端生成,用于区分相同不同的query + */ + private String queryId; + /** + * the graph + */ + private String graph; + /** + * the table name + */ + private String table; + /** + * 聚合函数列表 + */ + private List funcList; + /** + * 分组列表, 同时也是properties + */ + private List groupBy; + /** + * 排序字段。 + * 优先级低于 property. + * Agg: 如果不在group by中,id是无效的 + * 非Agg: 如果不在property中,id是无效的 + */ + private List orderBy; + /** + * 过滤条件 + */ + private ConditionQuery conditionQuery; + /** + * 暂不实现 + */ + private List having; + private StoreQueryType queryType; + private List queryParam; + /** + * 用于非 order by, 非Agg的查询中 + */ + private byte[] position; + /** + * 将olap表中对应的属性,添加到HgElement上 (Vertex) + */ + private List olapProperties; + /** + * 索引, 每个内层的元素为and关系,外层为or关系。IndexRange是一个range 查询 + * 如果 scanType是 INDEX_SCAN,则需要回查原表。 + */ + private List> indexes; + + private static void isFalse(boolean expression, String message) { + + if (message == null) { + throw new IllegalArgumentException("message is null"); + } + + if (expression) { + throw new IllegalArgumentException(message); + } + } + + private static boolean isEmpty(List list) { + return list == null || list.size() == 0; + } + + public void checkQuery() { + isFalse(queryId == null, "query id is null"); + isFalse(graph == null, "graph is null"); + isFalse(table == null, "table is null"); + + isFalse(queryType == null, "queryType is null"); + + isFalse(queryType == StoreQueryType.PRIMARY_SCAN && isEmpty(queryParam), + "query param is null when PRIMARY_SCAN"); + // no scan & index scan should have indexes + isFalse(queryType == StoreQueryType.NO_SCAN && isEmpty(indexes), + "ScanType.NO_SCAN without indexes"); + isFalse(queryType == StoreQueryType.NO_SCAN && + (indexes.size() != 1 || indexes.get(0).size() != 1), + "ScanType.NO_SCAN only support one index"); + isFalse(loadPropertyFromIndex && + (isEmpty(indexes) || indexes.size() != 1 || indexes.get(0).size() != 1), + " loadPropertyFromIndex only support one(must be one) index in no scan"); + + isFalse(queryType == StoreQueryType.INDEX_SCAN && isEmpty(indexes), + "ScanType.INDEX_SCAN without indexes "); + + isFalse(!isEmpty(groupBy) && !isEmpty(properties.getPropertyIds()) && + !new HashSet<>(groupBy).containsAll(properties.getPropertyIds()), + "properties should be subset of groupBy"); + + isFalse(!isEmpty(groupBy) && !isEmpty(orderBy) && + !new HashSet<>(groupBy).containsAll(orderBy), + "order by should be subset of groupBy"); + + // isFalse(properties.isEmptyId() && ! queryParam.stream().allMatch(p -> p.isIdScan()), + // "empty property only apply id scan"); + + // todo: just group by, no aggregations ?? + if (funcList != null) { + for (var func : funcList) { + if (func.getFunctionType() == AggregationFunctionParam.AggregationFunctionType.SUM + || + func.getFunctionType() == AggregationFunctionParam.AggregationFunctionType.MAX + || + func.getFunctionType() == AggregationFunctionParam.AggregationFunctionType.MIN + || func.getFunctionType() == + AggregationFunctionParam.AggregationFunctionType.AVG) { + isFalse(func.getField() == null, + func.getFunctionType().name() + " has no filed value"); + } + + if (func.getFunctionType() == + AggregationFunctionParam.AggregationFunctionType.SUM) { + // ||func.getFunctionType() == AggregationFunctionParam + // .AggregationFunctionType.AVG){ + isFalse(func.getFiledType() == AggregationFunctionParam.FiledType.STRING, + func.getFunctionType().name() + " can not apply a String type"); + } + } + } + + isFalse(limit < 0, "limit should be greater than 0"); + isFalse(sampleFactor < 0 || sampleFactor > 1, "sample factor out of range [0-1]"); + } + + public enum DEDUP_OPTION { + NONE, + /** + * 模糊去重,使用bitmap + */ + DEDUP, + /** + * 前N行保证精确去重,之后的非精确 + */ + LIMIT_DEDUP, + /** + * 精确去重,保证准确性 + */ + PRECISE_DEDUP + } + + public enum SORT_ORDER { + ASC, + DESC, + /** + * 仅仅针对全部是ID查询,保持原始传入的id顺序 + */ + STRICT_ORDER + } + +} diff --git a/hugegraph-store/hg-store-common/src/main/java/org/apache/hugegraph/store/query/StoreQueryType.java b/hugegraph-store/hg-store-common/src/main/java/org/apache/hugegraph/store/query/StoreQueryType.java new file mode 100644 index 0000000000..d4e46e65bd --- /dev/null +++ b/hugegraph-store/hg-store-common/src/main/java/org/apache/hugegraph/store/query/StoreQueryType.java @@ -0,0 +1,40 @@ +/* + * 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.store.query; + +public enum StoreQueryType { + /** + * full table scan + */ + TABLE_SCAN, + + /** + * include id, prefix and range + */ + PRIMARY_SCAN, + + /** + * index scan that need look up table (g+v, g+e) back. + */ + INDEX_SCAN, + + /** + * index scan, without look up table back + */ + NO_SCAN +} diff --git a/hugegraph-store/hg-store-common/src/main/java/org/apache/hugegraph/store/query/Tuple2.java b/hugegraph-store/hg-store-common/src/main/java/org/apache/hugegraph/store/query/Tuple2.java new file mode 100644 index 0000000000..ae50cbdb2a --- /dev/null +++ b/hugegraph-store/hg-store-common/src/main/java/org/apache/hugegraph/store/query/Tuple2.java @@ -0,0 +1,38 @@ +/* + * 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.store.query; + +import java.io.Serializable; + +import lombok.Data; + +@Data +public class Tuple2 implements Serializable { + + private final X v1; + private final Y v2; + + public Tuple2(X v1, Y v2) { + this.v1 = v1; + this.v2 = v2; + } + + public static Tuple2 of(X v1, Y v2) { + return new Tuple2<>(v1, v2); + } +} diff --git a/hugegraph-store/hg-store-common/src/main/java/org/apache/hugegraph/store/query/concurrent/AtomicFloat.java b/hugegraph-store/hg-store-common/src/main/java/org/apache/hugegraph/store/query/concurrent/AtomicFloat.java new file mode 100644 index 0000000000..fa404b00be --- /dev/null +++ b/hugegraph-store/hg-store-common/src/main/java/org/apache/hugegraph/store/query/concurrent/AtomicFloat.java @@ -0,0 +1,165 @@ +/* + * 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.store.query.concurrent; + +import java.io.Serializable; +import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; + +public class AtomicFloat extends Number implements Serializable, Comparable { + + private static final AtomicIntegerFieldUpdater FIELD_UPDATER; + + static { + FIELD_UPDATER = AtomicIntegerFieldUpdater.newUpdater(AtomicFloat.class, "intBits"); + } + + private volatile int intBits; + + public AtomicFloat() { + this.intBits = Float.floatToIntBits(0.0f); + } + + public AtomicFloat(float value) { + this.intBits = Float.floatToRawIntBits(value); + } + + public float get() { + return Float.intBitsToFloat(intBits); + } + + public final void set(float newValue) { + this.intBits = Float.floatToIntBits(newValue); + } + + public final float getAndSet(float newValue) { + return getAndSetFloat(newValue); + } + + public final float getAndAdd(float delta) { + return getAndAddFloat(delta); + } + + /** + * 向当前值添加指定值并返回总和。 + * + * @param delta 需要添加的值 + * @return 当前值与参数delta的总和 + */ + public final float addAndGet(float delta) { + return getAndAddFloat(delta) + delta; + } + + /** + * 计算并添加浮点数。将给定的浮点数delta加到当前的浮点数上,并返回结果。 + * + * @param delta 浮点数的增量值 + * @return 返回更新后的浮点数 + */ + private float getAndAddFloat(float delta) { + int oldBits; + int newBits; + do { + oldBits = intBits; + newBits = Float.floatToIntBits(Float.intBitsToFloat(oldBits) + delta); + } while (!FIELD_UPDATER.compareAndSet(this, oldBits, newBits)); + return Float.intBitsToFloat(oldBits); + } + + /** + * 将float值设置为给定的新值,并返回旧值。 + * + * @param newValue 新的float值 + * @return 旧值 + */ + private float getAndSetFloat(float newValue) { + int oldBits; + int newBits; + do { + oldBits = intBits; + newBits = Float.floatToIntBits(newValue); + } while (!FIELD_UPDATER.compareAndSet(this, oldBits, newBits)); + return Float.intBitsToFloat(oldBits); + } + + /** + * {@inheritDoc} + * 返回值将转换为int类型并返回。 + * + * @return 整型数值 + */ + @Override + public int intValue() { + return (int) get(); + } + + /** + * {@inheritDoc} + * 返回一个长整型值。 + * + * @return 长整型值 + */ + @Override + public long longValue() { + return (long) get(); + } + + /** + * {@inheritDoc} 返回当前值的float类型值。 + */ + @Override + public float floatValue() { + return get(); + } + + /** + * {@inheritDoc} + * 返回当前对象的值对应的double类型值。 + * + * @return 当前对象的对应double类型值。 + */ + @Override + public double doubleValue() { + return get(); + } + + /** + * {@inheritDoc} + * 重写父类方法,实现了浮点数的比较。 + * + * @param o 待比较的浮点数 + * @return 如果当前浮点数小于o,返回-1;如果相等,返回0;否则返回1 + */ + @Override + public int compareTo(AtomicFloat o) { + return Float.compare(get(), o.get()); + } + + /** + * {@inheritDoc} + * 返回字符串表示形式。 + * + * @return 包含整型位(intBits)和值的字符串 + */ + @Override + public String toString() { + return "AtomicFloat{" + + "intBits=" + intBits + + ", value = " + get() + + '}'; + } +} diff --git a/hugegraph-store/hg-store-common/src/main/java/org/apache/hugegraph/store/query/func/AbstractAggregationFunction.java b/hugegraph-store/hg-store-common/src/main/java/org/apache/hugegraph/store/query/func/AbstractAggregationFunction.java new file mode 100644 index 0000000000..7a4dcf8692 --- /dev/null +++ b/hugegraph-store/hg-store-common/src/main/java/org/apache/hugegraph/store/query/func/AbstractAggregationFunction.java @@ -0,0 +1,29 @@ +/* + * 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.store.query.func; + +public abstract class AbstractAggregationFunction implements AggregationFunction { + + protected volatile U buffer; + + @Override + public U getBuffer() { + return buffer; + } +} diff --git a/hugegraph-store/hg-store-common/src/main/java/org/apache/hugegraph/store/query/func/AggregationFunction.java b/hugegraph-store/hg-store-common/src/main/java/org/apache/hugegraph/store/query/func/AggregationFunction.java new file mode 100644 index 0000000000..d99763baae --- /dev/null +++ b/hugegraph-store/hg-store-common/src/main/java/org/apache/hugegraph/store/query/func/AggregationFunction.java @@ -0,0 +1,67 @@ +/* + * 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.store.query.func; + +/** + * agg function + * + * @param buffer type + * @param record type + * @param return type + */ +public interface AggregationFunction { + + default void init() { + } + + /** + * initial value of the merge function + * + * @return initial value + */ + U createBuffer(); + + /** + * get the buffer that created by createBuffer() + * + * @return + */ + U getBuffer(); + + /** + * the operation when iterator the record + * + * @param record record + */ + void iterate(R record); + + /** + * merge other to buffer + * + * @param other other buffer + */ + void merge(U other); + + /** + * finial aggregator + * + * @return reduce buffer + */ + T reduce(); + +} diff --git a/hugegraph-store/hg-store-common/src/main/java/org/apache/hugegraph/store/query/func/AggregationFunctionParam.java b/hugegraph-store/hg-store-common/src/main/java/org/apache/hugegraph/store/query/func/AggregationFunctionParam.java new file mode 100644 index 0000000000..dbf32c5fb9 --- /dev/null +++ b/hugegraph-store/hg-store-common/src/main/java/org/apache/hugegraph/store/query/func/AggregationFunctionParam.java @@ -0,0 +1,90 @@ +/* + * 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.store.query.func; + +import org.apache.hugegraph.id.Id; + +import lombok.Data; + +@Data +public class AggregationFunctionParam { + + private AggregationFunctionType functionType; + /** + * the type of aggregation filed. + * eg: sum(age): the type is integer + */ + private FiledType filedType; + /** + * field id + */ + private Id field; + + private AggregationFunctionParam(AggregationFunctionType functionType, FiledType filedType, + Id filed) { + this.functionType = functionType; + this.filedType = filedType; + this.field = filed; + } + + public static AggregationFunctionParam ofCount() { + return new AggregationFunctionParam(AggregationFunctionType.COUNT, FiledType.LONG, null); + } + + public static AggregationFunctionParam ofSum(FiledType filedType, Id filed) { + return new AggregationFunctionParam(AggregationFunctionType.SUM, filedType, filed); + } + + public static AggregationFunctionParam ofMin(FiledType filedType, Id filed) { + return new AggregationFunctionParam(AggregationFunctionType.MIN, filedType, filed); + } + + public static AggregationFunctionParam ofMax(FiledType filedType, Id filed) { + return new AggregationFunctionParam(AggregationFunctionType.MAX, filedType, filed); + } + + public static AggregationFunctionParam ofAvg(FiledType filedType, Id filed) { + return new AggregationFunctionParam(AggregationFunctionType.AVG, filedType, filed); + } + + public enum AggregationFunctionType { + COUNT, + SUM, + MIN, + MAX, + AVG + } + + public enum FiledType { + LONG("java.lang.Long"), + INTEGER("java.lang.Integer"), + FLOAT("java.lang.Float"), + DOUBLE("java.lang.Double"), + STRING("java.lang.String"); + + private final String genericType; + + FiledType(String genericType) { + this.genericType = genericType; + } + + public String getGenericType() { + return genericType; + } + } +} diff --git a/hugegraph-store/hg-store-common/src/main/java/org/apache/hugegraph/store/query/func/AggregationFunctions.java b/hugegraph-store/hg-store-common/src/main/java/org/apache/hugegraph/store/query/func/AggregationFunctions.java new file mode 100644 index 0000000000..8c946192f5 --- /dev/null +++ b/hugegraph-store/hg-store-common/src/main/java/org/apache/hugegraph/store/query/func/AggregationFunctions.java @@ -0,0 +1,532 @@ +/* + * 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.store.query.func; + +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; +import java.util.function.Supplier; + +import org.apache.hugegraph.id.Id; +import org.apache.hugegraph.store.query.Tuple2; +import org.apache.hugegraph.store.query.concurrent.AtomicFloat; + +import com.google.common.util.concurrent.AtomicDouble; + +public class AggregationFunctions { + + public static Supplier getAggregationBufferSupplier(String genericType) { + switch (genericType) { + case "java.lang.Long": + return () -> 0L; + case "java.lang.Integer": + return () -> 0; + case "java.lang.Float": + // fall through to case "java.lang.Double" + case "java.lang.Double": + return () -> 0.0; + case "java.lang.String": + return () -> ""; + default: + throw new RuntimeException("unsupported generic type of buffer: " + genericType); + } + } + + public static class SumFunction extends UnaryAggregationFunction { + + public SumFunction(Id field, Supplier supplier) { + super(field, supplier); + } + + public SumFunction(Supplier supplier) { + super(); + this.supplier = supplier; + this.buffer = initBuffer(); + } + + /** + * 获取并添加记录 + * + * @param record - 添加的记录 + */ + @Override + public void iterate(T record) { + if (record != null) { + switch (buffer.getClass().getName()) { + case "java.util.concurrent.atomic.AtomicLong": + ((AtomicLong) buffer).getAndAdd((long) record); + break; + case "java.util.concurrent.atomic.AtomicInteger": + ((AtomicInteger) buffer).getAndAdd((Integer) record); + break; + case "com.google.common.util.concurrent.AtomicDouble": + ((AtomicDouble) buffer).getAndAdd((Double) record); + break; + case "org.apache.hugegraph.store.query.concurrent.AtomicFloat": + ((AtomicFloat) buffer).getAndAdd((Float) record); + break; + default: + // throw new Exception ? + break; + } + } + } + + /** + * {@inheritDoc} + * 将另一个 U 对象合并到当前对象。 + */ + @Override + public void merge(U other) { + switch (buffer.getClass().getName()) { + case "java.util.concurrent.atomic.AtomicLong": + ((AtomicLong) buffer).getAndAdd(((AtomicLong) other).get()); + break; + case "java.util.concurrent.atomic.AtomicInteger": + ((AtomicInteger) buffer).getAndAdd(((AtomicInteger) other).get()); + break; + case "com.google.common.util.concurrent.AtomicDouble": + ((AtomicDouble) buffer).getAndAdd(((AtomicDouble) other).get()); + break; + case "org.apache.hugegraph.store.query.concurrent.AtomicFloat": + ((AtomicFloat) buffer).getAndAdd(((AtomicFloat) other).get()); + break; + default: + // throw new Exception ? + break; + } + } + + /** + * {@inheritDoc} + */ + @Override + public T reduce() { + switch (buffer.getClass().getName()) { + case "java.util.concurrent.atomic.AtomicLong": + return (T) Long.valueOf(((AtomicLong) buffer).get()); + case "java.util.concurrent.atomic.AtomicInteger": + return (T) Integer.valueOf(((AtomicInteger) buffer).get()); + case "com.google.common.util.concurrent.AtomicDouble": + return (T) Double.valueOf(((AtomicDouble) buffer).get()); + case "org.apache.hugegraph.store.query.concurrent.AtomicFloat": + return (T) Float.valueOf(((AtomicFloat) buffer).get()); + default: + // throw new Exception ? + break; + } + return null; + } + + /** + * {@inheritDoc} + * 初始化缓冲区,返回相应类型的 Atomic 引用对象。 + * + * @return 返回初始化后的 Atomic 对象。 + */ + @Override + protected U initBuffer() { + return getInitValue(() -> new AtomicLong(0), + () -> new AtomicInteger(0), + () -> new AtomicDouble(0.0), + () -> new AtomicFloat(0.0f)); + } + } + + public static class MaxFunction extends UnaryAggregationFunction { + + public MaxFunction(Id field, Supplier supplier) { + super(field, supplier); + } + + public MaxFunction(Supplier supplier) { + super(); + this.supplier = supplier; + this.buffer = initBuffer(); + } + + @Override + protected U initBuffer() { + return getInitValue(() -> new AtomicLong(Long.MIN_VALUE), + () -> new AtomicInteger(Integer.MIN_VALUE), + () -> new AtomicDouble(Double.MIN_VALUE), + () -> new AtomicFloat(Float.MIN_VALUE)); + } + + @Override + public void iterate(T record) { + if (record != null) { + // string case + if (this.buffer == null && record != null) { + this.buffer = (U) record; + return; + } + + switch (buffer.getClass().getName()) { + case "java.util.concurrent.atomic.AtomicLong": + if (((AtomicLong) buffer).get() < (long) record) { + ((AtomicLong) buffer).set((long) record); + } + break; + case "java.util.concurrent.atomic.AtomicInteger": + if (((AtomicInteger) buffer).get() < (int) record) { + ((AtomicInteger) buffer).set((int) record); + } + break; + case "com.google.common.util.concurrent.AtomicDouble": + if (((AtomicDouble) buffer).get() < (double) record) { + ((AtomicDouble) buffer).set((double) record); + } + break; + case "org.apache.hugegraph.store.query.concurrent.AtomicFloat": + if (((AtomicFloat) buffer).get() < (float) record) { + ((AtomicFloat) buffer).set((float) record); + } + break; + + case "java.lang.String": + this.buffer = (U) maxString((String) buffer, (String) record); + break; + default: + // throw new Exception ? + break; + } + } + + } + + @Override + public void merge(U other) { + if (this.buffer == null && other != null) { + this.buffer = other; + return; + } + + switch (buffer.getClass().getName()) { + case "java.util.concurrent.atomic.AtomicLong": + if (((AtomicLong) buffer).get() < ((AtomicLong) other).get()) { + ((AtomicLong) buffer).set(((AtomicLong) other).get()); + } + break; + case "java.util.concurrent.atomic.AtomicInteger": + if (((AtomicInteger) buffer).get() < ((AtomicInteger) other).get()) { + ((AtomicInteger) buffer).set(((AtomicInteger) other).get()); + } + break; + case "com.google.common.util.concurrent.AtomicDouble": + if (((AtomicDouble) buffer).get() < ((AtomicDouble) other).get()) { + ((AtomicDouble) buffer).set(((AtomicDouble) other).get()); + } + break; + case "org.apache.hugegraph.store.query.concurrent.AtomicFloat": + if (((AtomicFloat) buffer).compareTo(((AtomicFloat) other)) < 0) { + ((AtomicFloat) buffer).set(((AtomicFloat) other).get()); + } + break; + case "java.lang.String": + this.buffer = (U) maxString((String) buffer, (String) other); + break; + default: + // throw new Exception ? + break; + } + } + + /** + * 获取两个字符串中较长的那个。如果一个为null,则返回另一个。 + * + * @param s1 第一个字符串 + * @param s2 第二个字符串 + * @return 较长的字符串 + */ + private String maxString(String s1, String s2) { + if (s1 == null || s2 == null) { + return s1 == null ? s2 : s1; + } + return s1.compareTo(s2) >= 0 ? s1 : s2; + } + + @Override + public T reduce() { + switch (buffer.getClass().getName()) { + case "java.util.concurrent.atomic.AtomicLong": + return (T) Long.valueOf(((AtomicLong) this.buffer).get()); + case "java.util.concurrent.atomic.AtomicInteger": + return (T) Integer.valueOf(((AtomicInteger) this.buffer).get()); + case "com.google.common.util.concurrent.AtomicDouble": + return (T) Double.valueOf(((AtomicDouble) this.buffer).get()); + case "org.apache.hugegraph.store.query.concurrent.AtomicFloat": + return (T) Float.valueOf(((AtomicFloat) this.buffer).get()); + case "java.lang.String": + return (T) this.buffer; + default: + // throw new Exception ? + break; + } + return null; + } + } + + public static class MinFunction extends UnaryAggregationFunction { + + public MinFunction(Id field, Supplier supplier) { + super(field, supplier); + } + + public MinFunction(Supplier supplier) { + super(); + this.supplier = supplier; + this.buffer = initBuffer(); + } + + @Override + protected U initBuffer() { + return getInitValue(() -> new AtomicLong(Long.MAX_VALUE), + () -> new AtomicInteger(Integer.MAX_VALUE), + () -> new AtomicDouble(Double.MAX_VALUE), + () -> new AtomicFloat(Float.MAX_VALUE)); + } + + @Override + public void iterate(T record) { + if (record != null) { + // string case + if (this.buffer == null && record != null) { + this.buffer = (U) record; + return; + } + + switch (buffer.getClass().getName()) { + case "java.util.concurrent.atomic.AtomicLong": + if (((AtomicLong) buffer).get() < (long) record) { + ((AtomicLong) buffer).set((long) record); + } + break; + case "java.util.concurrent.atomic.AtomicInteger": + if (((AtomicInteger) buffer).get() < (int) record) { + ((AtomicInteger) buffer).set((int) record); + } + break; + case "com.google.common.util.concurrent.AtomicDouble": + if (((AtomicDouble) buffer).get() < (double) record) { + ((AtomicDouble) buffer).set((double) record); + } + break; + case "org.apache.hugegraph.store.query.concurrent.AtomicFloat": + if (((AtomicFloat) buffer).get() < (float) record) { + ((AtomicFloat) buffer).set((float) record); + } + break; + + case "java.lang.String": + this.buffer = (U) minString((String) buffer, (String) record); + break; + default: + // throw new Exception ? + break; + } + } + } + + @Override + public void merge(U other) { + if (this.buffer == null && other != null) { + this.buffer = other; + return; + } + + switch (buffer.getClass().getName()) { + case "java.util.concurrent.atomic.AtomicLong": + if (((AtomicLong) buffer).get() < ((AtomicLong) other).get()) { + ((AtomicLong) buffer).set(((AtomicLong) other).get()); + } + break; + case "java.util.concurrent.atomic.AtomicInteger": + if (((AtomicInteger) buffer).get() < ((AtomicInteger) other).get()) { + ((AtomicInteger) buffer).set(((AtomicInteger) other).get()); + } + break; + case "com.google.common.util.concurrent.AtomicDouble": + if (((AtomicDouble) buffer).get() < ((AtomicDouble) other).get()) { + ((AtomicDouble) buffer).set(((AtomicDouble) other).get()); + } + break; + case "org.apache.hugegraph.store.query.concurrent.AtomicFloat": + if (((AtomicFloat) buffer).compareTo(((AtomicFloat) other)) < 0) { + ((AtomicFloat) buffer).set(((AtomicFloat) other).get()); + } + break; + case "java.lang.String": + this.buffer = (U) minString((String) buffer, (String) other); + break; + default: + // throw new Exception ? + break; + } + } + + /** + * 返回两个字符串中的较小值。如果一个值为null则返回另一个值。 + * + * @param s1 第一个需要比较的字符串 + * @param s2 第二个需要比较的字符串 + * @return 较小的字符串 + */ + private String minString(String s1, String s2) { + if (s1 == null || s2 == null) { + return s1 == null ? s2 : s1; + } + return s1.compareTo(s2) <= 0 ? s1 : s2; + } + + @Override + public T reduce() { + switch (buffer.getClass().getName()) { + case "java.util.concurrent.atomic.AtomicLong": + return (T) Long.valueOf(((AtomicLong) this.buffer).get()); + case "java.util.concurrent.atomic.AtomicInteger": + return (T) Integer.valueOf(((AtomicInteger) this.buffer).get()); + case "com.google.common.util.concurrent.AtomicDouble": + return (T) Double.valueOf(((AtomicDouble) this.buffer).get()); + case "java.lang.Float": + return (T) Float.valueOf(((AtomicFloat) this.buffer).get()); + case "org.apache.hugegraph.store.query.concurrent.AtomicFloat": + return (T) this.buffer; + default: + // throw new Exception ? + break; + } + return null; + } + + } + + public static class AvgFunction extends + AbstractAggregationFunction, + Double, Double> { + + private final Class filedClassType; + + public AvgFunction(Supplier supplier) { + createBuffer(); + filedClassType = supplier.get().getClass(); + } + + public Class getFiledClassType() { + return filedClassType; + } + + /** + * 创建缓冲区,返回一个包含两个原子变量的元组。 + * + * @return 包含两个原子变量的元组 + */ + @Override + public Tuple2 createBuffer() { + this.buffer = new Tuple2<>(new AtomicLong(0), new AtomicDouble(0.0)); + return this.buffer; + } + + @Override + public void iterate(Double record) { + if (record != null) { + buffer.getV1().getAndAdd(1); + buffer.getV2().getAndAdd(record.doubleValue()); + } + } + + @Override + public void merge(Tuple2 other) { + buffer.getV1().getAndAdd(other.getV1().get()); + buffer.getV2().getAndAdd(other.getV2().get()); + } + + @Override + public Double reduce() { + if (buffer.getV1().get() == 0) { + return Double.NaN; + } + + return buffer.getV2().get() / buffer.getV1().get(); + } + } + + public static class CountFunction extends AbstractAggregationFunction { + + public CountFunction() { + createBuffer(); + } + + @Override + public AtomicLong createBuffer() { + this.buffer = new AtomicLong(); + return this.buffer; + } + + @Override + public AtomicLong getBuffer() { + return this.buffer; + } + + @Override + public void iterate(Long record) { + this.buffer.getAndIncrement(); + } + + @Override + public void merge(AtomicLong other) { + this.buffer.getAndAdd(other.get()); + } + + @Override + public Long reduce() { + return this.buffer.get(); + } + } + + /** + * 应对 group by 无 aggregator的情况 + */ + public static class EmptyFunction implements AggregationFunction { + + @Override + public Integer createBuffer() { + return 0; + } + + @Override + public Integer getBuffer() { + return 0; + } + + @Override + public void iterate(Integer record) { + + } + + @Override + public void merge(Integer other) { + + } + + @Override + public Integer reduce() { + return null; + } + } + +} diff --git a/hugegraph-store/hg-store-common/src/main/java/org/apache/hugegraph/store/query/func/UnaryAggregationFunction.java b/hugegraph-store/hg-store-common/src/main/java/org/apache/hugegraph/store/query/func/UnaryAggregationFunction.java new file mode 100644 index 0000000000..a2c7737e40 --- /dev/null +++ b/hugegraph-store/hg-store-common/src/main/java/org/apache/hugegraph/store/query/func/UnaryAggregationFunction.java @@ -0,0 +1,122 @@ +/* + * 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.store.query.func; + +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; +import java.util.function.Supplier; + +import org.apache.hugegraph.id.Id; +import org.apache.hugegraph.store.query.concurrent.AtomicFloat; + +import com.google.common.util.concurrent.AtomicDouble; + +/** + * base of max, min, sum. (input type equals output type) + * + * @param buffer type (using for concurrency) + * @param record type + */ + +public abstract class UnaryAggregationFunction extends AbstractAggregationFunction { + + /** + * create the buffer + */ + protected Supplier supplier; + + /** + * filed id + */ + protected Id field; + + /** + * type check, filed id and supplier should not be null + */ + protected UnaryAggregationFunction() { + + } + + /** + * init the agg function. the generic info of java would be erased during compiling stage, + * the supplier is used to save the type info mostly. + * + * @param field the field of the element + * @param supplier use to create buffer. + */ + public UnaryAggregationFunction(Id field, Supplier supplier) { + this.field = field; + this.supplier = supplier; + buffer = createBuffer(); + } + + public Id getFieldId() { + return field; + } + + /** + * 创建一个新的缓冲区。 + * + * @return 返回创建的新缓冲区。 + */ + @Override + public U createBuffer() { + return initBuffer(); + } + + protected abstract U initBuffer(); + + /** + * 获取初始值。 + * + * @param longSupplier Long类型的供给者。 + * @param integerSupplier Integer类型的供给者。 + * @param doubleSupplier Double类型的供给者。 + * @param floatSupplier Float类型的供给者。 + * @return 返回初始化值的类型,如果没有找到匹配的类型则返回原来的实例。 + */ + protected U getInitValue(Supplier longSupplier, + Supplier integerSupplier, + Supplier doubleSupplier, + Supplier floatSupplier) { + Object result; + var ins = this.supplier.get(); + switch (ins.getClass().getName()) { + case "java.lang.Long": + result = longSupplier.get(); + break; + case "java.lang.Integer": + result = integerSupplier.get(); + break; + case "java.lang.Double": + result = doubleSupplier.get(); + break; + case "java.lang.Float": + result = floatSupplier.get(); + break; + case "java.lang.String": + result = null; + break; + default: + result = ins; + break; + } + + return (U) result; + } +} diff --git a/hugegraph-store/hg-store-common/src/main/java/org/apache/hugegraph/store/query/util/KeyUtil.java b/hugegraph-store/hg-store-common/src/main/java/org/apache/hugegraph/store/query/util/KeyUtil.java new file mode 100644 index 0000000000..1bb29cbf39 --- /dev/null +++ b/hugegraph-store/hg-store-common/src/main/java/org/apache/hugegraph/store/query/util/KeyUtil.java @@ -0,0 +1,68 @@ +/* + * 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.store.query.util; + +import org.apache.hugegraph.backend.BinaryId; +import org.apache.hugegraph.id.EdgeId; +import org.apache.hugegraph.id.Id; +import org.apache.hugegraph.id.IdUtil; +import org.apache.hugegraph.serializer.BytesBuffer; +import org.apache.hugegraph.store.constant.HugeServerTables; + +public class KeyUtil { + + private static final byte[] EMPTY_BYTES = new byte[0]; + + /** + * 使用的是 huge server的算法 + * + * @param key original key + * @param table looking up table + * @return + */ + public static byte[] getOwnerKey(String table, byte[] key) { + if (key == null || key.length == 0) { + return EMPTY_BYTES; + } + + if (HugeServerTables.isEdgeTable(table)) { + var id = (EdgeId) IdUtil.fromBytes(key); + return idToBytes(id.ownerVertexId()); + } + + return key; + } + + public static byte[] getOwnerId(Id id) { + if (id instanceof BinaryId) { + id = ((BinaryId) id).origin(); + } + if (id != null && id.edge()) { + id = ((EdgeId) id).ownerVertexId(); + } + return id != null ? id.asBytes() : EMPTY_BYTES; + + } + + public static byte[] idToBytes(Id id) { + BytesBuffer buffer = BytesBuffer.allocate(1 + id.length()); + buffer.writeId(id); + return buffer.bytes(); + } + +} diff --git a/hugegraph-store/hg-store-common/src/main/java/org/apache/hugegraph/store/util/Base58Encoder.java b/hugegraph-store/hg-store-common/src/main/java/org/apache/hugegraph/store/util/Base58Encoder.java index 617f6dd28f..48be004de4 100644 --- a/hugegraph-store/hg-store-common/src/main/java/org/apache/hugegraph/store/util/Base58Encoder.java +++ b/hugegraph-store/hg-store-common/src/main/java/org/apache/hugegraph/store/util/Base58Encoder.java @@ -21,6 +21,7 @@ import java.nio.charset.StandardCharsets; import java.util.Arrays; +@Deprecated public class Base58Encoder { public static final char[] CHAR_SET = diff --git a/hugegraph-store/hg-store-common/src/main/java/org/apache/hugegraph/store/util/DefaultThreadFactory.java b/hugegraph-store/hg-store-common/src/main/java/org/apache/hugegraph/store/util/DefaultThreadFactory.java new file mode 100644 index 0000000000..50c347c212 --- /dev/null +++ b/hugegraph-store/hg-store-common/src/main/java/org/apache/hugegraph/store/util/DefaultThreadFactory.java @@ -0,0 +1,49 @@ +/* + * 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.store.util; + +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * @author zhangyingjie + * @date 2023/6/13 + **/ +public class DefaultThreadFactory implements ThreadFactory { + + private final AtomicInteger number = new AtomicInteger(1); + private final String namePrefix; + private final boolean daemon; + + public DefaultThreadFactory(String prefix, boolean daemon) { + this.namePrefix = prefix + "-"; + this.daemon = daemon; + } + + public DefaultThreadFactory(String prefix) { + this(prefix, true); + } + + @Override + public Thread newThread(Runnable r) { + Thread t = new Thread(null, r, namePrefix + number.getAndIncrement(), 0); + t.setDaemon(daemon); + t.setPriority(Thread.NORM_PRIORITY); + return t; + } +} diff --git a/hugegraph-store/hg-store-common/src/main/java/org/apache/hugegraph/store/util/ExecutorUtil.java b/hugegraph-store/hg-store-common/src/main/java/org/apache/hugegraph/store/util/ExecutorUtil.java new file mode 100644 index 0000000000..ab972c43c7 --- /dev/null +++ b/hugegraph-store/hg-store-common/src/main/java/org/apache/hugegraph/store/util/ExecutorUtil.java @@ -0,0 +1,68 @@ +/* + * 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.store.util; + +import java.util.Map; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.SynchronousQueue; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +public final class ExecutorUtil { + + private static final Map pools = new ConcurrentHashMap<>(); + + public static ThreadPoolExecutor getThreadPoolExecutor(String name) { + if (name == null) { + return null; + } + return pools.get(name); + } + + public static ThreadPoolExecutor createExecutor(String name, int coreThreads, int maxThreads, + int queueSize) { + + return createExecutor(name, coreThreads, maxThreads, queueSize, true); + } + + public static ThreadPoolExecutor createExecutor(String name, int coreThreads, int maxThreads, + int queueSize, boolean daemon) { + ThreadPoolExecutor res = pools.get(name); + if (res != null) { + return res; + } + synchronized (pools) { + res = pools.get(name); + if (res != null) { + return res; + } + BlockingQueue queue; + if (queueSize <= 0) { + queue = new SynchronousQueue(); + } else { + queue = new LinkedBlockingQueue<>(queueSize); + } + res = new ThreadPoolExecutor(coreThreads, maxThreads, 60L, TimeUnit.SECONDS, queue, + new DefaultThreadFactory(name, daemon)); + pools.put(name, res); + } + return res; + } +} From 7a3e8fc10c01b384563b41b847d037b5c196e92f Mon Sep 17 00:00:00 2001 From: JisoLya <53420504@qq.com> Date: Fri, 18 Jul 2025 21:23:02 +0800 Subject: [PATCH 03/35] refactor grpc module --- .../hg-store-grpc/src/main/proto/query.proto | 122 ++++++++++++++++++ .../src/main/proto/store_common.proto | 7 + .../src/main/proto/store_session.proto | 6 - .../src/main/proto/store_state.proto | 9 +- hugegraph-store/pom.xml | 5 + 5 files changed, 142 insertions(+), 7 deletions(-) create mode 100644 hugegraph-store/hg-store-grpc/src/main/proto/query.proto diff --git a/hugegraph-store/hg-store-grpc/src/main/proto/query.proto b/hugegraph-store/hg-store-grpc/src/main/proto/query.proto new file mode 100644 index 0000000000..fe8b963bf1 --- /dev/null +++ b/hugegraph-store/hg-store-grpc/src/main/proto/query.proto @@ -0,0 +1,122 @@ +/* + * 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. + */ + +syntax = "proto3"; + +import "store_common.proto"; + +option java_multiple_files = true; +option java_package = "org.apache.hugegraph.store.grpc.query"; +option java_outer_classname = "AggPushDownQueryProto"; + +service QueryService { + rpc query(stream QueryRequest) returns (stream QueryResponse) {} + // rpc close(google.protobuf.StringValue) returns(google.protobuf.BoolValue) {} + // 简单的查询 + rpc query0(QueryRequest) returns (QueryResponse) {} + rpc count(QueryRequest) returns (QueryResponse) {} +} + +enum AggregationType { + COUNT = 0; + SUM = 1; + MIN = 2; + MAX = 3; + AVG = 4; +} + +message AggregateFunc { + AggregationType funcType = 1; + bytes field = 2; // count 函数不检查,设置为-1, property id + string type = 3; // 初始化buffer类型 +} + +enum ScanType { + TABLE_SCAN = 0; + PRIMARY_SCAN = 1; + INDEX_SCAN = 2; + NO_SCAN = 3; // 只扫索引 +} + +message ScanTypeParam { + bytes key_start = 1; + bytes key_end = 2; + int32 scan_boundary = 3; // range boundary + bool is_prefix = 4; // 区分id和prefix + bool is_secondary_index = 5; // 区分primary scan or index scan + int32 code = 6; // id code + bytes id_prefix = 7; // check the element id prefix when parse index +} + +message Index { + repeated ScanTypeParam params = 1; +} + +enum DeDupOption { + NONE = 0; + DEDUP = 1; + LIMIT_DEDUP = 2; + PRECISE_DEDUP = 3; +} + +message QueryRequest{ + string queryId = 1; + string graph = 2; + string table = 3; + + repeated AggregateFunc functions = 4; + // 属性剪裁,如果为空,则返回所有的属性, aggregation 作为单独字段,不包含此列 + // 如果有group by,应该是group by的子集 + repeated bytes property = 5; + repeated bytes group_by = 6; // group by的字段 + repeated uint32 having = 7; // having 的过滤 (暂不实现), + repeated bytes order_by = 8; // order by 字段 + bool sort_order = 9; // asc or desc + bool null_property = 10; // 不使用property,仅仅返回key + + ScanType scan_type = 11; // 表扫描类型, 如果有索引,此项忽略 + + repeated ScanTypeParam scan_type_param = 12; // id, prefix 只用到start + + DeDupOption dedup_option = 13; // 是否需要key消重 + + bytes condition = 21; // condition + bytes position = 24; // 返回offset ~ offset + limit + uint32 limit = 23; // page + uint32 offset = 25; // offset + + double sample_factor = 31; // 抽样频率,应该小于等于1 + + repeated bytes olap_property = 32; // 读取的olap 属性 + + // 使用的索引, 第一层为or关系,第二层为 and关系 + // indexes ((index,index) or (index, index)) + repeated Index indexes = 41; + + bool load_property_from_index = 42; + bool check_ttl = 43; + // 按照element的 label id group by + bool group_by_schema_label = 44; +} + +message QueryResponse { + string query_id = 1; + bool is_ok = 2; + bool is_finished = 3; + string message = 4; + repeated Kv data = 5; +} diff --git a/hugegraph-store/hg-store-grpc/src/main/proto/store_common.proto b/hugegraph-store/hg-store-grpc/src/main/proto/store_common.proto index bc45670198..06d161c70f 100644 --- a/hugegraph-store/hg-store-grpc/src/main/proto/store_common.proto +++ b/hugegraph-store/hg-store-grpc/src/main/proto/store_common.proto @@ -111,3 +111,10 @@ enum GraphMethod{ GRAPH_METHOD_UNKNOWN = 0; GRAPH_METHOD_DELETE = 3; } + +message TTLCleanRequest { + string graph = 1; + int32 partitionId = 2; + string table = 3; + repeated bytes ids = 4; +} diff --git a/hugegraph-store/hg-store-grpc/src/main/proto/store_session.proto b/hugegraph-store/hg-store-grpc/src/main/proto/store_session.proto index e9cb940881..483a7f1ef5 100644 --- a/hugegraph-store/hg-store-grpc/src/main/proto/store_session.proto +++ b/hugegraph-store/hg-store-grpc/src/main/proto/store_session.proto @@ -22,7 +22,6 @@ option java_package = "org.apache.hugegraph.store.grpc.session"; option java_outer_classname = "HgStoreSessionProto"; import "store_common.proto"; -import "store_stream_meta.proto"; service HgStoreSession { rpc Get2(GetReq) returns (FeedbackRes) {} @@ -31,7 +30,6 @@ service HgStoreSession { rpc Table(TableReq) returns (FeedbackRes){}; rpc Graph(GraphReq) returns (FeedbackRes){}; rpc Clean(CleanReq) returns (FeedbackRes) {} - rpc Count(ScanStreamReq) returns (Agg) {} } message TableReq{ @@ -130,7 +128,3 @@ enum PartitionFaultType{ PARTITION_FAULT_TYPE_NOT_LOCAL = 3; } -message Agg { - Header header = 1; - int64 count = 2; -} diff --git a/hugegraph-store/hg-store-grpc/src/main/proto/store_state.proto b/hugegraph-store/hg-store-grpc/src/main/proto/store_state.proto index d2b0aa3613..50671753f5 100644 --- a/hugegraph-store/hg-store-grpc/src/main/proto/store_state.proto +++ b/hugegraph-store/hg-store-grpc/src/main/proto/store_state.proto @@ -32,7 +32,7 @@ service HgStoreState { // Unsubscribe Store Node state publishing. rpc UnsubState(SubStateReq) returns (google.protobuf.Empty){} rpc getScanState(SubStateReq) returns (ScanState){} - + rpc getPeers(PartitionRequest) returns (PeersResponse){} } message SubStateReq{ @@ -71,3 +71,10 @@ enum NodeStateType { message QuotaRequest { map limits = 1; } + +message PartitionRequest{ + int32 id = 1; +} +message PeersResponse{ + string peers = 1; +} diff --git a/hugegraph-store/pom.xml b/hugegraph-store/pom.xml index f9cd0bcfb3..d25db72216 100644 --- a/hugegraph-store/pom.xml +++ b/hugegraph-store/pom.xml @@ -75,6 +75,11 @@ hg-store-core ${project.version} + + org.apache + hugegraph-struct + ${project.version} + org.apache.hugegraph hg-store-transfer From 0de281738c956138e85dde7314ca58e7529e4961 Mon Sep 17 00:00:00 2001 From: JisoLya <53420504@qq.com> Date: Sat, 19 Jul 2025 18:00:48 +0800 Subject: [PATCH 04/35] refactor rocksDB module --- .../rocksdb/access/RocksDBFactory.java | 56 +++++--- .../rocksdb/access/RocksDBOptions.java | 32 +---- .../rocksdb/access/RocksDBScanIterator.java | 7 +- .../rocksdb/access/RocksDBSession.java | 34 ++++- .../rocksdb/access/SessionOperatorImpl.java | 122 +++++++++++------- 5 files changed, 149 insertions(+), 102 deletions(-) diff --git a/hugegraph-store/hg-store-rocksdb/src/main/java/org/apache/hugegraph/rocksdb/access/RocksDBFactory.java b/hugegraph-store/hg-store-rocksdb/src/main/java/org/apache/hugegraph/rocksdb/access/RocksDBFactory.java index ce5dc665a6..2e8e0bae68 100644 --- a/hugegraph-store/hg-store-rocksdb/src/main/java/org/apache/hugegraph/rocksdb/access/RocksDBFactory.java +++ b/hugegraph-store/hg-store-rocksdb/src/main/java/org/apache/hugegraph/rocksdb/access/RocksDBFactory.java @@ -29,6 +29,7 @@ import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.stream.Collectors; @@ -58,11 +59,28 @@ public final class RocksDBFactory { private final ReentrantReadWriteLock operateLock; ScheduledExecutorService scheduledExecutor; private HugeConfig hugeConfig; + private AtomicBoolean closing = new AtomicBoolean(false); private RocksDBFactory() { this.operateLock = new ReentrantReadWriteLock(); scheduledExecutor = Executors.newScheduledThreadPool(2); scheduledExecutor.scheduleWithFixedDelay(() -> { + try { + dbSessionMap.forEach((k, session) -> { + for (var entry : session.getIteratorMap().entrySet()) { + String key = entry.getKey(); + var ts = Long.parseLong(key.split("-")[0]); + // output once per 10min + var passed = (System.currentTimeMillis() - ts) / 1000 - 600; + if (passed > 0 && passed % 10 == 0) { + log.info("iterator not close, stack: {}", entry.getValue()); + } + } + }); + } catch (Exception e) { + log.error("got error, ", e); + } + try { Iterator itr = destroyGraphDBs.listIterator(); while (itr.hasNext()) { @@ -146,12 +164,30 @@ public RocksDBSession queryGraphDB(String dbName) { } return null; } + //TODO is this necessary? + class RocksdbEventListener extends AbstractEventListener { + @Override + public void onCompactionCompleted(RocksDB db, CompactionJobInfo compactionJobInfo) { + super.onCompactionCompleted(db, compactionJobInfo); + rocksdbChangedListeners.forEach(listener -> { + listener.onCompacted(db.getName()); + }); + } + + @Override + public void onCompactionBegin(final RocksDB db, final CompactionJobInfo compactionJobInfo) { + log.info("RocksdbEventListener onCompactionBegin"); + } + } public RocksDBSession createGraphDB(String dbPath, String dbName) { return createGraphDB(dbPath, dbName, 0); } public RocksDBSession createGraphDB(String dbPath, String dbName, long version) { + if (closing.get()) { + throw new RuntimeException("db closed"); + } operateLock.writeLock().lock(); try { RocksDBSession dbSession = dbSessionMap.get(dbName); @@ -231,7 +267,8 @@ public void destroyGraphDB(String dbName) { } public void releaseAllGraphDB() { - log.info("close all rocksdb."); + closing.set(true); + log.info("closing all rocksdb...."); operateLock.writeLock().lock(); try { dbSessionMap.forEach((k, v) -> { @@ -292,24 +329,7 @@ default void onDBSessionReleased(RocksDBSession dbSession) { } } - class RocksdbEventListener extends AbstractEventListener { - - @Override - public void onCompactionCompleted(RocksDB db, CompactionJobInfo compactionJobInfo) { - super.onCompactionCompleted(db, compactionJobInfo); - rocksdbChangedListeners.forEach(listener -> { - listener.onCompacted(db.getName()); - }); - } - - @Override - public void onCompactionBegin(final RocksDB db, final CompactionJobInfo compactionJobInfo) { - log.info("RocksdbEventListener onCompactionBegin"); - } - } - class DBSessionWatcher { - public RocksDBSession dbSession; public Long timestamp; diff --git a/hugegraph-store/hg-store-rocksdb/src/main/java/org/apache/hugegraph/rocksdb/access/RocksDBOptions.java b/hugegraph-store/hg-store-rocksdb/src/main/java/org/apache/hugegraph/rocksdb/access/RocksDBOptions.java index 6f5c35f627..7fcd07f3b8 100644 --- a/hugegraph-store/hg-store-rocksdb/src/main/java/org/apache/hugegraph/rocksdb/access/RocksDBOptions.java +++ b/hugegraph-store/hg-store-rocksdb/src/main/java/org/apache/hugegraph/rocksdb/access/RocksDBOptions.java @@ -23,8 +23,6 @@ import static org.apache.hugegraph.config.OptionChecker.rangeDouble; import static org.apache.hugegraph.config.OptionChecker.rangeInt; -import java.util.Map; - import org.apache.hugegraph.config.ConfigConvOption; import org.apache.hugegraph.config.ConfigListConvOption; import org.apache.hugegraph.config.ConfigOption; @@ -32,7 +30,6 @@ import org.apache.hugegraph.util.Bytes; import org.rocksdb.CompactionStyle; import org.rocksdb.CompressionType; -import org.rocksdb.InfoLogLevel; public class RocksDBOptions extends OptionHolder { @@ -91,13 +88,6 @@ public class RocksDBOptions extends OptionHolder { allowValues("DEBUG", "INFO", "WARN", "ERROR", "FATAL", "HEADER"), "INFO" ); - public static final Map LOG_LEVEL_MAPPING = - Map.of("DEBUG", InfoLogLevel.DEBUG_LEVEL, - "INFO", InfoLogLevel.INFO_LEVEL, - "WARN", InfoLogLevel.WARN_LEVEL, - "ERROR", InfoLogLevel.ERROR_LEVEL, - "FATAL", InfoLogLevel.FATAL_LEVEL, - "HEADER", InfoLogLevel.HEADER_LEVEL); public static final ConfigOption NUM_LEVELS = new ConfigOption<>( @@ -106,27 +96,7 @@ public class RocksDBOptions extends OptionHolder { rangeInt(1, Integer.MAX_VALUE), 7 ); - public static final ConfigOption BLOCK_CACHE_CAPACITY = - new ConfigOption<>( - "rocksdb.block_cache_capacity", - "The amount of block cache in bytes that will be used by all RocksDBs", - rangeInt(0L, Long.MAX_VALUE), - 16L * Bytes.GB - ); - public static final ConfigOption SNAPSHOT_PATH = - new ConfigOption<>( - "rocksdb.snapshot_path", - "The path for storing snapshot of RocksDB.", - disallowEmpty(), - "rocksdb-snapshot" - ); - public static final ConfigOption DISABLE_AUTO_COMPACTION = - new ConfigOption<>( - "rocksdb.disable_auto_compaction", - "Set disable auto compaction.", - disallowEmpty(), - false - ); + public static final ConfigConvOption COMPACTION_STYLE = new ConfigConvOption<>( "rocksdb.compaction_style", diff --git a/hugegraph-store/hg-store-rocksdb/src/main/java/org/apache/hugegraph/rocksdb/access/RocksDBScanIterator.java b/hugegraph-store/hg-store-rocksdb/src/main/java/org/apache/hugegraph/rocksdb/access/RocksDBScanIterator.java index ff255d9ea9..dca8179308 100644 --- a/hugegraph-store/hg-store-rocksdb/src/main/java/org/apache/hugegraph/rocksdb/access/RocksDBScanIterator.java +++ b/hugegraph-store/hg-store-rocksdb/src/main/java/org/apache/hugegraph/rocksdb/access/RocksDBScanIterator.java @@ -20,6 +20,7 @@ import java.util.Arrays; import java.util.NoSuchElementException; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.Consumer; import org.apache.hugegraph.rocksdb.access.RocksDBSession.BackendColumn; import org.apache.hugegraph.util.Bytes; @@ -39,11 +40,13 @@ public class RocksDBScanIterator implements ScanIterator { private final AtomicBoolean closed = new AtomicBoolean(false); private final RocksDBSession.RefCounter iterReference; + private final Consumer closeOp; private byte[] key; private boolean matched; public RocksDBScanIterator(RocksIterator rawIt, byte[] keyBegin, byte[] keyEnd, - int scanType, RocksDBSession.RefCounter iterReference) { + int scanType, RocksDBSession.RefCounter iterReference, + Consumer closeOp) { this.rawIt = rawIt; this.keyBegin = keyBegin; this.keyEnd = keyEnd; @@ -52,6 +55,7 @@ public RocksDBScanIterator(RocksIterator rawIt, byte[] keyBegin, byte[] keyEnd, this.key = keyBegin; this.matched = false; this.iterReference = iterReference; + this.closeOp = closeOp; this.seek(); } @@ -226,6 +230,7 @@ public void close() { if (this.rawIt.isOwningHandle()) { this.rawIt.close(); } + this.closeOp.accept(true); this.iterReference.release(); } } diff --git a/hugegraph-store/hg-store-rocksdb/src/main/java/org/apache/hugegraph/rocksdb/access/RocksDBSession.java b/hugegraph-store/hg-store-rocksdb/src/main/java/org/apache/hugegraph/rocksdb/access/RocksDBSession.java index c3356de248..9c3005da66 100644 --- a/hugegraph-store/hg-store-rocksdb/src/main/java/org/apache/hugegraph/rocksdb/access/RocksDBSession.java +++ b/hugegraph-store/hg-store-rocksdb/src/main/java/org/apache/hugegraph/rocksdb/access/RocksDBSession.java @@ -24,6 +24,7 @@ import java.nio.file.Files; import java.nio.file.Paths; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.Set; @@ -50,6 +51,7 @@ import org.rocksdb.DBOptionsInterface; import org.rocksdb.Env; import org.rocksdb.FlushOptions; +import org.rocksdb.InfoLogLevel; import org.rocksdb.IngestExternalFileOptions; import org.rocksdb.MutableColumnFamilyOptionsInterface; import org.rocksdb.MutableDBOptionsInterface; @@ -63,6 +65,7 @@ import org.rocksdb.WriteBufferManager; import org.rocksdb.WriteOptions; +import lombok.Getter; import lombok.extern.slf4j.Slf4j; @Slf4j @@ -83,8 +86,10 @@ public class RocksDBSession implements AutoCloseable, Cloneable { private DBOptions dbOptions; private volatile boolean closed = false; - public RocksDBSession(HugeConfig hugeConfig, String dbDataPath, String graphName, - long version) { + @Getter + private Map iteratorMap; + + public RocksDBSession(HugeConfig hugeConfig, String dbDataPath, String graphName, long version) { this.hugeConfig = hugeConfig; this.graphName = graphName; this.cfHandleLock = new ReentrantReadWriteLock(); @@ -93,6 +98,7 @@ public RocksDBSession(HugeConfig hugeConfig, String dbDataPath, String graphName this.shutdown = new AtomicBoolean(false); this.writeOptions = new WriteOptions(); this.rocksDbStats = new Statistics(); + this.iteratorMap = new ConcurrentHashMap<>(); openRocksDB(dbDataPath, version); } @@ -107,6 +113,7 @@ private RocksDBSession(RocksDBSession origin) { this.writeOptions = origin.writeOptions; this.rocksDbStats = origin.rocksDbStats; this.shutdown = origin.shutdown; + this.iteratorMap = origin.iteratorMap; this.refCount = origin.refCount; this.refCount.incrementAndGet(); } @@ -143,8 +150,8 @@ public static void initOptions(HugeConfig conf, db.setAllowConcurrentMemtableWrite(true); db.setEnableWriteThreadAdaptiveYield(true); } - db.setInfoLogLevel( - RocksDBOptions.LOG_LEVEL_MAPPING.get(conf.get(RocksDBOptions.LOG_LEVEL))); + db.setInfoLogLevel(InfoLogLevel.valueOf( + conf.get(RocksDBOptions.LOG_LEVEL) + "_LEVEL")); db.setMaxSubcompactions(conf.get(RocksDBOptions.MAX_SUB_COMPACTIONS)); db.setAllowMmapWrites(conf.get(RocksDBOptions.ALLOW_MMAP_WRITES)); db.setAllowMmapReads(conf.get(RocksDBOptions.ALLOW_MMAP_READS)); @@ -430,9 +437,6 @@ private void openRocksDB(String dbDataPath, long version) { List columnFamilyBytes = RocksDB.listColumnFamilies(new Options(), dbPath); ColumnFamilyOptions cfOptions = new ColumnFamilyOptions(); - if (hugeConfig.get(RocksDBOptions.DISABLE_AUTO_COMPACTION)) { - cfOptions.setDisableAutoCompactions(true); - } RocksDBSession.initOptions(this.hugeConfig, null, null, cfOptions, cfOptions); if (columnFamilyBytes.size() > 0) { @@ -1055,4 +1059,20 @@ public void release() { } } } + + public static String stackToString() { + return Arrays.stream(Thread.currentThread().getStackTrace()) + .map(StackTraceElement::toString) + .collect(Collectors.joining("\n\t")); + } + + public void addIterator(String key, ScanIterator iterator) { + log.debug("add iterator, key {}", key); + this.iteratorMap.put(key, stackToString()); + } + + public void removeIterator(String key) { + log.debug("remove iterator key, {}", key); + this.iteratorMap.remove(key); + } } diff --git a/hugegraph-store/hg-store-rocksdb/src/main/java/org/apache/hugegraph/rocksdb/access/SessionOperatorImpl.java b/hugegraph-store/hg-store-rocksdb/src/main/java/org/apache/hugegraph/rocksdb/access/SessionOperatorImpl.java index 0dd58dc7b7..d8e668391e 100644 --- a/hugegraph-store/hg-store-rocksdb/src/main/java/org/apache/hugegraph/rocksdb/access/SessionOperatorImpl.java +++ b/hugegraph-store/hg-store-rocksdb/src/main/java/org/apache/hugegraph/rocksdb/access/SessionOperatorImpl.java @@ -20,6 +20,7 @@ import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.Iterator; +import java.util.Random; import org.apache.hugegraph.rocksdb.access.RocksDBSession.CFHandleLock; import org.apache.hugegraph.rocksdb.access.util.Asserts; @@ -264,9 +265,18 @@ public ScanIterator scan(String tableName) { log.info("no find table : {}", tableName); return null; } - return new RocksDBScanIterator(this.rocksdb().newIterator(handle.get()), null, null, - ScanIterator.Trait.SCAN_ANY, - this.session.getRefCounter()); + String key = getIteratorKey(); + + var iterator = + new RocksDBScanIterator( + this.rocksdb().newIterator(handle.get()), + null, + null, + ScanIterator.Trait.SCAN_ANY, + this.session.getRefCounter(), + b -> session.removeIterator(key)); + this.session.addIterator(key, iterator); + return iterator; } } @@ -283,9 +293,17 @@ public ScanIterator scan(String tableName, byte[] prefix, int scanType) { new String(prefix)); return null; } - return new RocksDBScanIterator(this.rocksdb().newIterator(handle.get()), prefix, null, - ScanIterator.Trait.SCAN_PREFIX_BEGIN | scanType, - this.session.getRefCounter()); + String key = getIteratorKey(); + var iterator = + new RocksDBScanIterator( + this.rocksdb().newIterator(handle.get()), + prefix, + null, + ScanIterator.Trait.SCAN_PREFIX_BEGIN | scanType, + this.session.getRefCounter(), + b -> session.removeIterator(key)); + this.session.addIterator(key, iterator); + return iterator; } } @@ -296,9 +314,17 @@ public ScanIterator scan(String tableName, byte[] keyFrom, byte[] keyTo, int sca log.info("no find table: {} for scantype: {}", tableName, scanType); return null; } - return new RocksDBScanIterator(this.rocksdb().newIterator(handle.get()), keyFrom, keyTo, - scanType, - this.session.getRefCounter()); + String key = getIteratorKey(); + var iterator = + new RocksDBScanIterator( + this.rocksdb().newIterator(handle.get()), + keyFrom, + keyTo, + scanType, + this.session.getRefCounter(), + b -> session.removeIterator(key)); + this.session.addIterator(key, iterator); + return iterator; } } @@ -344,53 +370,55 @@ public T next() { iterator.seekToFirst(); } } - if (iterator == null) { - return null; - } - RocksIterator finalIterator = iterator; - return (T) new ScanIterator() { - private final ReadOptions holdReadOptions = readOptions; - - @Override - public boolean hasNext() { - return finalIterator.isValid(); - } + if (iterator == null) return null; + String key = getIteratorKey(); + var newIterator = getScanRawIterator(iterator, readOptions, startSeqNum, key); + session.addIterator(key, newIterator); + return (T) newIterator; + } - @Override - public boolean isValid() { - return finalIterator.isValid(); - } + @Override + public void close() { + rocksdb().releaseSnapshot(snapshot); + } - @Override - public T next() { - byte[] key = finalIterator.key(); - if (startSeqNum > 0) { - key = Arrays.copyOfRange(key, 0, key.length - kNumInternalBytes); - } - RocksDBSession.BackendColumn col = - RocksDBSession.BackendColumn.of(key, finalIterator.value()); - finalIterator.next(); - return (T) col; - } + public byte[] position() { + return cfName.getBytes(StandardCharsets.UTF_8); + } + }; + } - @Override - public void close() { - finalIterator.close(); - holdReadOptions.close(); - } + private ScanIterator getScanRawIterator(RocksIterator iterator, ReadOptions readOptions, + long startSeqNum, String key) { + int kNumInternalBytes = 8; // internal key new 8 bytes suffix - }; + return new ScanIterator() { + @Override + public boolean hasNext() { + return iterator.isValid(); } @Override - public void close() { - rocksdb().releaseSnapshot(snapshot); + public boolean isValid() { + return iterator.isValid(); } @Override - public byte[] position() { - return cfName.getBytes(StandardCharsets.UTF_8); + public T next() { + byte[] key = iterator.key(); + if (startSeqNum > 0) { + key = Arrays.copyOfRange(key, 0, key.length - kNumInternalBytes); + } + var col = RocksDBSession.BackendColumn.of(key, iterator.value()); + iterator.next(); + return (T) col; + } + @Override + public void close() { + iterator.close(); + readOptions.close(); + session.removeIterator(key); } }; } @@ -418,4 +446,8 @@ private WriteBatch getBatch() { } return this.batch; } + + private String getIteratorKey() { + return System.currentTimeMillis() + "-" + (new Random()).nextLong(); + } } From 928b322591ded9a8588225400602a0c05b0ef8f1 Mon Sep 17 00:00:00 2001 From: JisoLya <523420504@qq.com> Date: Sun, 20 Jul 2025 16:15:24 +0800 Subject: [PATCH 05/35] add struct module --- hugegraph-struct/pom.xml | 197 +++ .../apache/hugegraph/HugeGraphSupplier.java | 79 ++ .../org/apache/hugegraph/SchemaDriver.java | 860 ++++++++++++ .../org/apache/hugegraph/SchemaGraph.java | 182 +++ .../apache/hugegraph/analyzer/Analyzer.java | 27 + .../hugegraph/analyzer/AnalyzerFactory.java | 102 ++ .../hugegraph/analyzer/AnsjAnalyzer.java | 87 ++ .../hugegraph/analyzer/HanLPAnalyzer.java | 108 ++ .../apache/hugegraph/analyzer/IKAnalyzer.java | 73 + .../hugegraph/analyzer/JcsegAnalyzer.java | 77 + .../hugegraph/analyzer/JiebaAnalyzer.java | 63 + .../hugegraph/analyzer/MMSeg4JAnalyzer.java | 92 ++ .../hugegraph/analyzer/SmartCNAnalyzer.java | 66 + .../hugegraph/analyzer/WordAnalyzer.java | 74 + .../apache/hugegraph/auth/AuthConstant.java | 30 + .../apache/hugegraph/auth/TokenGenerator.java | 70 + .../hugegraph/backend/BackendColumn.java | 69 + .../apache/hugegraph/backend/BinaryId.java | 103 ++ .../org/apache/hugegraph/backend/Shard.java | 71 + .../hugegraph/exception/BackendException.java | 53 + .../exception/ErrorCodeProvider.java | 27 + .../hugegraph/exception/HugeException.java | 70 + .../exception/LimitExceedException.java | 33 + .../exception/NotAllowException.java | 33 + .../exception/NotFoundException.java | 37 + .../exception/NotSupportException.java | 34 + .../java/org/apache/hugegraph/id/EdgeId.java | 350 +++++ .../main/java/org/apache/hugegraph/id/Id.java | 90 ++ .../org/apache/hugegraph/id/IdGenerator.java | 465 +++++++ .../java/org/apache/hugegraph/id/IdUtil.java | 162 +++ .../hugegraph/id/SplicingIdGenerator.java | 150 ++ .../apache/hugegraph/options/AuthOptions.java | 153 ++ .../apache/hugegraph/options/CoreOptions.java | 715 ++++++++++ .../org/apache/hugegraph/query/Aggregate.java | 61 + .../hugegraph/query/AggregateFuncDefine.java | 33 + .../org/apache/hugegraph/query/Condition.java | 1045 ++++++++++++++ .../hugegraph/query/ConditionQuery.java | 1239 +++++++++++++++++ .../org/apache/hugegraph/query/IdQuery.java | 127 ++ .../apache/hugegraph/query/MatchedIndex.java | 81 ++ .../org/apache/hugegraph/query/Query.java | 720 ++++++++++ .../serializer/AbstractSerializerAdapter.java | 62 + .../query/serializer/QueryAdapter.java | 148 ++ .../query/serializer/QueryIdAdapter.java | 46 + .../apache/hugegraph/schema/EdgeLabel.java | 449 ++++++ .../apache/hugegraph/schema/IndexLabel.java | 498 +++++++ .../apache/hugegraph/schema/PropertyKey.java | 646 +++++++++ .../hugegraph/schema/SchemaElement.java | 259 ++++ .../apache/hugegraph/schema/SchemaLabel.java | 204 +++ .../org/apache/hugegraph/schema/Userdata.java | 64 + .../apache/hugegraph/schema/VertexLabel.java | 414 ++++++ .../schema/builder/SchemaBuilder.java | 42 + .../serializer/BinaryElementSerializer.java | 536 +++++++ .../hugegraph/serializer/BytesBuffer.java | 1012 ++++++++++++++ .../serializer/DirectBinarySerializer.java | 128 ++ .../apache/hugegraph/structure/BaseEdge.java | 288 ++++ .../hugegraph/structure/BaseElement.java | 355 +++++ .../hugegraph/structure/BaseProperty.java | 68 + .../hugegraph/structure/BaseRawElement.java | 57 + .../hugegraph/structure/BaseVertex.java | 168 +++ .../org/apache/hugegraph/structure/Index.java | 334 +++++ .../apache/hugegraph/structure/KvElement.java | 101 ++ .../structure/builder/IndexBuilder.java | 327 +++++ .../org/apache/hugegraph/type/GraphType.java | 23 + .../org/apache/hugegraph/type/HugeType.java | 213 +++ .../org/apache/hugegraph/type/Idfiable.java | 27 + .../apache/hugegraph/type/Indexfiable.java | 29 + .../org/apache/hugegraph/type/Namifiable.java | 30 + .../org/apache/hugegraph/type/Propfiable.java | 29 + .../org/apache/hugegraph/type/Typifiable.java | 26 + .../apache/hugegraph/type/define/Action.java | 76 + .../hugegraph/type/define/AggregateType.java | 93 ++ .../hugegraph/type/define/Cardinality.java | 69 + .../hugegraph/type/define/CollectionType.java | 68 + .../hugegraph/type/define/DataType.java | 224 +++ .../hugegraph/type/define/Directions.java | 89 ++ .../hugegraph/type/define/EdgeLabelType.java | 72 + .../hugegraph/type/define/Frequency.java | 51 + .../hugegraph/type/define/HugeKeys.java | 108 ++ .../hugegraph/type/define/IdStrategy.java | 71 + .../hugegraph/type/define/IndexType.java | 122 ++ .../hugegraph/type/define/SchemaStatus.java | 67 + .../hugegraph/type/define/SerialEnum.java | 83 ++ .../hugegraph/type/define/WriteType.java | 67 + .../java/org/apache/hugegraph/util/Blob.java | 73 + .../org/apache/hugegraph/util/GraphUtils.java | 34 + .../org/apache/hugegraph/util/LZ4Util.java | 95 ++ .../apache/hugegraph/util/StringEncoding.java | 203 +++ .../util/collection/CollectionFactory.java | 264 ++++ .../hugegraph/util/collection/IdSet.java | 120 ++ 89 files changed, 16740 insertions(+) create mode 100644 hugegraph-struct/pom.xml create mode 100644 hugegraph-struct/src/main/java/org/apache/hugegraph/HugeGraphSupplier.java create mode 100644 hugegraph-struct/src/main/java/org/apache/hugegraph/SchemaDriver.java create mode 100644 hugegraph-struct/src/main/java/org/apache/hugegraph/SchemaGraph.java create mode 100644 hugegraph-struct/src/main/java/org/apache/hugegraph/analyzer/Analyzer.java create mode 100644 hugegraph-struct/src/main/java/org/apache/hugegraph/analyzer/AnalyzerFactory.java create mode 100644 hugegraph-struct/src/main/java/org/apache/hugegraph/analyzer/AnsjAnalyzer.java create mode 100644 hugegraph-struct/src/main/java/org/apache/hugegraph/analyzer/HanLPAnalyzer.java create mode 100644 hugegraph-struct/src/main/java/org/apache/hugegraph/analyzer/IKAnalyzer.java create mode 100644 hugegraph-struct/src/main/java/org/apache/hugegraph/analyzer/JcsegAnalyzer.java create mode 100644 hugegraph-struct/src/main/java/org/apache/hugegraph/analyzer/JiebaAnalyzer.java create mode 100644 hugegraph-struct/src/main/java/org/apache/hugegraph/analyzer/MMSeg4JAnalyzer.java create mode 100644 hugegraph-struct/src/main/java/org/apache/hugegraph/analyzer/SmartCNAnalyzer.java create mode 100644 hugegraph-struct/src/main/java/org/apache/hugegraph/analyzer/WordAnalyzer.java create mode 100644 hugegraph-struct/src/main/java/org/apache/hugegraph/auth/AuthConstant.java create mode 100644 hugegraph-struct/src/main/java/org/apache/hugegraph/auth/TokenGenerator.java create mode 100644 hugegraph-struct/src/main/java/org/apache/hugegraph/backend/BackendColumn.java create mode 100644 hugegraph-struct/src/main/java/org/apache/hugegraph/backend/BinaryId.java create mode 100644 hugegraph-struct/src/main/java/org/apache/hugegraph/backend/Shard.java create mode 100644 hugegraph-struct/src/main/java/org/apache/hugegraph/exception/BackendException.java create mode 100644 hugegraph-struct/src/main/java/org/apache/hugegraph/exception/ErrorCodeProvider.java create mode 100644 hugegraph-struct/src/main/java/org/apache/hugegraph/exception/HugeException.java create mode 100644 hugegraph-struct/src/main/java/org/apache/hugegraph/exception/LimitExceedException.java create mode 100644 hugegraph-struct/src/main/java/org/apache/hugegraph/exception/NotAllowException.java create mode 100644 hugegraph-struct/src/main/java/org/apache/hugegraph/exception/NotFoundException.java create mode 100644 hugegraph-struct/src/main/java/org/apache/hugegraph/exception/NotSupportException.java create mode 100644 hugegraph-struct/src/main/java/org/apache/hugegraph/id/EdgeId.java create mode 100644 hugegraph-struct/src/main/java/org/apache/hugegraph/id/Id.java create mode 100644 hugegraph-struct/src/main/java/org/apache/hugegraph/id/IdGenerator.java create mode 100644 hugegraph-struct/src/main/java/org/apache/hugegraph/id/IdUtil.java create mode 100644 hugegraph-struct/src/main/java/org/apache/hugegraph/id/SplicingIdGenerator.java create mode 100644 hugegraph-struct/src/main/java/org/apache/hugegraph/options/AuthOptions.java create mode 100644 hugegraph-struct/src/main/java/org/apache/hugegraph/options/CoreOptions.java create mode 100644 hugegraph-struct/src/main/java/org/apache/hugegraph/query/Aggregate.java create mode 100644 hugegraph-struct/src/main/java/org/apache/hugegraph/query/AggregateFuncDefine.java create mode 100644 hugegraph-struct/src/main/java/org/apache/hugegraph/query/Condition.java create mode 100644 hugegraph-struct/src/main/java/org/apache/hugegraph/query/ConditionQuery.java create mode 100644 hugegraph-struct/src/main/java/org/apache/hugegraph/query/IdQuery.java create mode 100644 hugegraph-struct/src/main/java/org/apache/hugegraph/query/MatchedIndex.java create mode 100644 hugegraph-struct/src/main/java/org/apache/hugegraph/query/Query.java create mode 100644 hugegraph-struct/src/main/java/org/apache/hugegraph/query/serializer/AbstractSerializerAdapter.java create mode 100644 hugegraph-struct/src/main/java/org/apache/hugegraph/query/serializer/QueryAdapter.java create mode 100644 hugegraph-struct/src/main/java/org/apache/hugegraph/query/serializer/QueryIdAdapter.java create mode 100644 hugegraph-struct/src/main/java/org/apache/hugegraph/schema/EdgeLabel.java create mode 100644 hugegraph-struct/src/main/java/org/apache/hugegraph/schema/IndexLabel.java create mode 100644 hugegraph-struct/src/main/java/org/apache/hugegraph/schema/PropertyKey.java create mode 100644 hugegraph-struct/src/main/java/org/apache/hugegraph/schema/SchemaElement.java create mode 100644 hugegraph-struct/src/main/java/org/apache/hugegraph/schema/SchemaLabel.java create mode 100644 hugegraph-struct/src/main/java/org/apache/hugegraph/schema/Userdata.java create mode 100644 hugegraph-struct/src/main/java/org/apache/hugegraph/schema/VertexLabel.java create mode 100644 hugegraph-struct/src/main/java/org/apache/hugegraph/schema/builder/SchemaBuilder.java create mode 100644 hugegraph-struct/src/main/java/org/apache/hugegraph/serializer/BinaryElementSerializer.java create mode 100644 hugegraph-struct/src/main/java/org/apache/hugegraph/serializer/BytesBuffer.java create mode 100644 hugegraph-struct/src/main/java/org/apache/hugegraph/serializer/DirectBinarySerializer.java create mode 100644 hugegraph-struct/src/main/java/org/apache/hugegraph/structure/BaseEdge.java create mode 100644 hugegraph-struct/src/main/java/org/apache/hugegraph/structure/BaseElement.java create mode 100644 hugegraph-struct/src/main/java/org/apache/hugegraph/structure/BaseProperty.java create mode 100644 hugegraph-struct/src/main/java/org/apache/hugegraph/structure/BaseRawElement.java create mode 100644 hugegraph-struct/src/main/java/org/apache/hugegraph/structure/BaseVertex.java create mode 100644 hugegraph-struct/src/main/java/org/apache/hugegraph/structure/Index.java create mode 100644 hugegraph-struct/src/main/java/org/apache/hugegraph/structure/KvElement.java create mode 100644 hugegraph-struct/src/main/java/org/apache/hugegraph/structure/builder/IndexBuilder.java create mode 100644 hugegraph-struct/src/main/java/org/apache/hugegraph/type/GraphType.java create mode 100644 hugegraph-struct/src/main/java/org/apache/hugegraph/type/HugeType.java create mode 100644 hugegraph-struct/src/main/java/org/apache/hugegraph/type/Idfiable.java create mode 100644 hugegraph-struct/src/main/java/org/apache/hugegraph/type/Indexfiable.java create mode 100644 hugegraph-struct/src/main/java/org/apache/hugegraph/type/Namifiable.java create mode 100644 hugegraph-struct/src/main/java/org/apache/hugegraph/type/Propfiable.java create mode 100644 hugegraph-struct/src/main/java/org/apache/hugegraph/type/Typifiable.java create mode 100644 hugegraph-struct/src/main/java/org/apache/hugegraph/type/define/Action.java create mode 100644 hugegraph-struct/src/main/java/org/apache/hugegraph/type/define/AggregateType.java create mode 100644 hugegraph-struct/src/main/java/org/apache/hugegraph/type/define/Cardinality.java create mode 100644 hugegraph-struct/src/main/java/org/apache/hugegraph/type/define/CollectionType.java create mode 100644 hugegraph-struct/src/main/java/org/apache/hugegraph/type/define/DataType.java create mode 100644 hugegraph-struct/src/main/java/org/apache/hugegraph/type/define/Directions.java create mode 100644 hugegraph-struct/src/main/java/org/apache/hugegraph/type/define/EdgeLabelType.java create mode 100644 hugegraph-struct/src/main/java/org/apache/hugegraph/type/define/Frequency.java create mode 100644 hugegraph-struct/src/main/java/org/apache/hugegraph/type/define/HugeKeys.java create mode 100644 hugegraph-struct/src/main/java/org/apache/hugegraph/type/define/IdStrategy.java create mode 100644 hugegraph-struct/src/main/java/org/apache/hugegraph/type/define/IndexType.java create mode 100644 hugegraph-struct/src/main/java/org/apache/hugegraph/type/define/SchemaStatus.java create mode 100644 hugegraph-struct/src/main/java/org/apache/hugegraph/type/define/SerialEnum.java create mode 100644 hugegraph-struct/src/main/java/org/apache/hugegraph/type/define/WriteType.java create mode 100644 hugegraph-struct/src/main/java/org/apache/hugegraph/util/Blob.java create mode 100644 hugegraph-struct/src/main/java/org/apache/hugegraph/util/GraphUtils.java create mode 100644 hugegraph-struct/src/main/java/org/apache/hugegraph/util/LZ4Util.java create mode 100644 hugegraph-struct/src/main/java/org/apache/hugegraph/util/StringEncoding.java create mode 100644 hugegraph-struct/src/main/java/org/apache/hugegraph/util/collection/CollectionFactory.java create mode 100644 hugegraph-struct/src/main/java/org/apache/hugegraph/util/collection/IdSet.java diff --git a/hugegraph-struct/pom.xml b/hugegraph-struct/pom.xml new file mode 100644 index 0000000000..dc9759bda5 --- /dev/null +++ b/hugegraph-struct/pom.xml @@ -0,0 +1,197 @@ + + + + 4.0.0 + + hugegraph-struct + + + org.apache.hugegraph + hugegraph + ${revision} + ../pom.xml + + + + 17 + 17 + UTF-8 + 25.1-jre + 3.5.1 + + + + + org.apache.hugegraph + hg-pd-client + ${project.version} + + + + jakarta.ws.rs + jakarta.ws.rs-api + 3.0.0 + + + + org.apache.tinkerpop + gremlin-test + ${tinkerpop.version} + + + + com.google.code.gson + gson + 2.8.9 + + + + org.apache.hugegraph + hugegraph-common + ${project.version} + + + org.glassfish.jersey.core + jersey-client + + + + + com.google.guava + guava + ${guava.version} + + + + + + + + org.apache.tinkerpop + gremlin-shaded + 3.5.1 + + + org.mindrot + jbcrypt + 0.4 + + + org.eclipse.collections + eclipse-collections-api + 10.4.0 + + + org.eclipse.collections + eclipse-collections + 10.4.0 + + + it.unimi.dsi + fastutil + 8.1.0 + + + org.lz4 + lz4-java + 1.7.1 + + + org.apache.commons + commons-text + 1.10.0 + + + + org.apdplat + word + 1.3 + + + ch.qos.logback + logback-classic + + + slf4j-api + org.slf4j + + + + + org.ansj + ansj_seg + 5.1.6 + + + com.hankcs + hanlp + portable-1.5.0 + + + org.apache.lucene + lucene-analyzers-smartcn + 7.4.0 + + + org.apache.lucene + lucene-core + 7.4.0 + + + io.jsonwebtoken + jjwt-api + 0.11.2 + + + io.jsonwebtoken + jjwt-impl + 0.11.2 + runtime + + + io.jsonwebtoken + jjwt-jackson + 0.11.2 + runtime + + + com.huaban + jieba-analysis + 1.0.2 + + + org.lionsoul + jcseg-core + 2.2.0 + + + com.chenlb.mmseg4j + mmseg4j-core + 1.10.0 + + + com.janeluo + ikanalyzer + 2012_u6 + + + + + diff --git a/hugegraph-struct/src/main/java/org/apache/hugegraph/HugeGraphSupplier.java b/hugegraph-struct/src/main/java/org/apache/hugegraph/HugeGraphSupplier.java new file mode 100644 index 0000000000..91c747676e --- /dev/null +++ b/hugegraph-struct/src/main/java/org/apache/hugegraph/HugeGraphSupplier.java @@ -0,0 +1,79 @@ +/* + * Copyright 2017 HugeGraph Authors + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph; + +import java.util.Collection; +import java.util.List; + +import org.apache.hugegraph.config.HugeConfig; +import org.apache.hugegraph.util.DateUtil; + +import org.apache.hugegraph.id.Id; +import org.apache.hugegraph.schema.EdgeLabel; +import org.apache.hugegraph.schema.IndexLabel; +import org.apache.hugegraph.schema.PropertyKey; +import org.apache.hugegraph.schema.VertexLabel; + +/** + * Acturally, it would be better if this interface be called + * "HugeGraphSchemaSupplier". + */ +public interface HugeGraphSupplier { + + public List mapPkId2Name(Collection ids); + + public List mapIlId2Name(Collection ids); + + public PropertyKey propertyKey(Id key); + + public Collection propertyKeys(); + + public VertexLabel vertexLabelOrNone(Id id); + + public boolean existsLinkLabel(Id vertexLabel); + + public VertexLabel vertexLabel(Id label); + + public VertexLabel vertexLabel(String label); + + + public default EdgeLabel edgeLabelOrNone(Id id) { + EdgeLabel el = this.edgeLabel(id); + if (el == null) { + el = EdgeLabel.undefined(this, id); + } + return el; + } + public EdgeLabel edgeLabel(Id label); + + public EdgeLabel edgeLabel(String label); + + public IndexLabel indexLabel(Id id); + + public Collection indexLabels(); + + public String name(); + + public HugeConfig configuration(); + + default long now() { + return DateUtil.now().getTime(); + } +} diff --git a/hugegraph-struct/src/main/java/org/apache/hugegraph/SchemaDriver.java b/hugegraph-struct/src/main/java/org/apache/hugegraph/SchemaDriver.java new file mode 100644 index 0000000000..9ce29c1b8b --- /dev/null +++ b/hugegraph-struct/src/main/java/org/apache/hugegraph/SchemaDriver.java @@ -0,0 +1,860 @@ +/* + * Copyright 2017 HugeGraph Authors + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Timer; +import java.util.TimerTask; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Consumer; + +import org.apache.hugegraph.util.E; +import org.apache.hugegraph.util.Log; +import org.apache.tinkerpop.shaded.jackson.core.JsonProcessingException; +import org.apache.tinkerpop.shaded.jackson.databind.ObjectMapper; +import org.slf4j.Logger; + +import org.apache.hugegraph.exception.HugeException; +import org.apache.hugegraph.exception.NotAllowException; +import org.apache.hugegraph.id.Id; +import org.apache.hugegraph.pd.client.KvClient; +import org.apache.hugegraph.pd.client.PDConfig; +import org.apache.hugegraph.pd.common.PDException; +import org.apache.hugegraph.pd.grpc.kv.KResponse; +import org.apache.hugegraph.pd.grpc.kv.ScanPrefixResponse; +import org.apache.hugegraph.pd.grpc.kv.WatchEvent; +import org.apache.hugegraph.pd.grpc.kv.WatchResponse; +import org.apache.hugegraph.pd.grpc.kv.WatchType; +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.VertexLabel; +import org.apache.hugegraph.type.HugeType; + +public class SchemaDriver { + private static Logger log = Log.logger(SchemaDriver.class); + private static final ObjectMapper MAPPER = new ObjectMapper(); + + public static final String DELIMITER = "-"; + public static final String META_PATH_DELIMITER = "/"; + public static final String META_PATH_HUGEGRAPH = "HUGEGRAPH"; + public static final String META_PATH_GRAPHSPACE = "GRAPHSPACE"; + public static final String META_PATH_GRAPH = "GRAPH"; + public static final String META_PATH_CLUSTER = "hg"; + public static final String META_PATH_SCHEMA = "SCHEMA"; + public static final String META_PATH_GRAPH_CONF = "GRAPH_CONF"; + public static final String META_PATH_PROPERTY_KEY = "PROPERTY_KEY"; + public static final String META_PATH_VERTEX_LABEL = "VERTEX_LABEL"; + public static final String META_PATH_EDGE_LABEL = "EDGE_LABEL"; + public static final String META_PATH_INDEX_LABEL = "INDEX_LABEL"; + public static final String META_PATH_NAME = "NAME"; + public static final String META_PATH_ID = "ID"; + public static final String META_PATH_EVENT = "EVENT"; + public static final String META_PATH_REMOVE = "REMOVE"; + public static final String META_PATH_CLEAR = "CLEAR"; + + private static final AtomicReference INSTANCE = + new AtomicReference<>(); + // 用于访问 pd 的 client + private final KvClient client; + + private SchemaCaches caches; + + private SchemaDriver(PDConfig pdConfig, int cacheSize, + long expiration) { + this.client = new KvClient<>(pdConfig); + this.caches = new SchemaCaches(cacheSize, expiration); + this.listenMetaChanges(); + log.info(String.format( + "The SchemaDriver initialized successfully, cacheSize = %s," + + " expiration = %s s", cacheSize, expiration / 1000)); + } + + + public static void init(PDConfig pdConfig) { + init(pdConfig, 300, 300 * 1000); + } + + public static void init(PDConfig pdConfig, int cacheSize, long expiration) { + SchemaDriver instance = INSTANCE.get(); + if (instance != null) { + throw new NotAllowException( + "The SchemaDriver [cacheSize=%s, expiration=%s, " + + "client=%s] has already been initialized and is not " + + "allowed to be initialized again", instance.caches.limit(), + instance.caches.expiration(), instance.client); + } + INSTANCE.compareAndSet(null, new SchemaDriver(pdConfig, cacheSize, + expiration)); + } + + public static void destroy() { + SchemaDriver instance = INSTANCE.get(); + if (instance != null) { + instance.caches.cancelScheduleCacheClean(); + instance.caches.destroyAll(); + INSTANCE.set(null); + } + } + + public SchemaCaches schemaCaches() { + return this.caches; + } + + public static SchemaDriver getInstance() { + return INSTANCE.get(); + } + + private void listenMetaChanges() { + this.listen(graphSpaceRemoveKey(), this::graphSpaceRemoveHandler); + this.listen(graphRemoveKey(), this::graphRemoveHandler); + this.listen(graphClearKey(), this::graphClearHandler); + this.listen(schemaCacheClearKey(), this::schemaCacheClearHandler); + } + + private void schemaCacheClearHandler(T response) { + List names = this.extractValuesFromResponse(response); + for (String gs : names) { + String[] arr = gs.split(DELIMITER); + assert arr.length == 2; + this.caches.clear(arr[0], arr[1]); + log.info(String.format( + "Graph '%s' schema clear event is received, deleting all " + + "schema caches under '%s'", gs, gs)); + } + } + + private void graphClearHandler(T response) { + List names = this.extractValuesFromResponse(response); + for (String gs : names) { + String[] arr = gs.split(DELIMITER); + assert arr.length == 2; + this.caches.clear(arr[0], arr[1]); + log.info(String.format( + "Graph '%s' clear event is received, deleting all " + + "schema caches under '%s'", gs, gs)); + } + } + + private void graphRemoveHandler(T response) { + List names = this.extractValuesFromResponse(response); + for (String gs : names) { + String[] arr = gs.split(DELIMITER); + assert arr.length == 2; + this.caches.destroy(arr[0], arr[1]); + log.info(String.format( + "Graph '%s' delete event is received, deleting all " + + "schema caches under '%s'", gs, gs)); + } + } + + private void graphSpaceRemoveHandler(T response) { + List names = this.extractValuesFromResponse(response); + for (String gs : names) { + this.caches.destroy(gs); + log.info(String.format( + "graph space '%s' delete event is received, deleting all " + + "schema caches under '%s'", gs, gs)); + } + } + + + public List extractValuesFromResponse(T response) { + List values = new ArrayList<>(); + WatchResponse res = (WatchResponse) response; + for (WatchEvent event : res.getEventsList()) { + // Skip if not PUT event + if (!event.getType().equals(WatchType.Put)) { + return null; + } + String value = event.getCurrent().getValue(); + values.add(value); + } + return values; + } + + + public void listen(String key, Consumer consumer) { + try { + this.client.listen(key, (Consumer) consumer); + } catch (PDException e) { + throw new HugeException("Failed to listen '%s' to pd", e, key); + } + } + + public Map graphConfig(String graphSpace, String graph) { + String content = this.get(graphConfKey(graphSpace, graph)); + if (content == null || content.length() == 0) { + return new HashMap<>(); + } else { + return fromJson(content, Map.class); + } + } + + public PropertyKey propertyKey(String graphSpace, String graph, Id id, + HugeGraphSupplier schemaGraph) { + SchemaElement pk = + this.caches.get(graphSpace, graph, HugeType.PROPERTY_KEY, id); + if (pk == null) { + pk = getPropertyKey(graphSpace, graph, id, schemaGraph); + E.checkArgument(pk != null, "no such propertyKey: id = '%s'", id); + this.caches.set(graphSpace, graph, HugeType.PROPERTY_KEY, pk.id(), pk); + this.caches.set(graphSpace, graph, HugeType.PROPERTY_KEY, pk.name(), pk); + } + return (PropertyKey) pk; + } + + public PropertyKey propertyKey(String graphSpace, String graph, + String name, HugeGraphSupplier schemaGraph) { + SchemaElement pk = + this.caches.get(graphSpace, graph, HugeType.PROPERTY_KEY, name); + if (pk == null) { + pk = getPropertyKey(graphSpace, graph, name, schemaGraph); + E.checkArgument(pk != null, "no such propertyKey: name = '%s'", + name); + this.caches.set(graphSpace, graph, HugeType.PROPERTY_KEY, pk.id(), pk); + this.caches.set(graphSpace, graph, HugeType.PROPERTY_KEY, pk.name(), pk); + } + return (PropertyKey) pk; + } + + public List propertyKeys(String graphSpace, String graph, + HugeGraphSupplier schemaGraph) { + Map propertyKeysKvs = + this.scanWithPrefix(propertyKeyPrefix(graphSpace, graph)); + List propertyKeys = + new ArrayList<>(propertyKeysKvs.size()); + for (String value : propertyKeysKvs.values()) { + PropertyKey pk = + PropertyKey.fromMap(fromJson(value, Map.class), schemaGraph); + this.caches.set(graphSpace, graph, HugeType.PROPERTY_KEY, pk.id(), pk); + this.caches.set(graphSpace, graph, HugeType.PROPERTY_KEY, pk.name(), pk); + propertyKeys.add(pk); + } + return propertyKeys; + } + + public List vertexLabels(String graphSpace, String graph, + HugeGraphSupplier schemaGraph) { + Map vertexLabelKvs = this.scanWithPrefix( + vertexLabelPrefix(graphSpace, graph)); + List vertexLabels = + new ArrayList<>(vertexLabelKvs.size()); + for (String value : vertexLabelKvs.values()) { + VertexLabel vl = + VertexLabel.fromMap(fromJson(value, Map.class), + schemaGraph); + this.caches.set(graphSpace, graph, HugeType.VERTEX_LABEL, vl.id(), vl); + this.caches.set(graphSpace, graph, HugeType.VERTEX_LABEL, vl.name(), vl); + vertexLabels.add(vl); + } + return vertexLabels; + } + + public List edgeLabels(String graphSpace, String graph, + HugeGraphSupplier schemaGraph) { + Map edgeLabelKvs = this.scanWithPrefix( + edgeLabelPrefix(graphSpace, graph)); + List edgeLabels = + new ArrayList<>(edgeLabelKvs.size()); + for (String value : edgeLabelKvs.values()) { + EdgeLabel el = + EdgeLabel.fromMap(fromJson(value, Map.class), schemaGraph); + this.caches.set(graphSpace, graph, HugeType.EDGE_LABEL, el.id(), el); + this.caches.set(graphSpace, graph, HugeType.EDGE_LABEL, el.name(), el); + edgeLabels.add(el); + } + return edgeLabels; + } + + public List indexLabels(String graphSpace, String graph, + HugeGraphSupplier schemaGraph) { + Map indexLabelKvs = this.scanWithPrefix( + indexLabelPrefix(graphSpace, graph)); + List indexLabels = + new ArrayList<>(indexLabelKvs.size()); + for (String value : indexLabelKvs.values()) { + IndexLabel il = + IndexLabel.fromMap(fromJson(value, Map.class), schemaGraph); + this.caches.set(graphSpace, graph, HugeType.INDEX_LABEL, il.id(), il); + this.caches.set(graphSpace, graph, HugeType.INDEX_LABEL, il.name(), il); + indexLabels.add(il); + } + return indexLabels; + } + + private String propertyKeyPrefix(String graphSpace, String graph) { + // HUGEGRAPH/{cluster}/GRAPHSPACE/{graphspace}/GRAPH/{graph + // }/SCHEMA/PROPERTY_KEY/NAME + return stringJoin(META_PATH_DELIMITER, + META_PATH_HUGEGRAPH, + META_PATH_CLUSTER, + META_PATH_GRAPHSPACE, + graphSpace, + graph, + META_PATH_SCHEMA, + META_PATH_PROPERTY_KEY, + META_PATH_NAME); + } + + private String vertexLabelPrefix(String graphSpace, String graph) { + // HUGEGRAPH/{cluster}/GRAPHSPACE/{graphspace}/GRAPH/{graph + // }/SCHEMA/VERTEX_LABEL/NAME + return stringJoin(META_PATH_DELIMITER, + META_PATH_HUGEGRAPH, + META_PATH_CLUSTER, + META_PATH_GRAPHSPACE, + graphSpace, + graph, + META_PATH_SCHEMA, + META_PATH_VERTEX_LABEL, + META_PATH_NAME); + } + + private String edgeLabelPrefix(String graphSpace, String graph) { + // HUGEGRAPH/{cluster}/GRAPHSPACE/{graphspace}/GRAPH/{graph + // }/SCHEMA/EDGELABEL/NAME + return stringJoin(META_PATH_DELIMITER, + META_PATH_HUGEGRAPH, + META_PATH_CLUSTER, + META_PATH_GRAPHSPACE, + graphSpace, + graph, + META_PATH_SCHEMA, + META_PATH_EDGE_LABEL, + META_PATH_NAME); + } + + private String indexLabelPrefix(String graphSpace, String graph) { + // HUGEGRAPH/{cluster}/GRAPHSPACE/{graphspace}/GRAPH/{graph + // }/SCHEMA/INDEX_LABEL/NAME + return stringJoin(META_PATH_DELIMITER, + META_PATH_HUGEGRAPH, + META_PATH_CLUSTER, + META_PATH_GRAPHSPACE, + graphSpace, + graph, + META_PATH_SCHEMA, + META_PATH_INDEX_LABEL, + META_PATH_NAME); + } + + public VertexLabel vertexLabel(String graphSpace, String graph, Id id, + HugeGraphSupplier schemaGraph) { + SchemaElement vl = + this.caches.get(graphSpace, graph, HugeType.VERTEX_LABEL, id); + if (vl == null) { + vl = getVertexLabel(graphSpace, graph, id, schemaGraph); + E.checkArgument(vl != null, "no such vertex label: id = '%s'", id); + this.caches.set(graphSpace, graph, HugeType.VERTEX_LABEL, vl.id(), vl); + this.caches.set(graphSpace, graph, HugeType.VERTEX_LABEL, vl.name(), vl); + } + return (VertexLabel) vl; + } + + public VertexLabel vertexLabel(String graphSpace, String graph, + String name, HugeGraphSupplier schemaGraph) { + SchemaElement vl = + this.caches.get(graphSpace, graph, HugeType.VERTEX_LABEL, name); + if (vl == null) { + vl = getVertexLabel(graphSpace, graph, name, schemaGraph); + E.checkArgument(vl != null, "no such vertex label: name = '%s'", + name); + this.caches.set(graphSpace, graph, HugeType.VERTEX_LABEL, vl.id(), vl); + this.caches.set(graphSpace, graph, HugeType.VERTEX_LABEL, vl.name(), vl); + } + return (VertexLabel) vl; + } + + public EdgeLabel edgeLabel(String graphSpace, String graph, Id id, + HugeGraphSupplier schemaGraph) { + SchemaElement el = + this.caches.get(graphSpace, graph, HugeType.EDGE_LABEL, id); + if (el == null) { + el = getEdgeLabel(graphSpace, graph, id, schemaGraph); + E.checkArgument(el != null, "no such edge label: id = '%s'", id); + this.caches.set(graphSpace, graph, HugeType.EDGE_LABEL, el.id(), el); + this.caches.set(graphSpace, graph, HugeType.EDGE_LABEL, el.name(), el); + } + return (EdgeLabel) el; + } + + public EdgeLabel edgeLabel(String graphSpace, String graph, String name, + HugeGraphSupplier schemaGraph) { + SchemaElement el = + this.caches.get(graphSpace, graph, HugeType.EDGE_LABEL, name); + if (el == null) { + el = getEdgeLabel(graphSpace, graph, name, schemaGraph); + E.checkArgument(el != null, "no such edge label: name = '%s'", + name); + this.caches.set(graphSpace, graph, HugeType.EDGE_LABEL, el.id(), el); + this.caches.set(graphSpace, graph, HugeType.EDGE_LABEL, el.name(), el); + } + return (EdgeLabel) el; + } + + public IndexLabel indexLabel(String graphSpace, String graph, Id id, + HugeGraphSupplier schemaGraph) { + SchemaElement il = + this.caches.get(graphSpace, graph, HugeType.INDEX_LABEL, id); + if (il == null) { + il = getIndexLabel(graphSpace, graph, id, schemaGraph); + E.checkArgument(il != null, "no such index label: id = '%s'", id); + this.caches.set(graphSpace, graph, HugeType.INDEX_LABEL, il.id(), il); + this.caches.set(graphSpace, graph, HugeType.INDEX_LABEL, il.name(), il); + } + return (IndexLabel) il; + } + + public IndexLabel indexLabel(String graphSpace, String graph, String name, + HugeGraphSupplier schemaGraph) { + SchemaElement il = + this.caches.get(graphSpace, graph, HugeType.INDEX_LABEL, name); + if (il == null) { + il = getIndexLabel(graphSpace, graph, name, schemaGraph); + E.checkArgument(il != null, "no such index label: name = '%s'", + name); + this.caches.set(graphSpace, graph, HugeType.INDEX_LABEL, il.id(), il); + this.caches.set(graphSpace, graph, HugeType.INDEX_LABEL, il.name(), il); + } + return (IndexLabel) il; + } + + private String get(String key) { + try { + KResponse response = this.client.get(key); + return response.getValue(); + } catch (PDException e) { + throw new HugeException("Failed to get '%s' from pd", e, key); + } + } + + private Map scanWithPrefix(String prefix) { + try { + ScanPrefixResponse response = this.client.scanPrefix(prefix); + return response.getKvsMap(); + } catch (PDException e) { + throw new HugeException("Failed to scanWithPrefix '%s' from pd", e, prefix); + } + } + + private PropertyKey getPropertyKey(String graphSpace, String graph, + Id propertyKey, HugeGraphSupplier schemaGraph) { + String content = + this.get(propertyKeyIdKey(graphSpace, graph, propertyKey)); + if (content == null || content.length() == 0) { + return null; + } else { + return PropertyKey.fromMap(fromJson(content, Map.class), schemaGraph); + } + } + + private PropertyKey getPropertyKey(String graphSpace, String graph, + String propertyKey, HugeGraphSupplier schemaGraph) { + String content = + this.get(propertyKeyNameKey(graphSpace, graph, propertyKey)); + if (content == null || content.length() == 0) { + return null; + } else { + return PropertyKey.fromMap(fromJson(content, Map.class), schemaGraph); + } + } + + private VertexLabel getVertexLabel(String graphSpace, String graph, + Id vertexLabel, HugeGraphSupplier schemaGraph) { + String content = + this.get(vertexLabelIdKey(graphSpace, graph, vertexLabel)); + if (content == null || content.length() == 0) { + return null; + } else { + return VertexLabel.fromMap(fromJson(content, Map.class), schemaGraph); + } + } + + private VertexLabel getVertexLabel(String graphSpace, String graph, + String vertexLabel, HugeGraphSupplier schemaGraph) { + String content = + this.get(vertexLabelNameKey(graphSpace, graph, vertexLabel)); + if (content == null || content.length() == 0) { + return null; + } else { + return VertexLabel.fromMap(fromJson(content, Map.class), schemaGraph); + } + } + + private EdgeLabel getEdgeLabel(String graphSpace, String graph, + Id edgeLabel, HugeGraphSupplier schemaGraph) { + String content = + this.get(edgeLabelIdKey(graphSpace, graph, edgeLabel)); + if (content == null || content.length() == 0) { + return null; + } else { + return EdgeLabel.fromMap(fromJson(content, Map.class), schemaGraph); + } + } + + private EdgeLabel getEdgeLabel(String graphSpace, String graph, + String edgeLabel, HugeGraphSupplier schemaGraph) { + String content = + this.get(edgeLabelNameKey(graphSpace, graph, edgeLabel)); + if (content == null || content.length() == 0) { + return null; + } else { + return EdgeLabel.fromMap(fromJson(content, Map.class), schemaGraph); + } + } + + + private IndexLabel getIndexLabel(String graphSpace, String graph, + Id indexLabel, HugeGraphSupplier schemaGraph) { + String content = + this.get(indexLabelIdKey(graphSpace, graph, indexLabel)); + if (content == null || content.length() == 0) { + return null; + } else { + return IndexLabel.fromMap(fromJson(content, Map.class), schemaGraph); + } + } + + private IndexLabel getIndexLabel(String graphSpace, String graph, + String indexLabel, + HugeGraphSupplier schemaGraph) { + String content = + this.get(indexLabelNameKey(graphSpace, graph, indexLabel)); + if (content == null || content.length() == 0) { + return null; + } else { + return IndexLabel.fromMap(fromJson(content, Map.class), + schemaGraph); + } + } + + + private T fromJson(String json, Class clazz) { + E.checkState(json != null, "Json value can't be null for '%s'", + clazz.getSimpleName()); + try { + return MAPPER.readValue(json, clazz); + } catch (IOException e) { + throw new HugeException("Can't read json: %s", e, e.getMessage()); + } + } + + private String toJson(Object object) { + try { + return MAPPER.writeValueAsString(object); + } catch (JsonProcessingException e) { + throw new HugeException("Can't write json: %s", e, e.getMessage()); + } + } + + private String propertyKeyIdKey(String graphSpace, String graph, Id id) { + return idKey(graphSpace, graph, id, HugeType.PROPERTY_KEY); + } + + private String propertyKeyNameKey(String graphSpace, String graph, + String name) { + return nameKey(graphSpace, graph, name, HugeType.PROPERTY_KEY); + } + + + private String vertexLabelIdKey(String graphSpace, String graph, Id id) { + return idKey(graphSpace, graph, id, HugeType.VERTEX_LABEL); + } + + private String vertexLabelNameKey(String graphSpace, String graph, + String name) { + return nameKey(graphSpace, graph, name, HugeType.VERTEX_LABEL); + } + + private String edgeLabelIdKey(String graphSpace, String graph, Id id) { + return idKey(graphSpace, graph, id, HugeType.EDGE_LABEL); + } + + private String edgeLabelNameKey(String graphSpace, String graph, + String name) { + return nameKey(graphSpace, graph, name, HugeType.EDGE_LABEL); + } + + private String indexLabelIdKey(String graphSpace, String graph, Id id) { + return idKey(graphSpace, graph, id, HugeType.INDEX_LABEL); + } + + private String indexLabelNameKey(String graphSpace, String graph, + String name) { + return nameKey(graphSpace, graph, name, HugeType.INDEX_LABEL); + } + + private String graphSpaceRemoveKey() { + // HUGEGRAPH/{cluster}/EVENT/GRAPHSPACE/REMOVE + return stringJoin(META_PATH_DELIMITER, + META_PATH_HUGEGRAPH, + META_PATH_CLUSTER, + META_PATH_EVENT, + META_PATH_GRAPHSPACE, + META_PATH_REMOVE); + } + + private String graphConfKey(String graphSpace, String graph) { + // HUGEGRAPH/{cluster}/GRAPHSPACE/{graphspace}/GRAPH_CONF/{graph} + return stringJoin(META_PATH_DELIMITER, + META_PATH_HUGEGRAPH, + META_PATH_CLUSTER, + META_PATH_GRAPHSPACE, + graphSpace, + META_PATH_GRAPH_CONF, + graph); + } + + private String nameKey(String graphSpace, String graph, + String name, HugeType type) { + // HUGEGRAPH/hg/GRAPHSPACE/{graphspace}/{graph}/SCHEMA + // /{META_PATH_TYPE}/NAME/{name} + return stringJoin(META_PATH_DELIMITER, + META_PATH_HUGEGRAPH, + META_PATH_CLUSTER, + META_PATH_GRAPHSPACE, + graphSpace, + graph, + META_PATH_SCHEMA, + hugeType2MetaPath(type), + META_PATH_NAME, + name); + } + + private String idKey(String graphSpace, String graph, + Id id, HugeType type) { + // HUGEGRAPH/hg/GRAPHSPACE/{graphspace}/{graph}/SCHEMA + // /{META_PATH_TYPE}/ID/{id} + return stringJoin(META_PATH_DELIMITER, + META_PATH_HUGEGRAPH, + META_PATH_CLUSTER, + META_PATH_GRAPHSPACE, + graphSpace, + graph, + META_PATH_SCHEMA, + hugeType2MetaPath(type), + META_PATH_ID, + id.asString()); + } + + private String schemaCacheClearKey() { + // HUGEGRAPH/{cluster}/EVENT/GRAPH/SCHEMA/CLEAR + return stringJoin(META_PATH_DELIMITER, + META_PATH_HUGEGRAPH, + META_PATH_CLUSTER, + META_PATH_EVENT, + META_PATH_GRAPH, + META_PATH_SCHEMA, + META_PATH_CLEAR); + } + + private String graphClearKey() { + // HUGEGRAPH/{cluster}/EVENT/GRAPH/CLEAR + return stringJoin(META_PATH_DELIMITER, + META_PATH_HUGEGRAPH, + META_PATH_CLUSTER, + META_PATH_EVENT, + META_PATH_GRAPH, + META_PATH_CLEAR); + } + + private String graphRemoveKey() { + // HUGEGRAPH/{cluster}/EVENT/GRAPH/REMOVE + return stringJoin(META_PATH_DELIMITER, + META_PATH_HUGEGRAPH, + META_PATH_CLUSTER, + META_PATH_EVENT, + META_PATH_GRAPH, + META_PATH_REMOVE); + } + + private String hugeType2MetaPath(HugeType type) { + String schemaType = null; + switch (type) { + case PROPERTY_KEY: + schemaType = META_PATH_PROPERTY_KEY; + break; + case VERTEX_LABEL: + schemaType = META_PATH_VERTEX_LABEL; + break; + case EDGE_LABEL: + schemaType = META_PATH_EDGE_LABEL; + break; + case INDEX_LABEL: + schemaType = META_PATH_INDEX_LABEL; + break; + default: + throw new AssertionError(String.format( + "Invalid HugeType : %s", type)); + } + return schemaType; + } + + private static String stringJoin(String delimiter, String... parts) { + StringBuilder builder = new StringBuilder(); + int size = parts.length; + for (int i = 0; i < size; i++) { + builder.append(parts[i]); + if (i < size - 1) { + builder.append(delimiter); + } + } + return builder.toString(); + } + + private static final class SchemaCaches { + private final int limit; + private final long expiration; + private final Timer timer; + + private ConcurrentHashMap> caches; + + public SchemaCaches(int limit, long expiration) { + this.expiration = expiration; + this.limit = limit; + this.timer = new Timer(); + this.caches = new ConcurrentHashMap<>(); + scheduleCacheCleanup(); + } + + public int limit() { + return this.limit; + } + + public long expiration() { + return this.expiration; + } + + private void scheduleCacheCleanup() { + timer.scheduleAtFixedRate(new TimerTask() { + @Override + public void run() { + log.debug("schedule clear schema caches"); + clearAll(); + } + }, expiration, expiration); + } + + public void cancelScheduleCacheClean() { + timer.cancel(); + } + + public SchemaElement get(String graphSpace, String graph, HugeType type, + Id id) { + return get(graphSpace, graph, type, id.asString()); + } + + public SchemaElement get(String graphSpace, String graph, HugeType type, + String name) { + String graphName = stringJoin(DELIMITER, graphSpace, graph); + if (this.caches.get(graphName) == null) { + this.caches.put(graphName, new ConcurrentHashMap<>(this.limit)); + } + return this.caches.get(graphName) + .get(stringJoin(DELIMITER, type.string(), name)); + } + + public void set(String graphSpace, String graph, HugeType type, Id id, + SchemaElement value) { + set(graphSpace, graph, type, id.asString(), value); + } + + public void set(String graphSpace, String graph, HugeType type, + String name, SchemaElement value) { + String graphName = stringJoin(DELIMITER, graphSpace, graph); + ConcurrentHashMap + schemaCaches = this.caches.get(graphName); + if (schemaCaches == null) { + schemaCaches = this.caches.put(graphName, new ConcurrentHashMap<>(this.limit)); + } + if (schemaCaches.size() >= limit) { + log.info(String.format( + "The current '%s''s schemaCaches size '%s' reached " + + "limit '%s'", graphName, schemaCaches.size(), limit)); + return; + } + schemaCaches.put(stringJoin(DELIMITER, type.string(), name), + value); + log.debug(String.format("graph '%s' add schema caches '%s'", + graphName, + stringJoin(DELIMITER, type.string(), + name))); + } + + public void remove(String graphSpace, String graph, HugeType type, + Id id) { + remove(graphSpace, graph, type, id.asString()); + } + + public void remove(String graphSpace, String graph, HugeType type, + String name) { + String graphName = stringJoin(DELIMITER, graphSpace, graph); + + ConcurrentHashMap + schemaCaches = this.caches.get(graphName); + schemaCaches.remove(stringJoin(DELIMITER, type.string(), name)); + + } + + public void clearAll() { + for (String key : this.caches.keySet()) { + log.debug(String.format("graph in '%s' schema caches clear", + key)); + this.caches.get(key).clear(); + } + } + + public void clear(String graphSpace, String graph) { + ConcurrentHashMap + schemaCaches = + this.caches.get(stringJoin(DELIMITER, graphSpace, graph)); + if (schemaCaches != null) { + schemaCaches.clear(); + } + } + + public void destroyAll() { + this.caches.clear(); + } + + public void destroy(String graphSpace, String graph) { + this.caches.remove(stringJoin(DELIMITER, graphSpace, graph)); + + } + + public void destroy(String graphSpace) { + for (String key : this.caches.keySet()) { + String gs = key.split(DELIMITER)[0]; + if (gs.equals(graphSpace)) { + this.caches.remove(key); + } + } + } + + } +} diff --git a/hugegraph-struct/src/main/java/org/apache/hugegraph/SchemaGraph.java b/hugegraph-struct/src/main/java/org/apache/hugegraph/SchemaGraph.java new file mode 100644 index 0000000000..f20c0d17fb --- /dev/null +++ b/hugegraph-struct/src/main/java/org/apache/hugegraph/SchemaGraph.java @@ -0,0 +1,182 @@ +/* + * Copyright 2017 HugeGraph Authors + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph; + +import org.apache.hugegraph.HugeGraphSupplier; +import org.apache.hugegraph.SchemaDriver; +import org.apache.hugegraph.id.Id; +import org.apache.hugegraph.pd.client.PDConfig; +import org.apache.hugegraph.schema.*; + +import org.apache.commons.configuration2.Configuration; +import org.apache.commons.configuration2.MapConfiguration; +import org.apache.hugegraph.config.HugeConfig; +import org.apache.hugegraph.util.E; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Map; + +public class SchemaGraph implements HugeGraphSupplier { + + private final String graphSpace; + private final String graph; + private final PDConfig pdConfig; + private HugeConfig config; + + private final SchemaDriver schemaDriver; + + public SchemaGraph(String graphSpace, String graph, PDConfig pdConfig) { + this.graphSpace = graphSpace; + this.graph = graph; + this.pdConfig = pdConfig; + this.schemaDriver = schemaDriverInit(); + this.config = this.loadConfig(); + } + + private SchemaDriver schemaDriverInit() { + if (SchemaDriver.getInstance() == null) { + synchronized (SchemaDriver.class) { + if (SchemaDriver.getInstance() == null) { + SchemaDriver.init(this.pdConfig); + } + } + } + return SchemaDriver.getInstance(); + } + + private HugeConfig loadConfig() { + // 加载 PD 中的配置 + Map configs = + schemaDriver.graphConfig(this.graphSpace, this.graph); + Configuration propConfig = new MapConfiguration(configs); + return new HugeConfig(propConfig); + } + + @Override + public List mapPkId2Name(Collection ids) { + List names = new ArrayList<>(ids.size()); + for (Id id : ids) { + SchemaElement schema = this.propertyKey(id); + names.add(schema.name()); + } + return names; + } + + @Override + public List mapIlId2Name(Collection ids) { + List names = new ArrayList<>(ids.size()); + for (Id id : ids) { + SchemaElement schema = this.indexLabel(id); + names.add(schema.name()); + } + return names; + } + + @Override + public HugeConfig configuration(){ + return this.config; + } + + @Override + public PropertyKey propertyKey(Id id) { + return schemaDriver.propertyKey(this.graphSpace, this.graph, id, this); + } + + public PropertyKey propertyKey(String name) { + return schemaDriver.propertyKey(this.graphSpace, this.graph, name, this); + } + + @Override + public Collection propertyKeys() { + // TODO + return null; + } + + @Override + public VertexLabel vertexLabelOrNone(Id id) { + VertexLabel vl = vertexLabel(id); + if (vl == null) { + vl = VertexLabel.undefined(null, id); + } + return vl; + } + + @Override + public boolean existsLinkLabel(Id vertexLabel) { + List edgeLabels = + schemaDriver.edgeLabels(this.graphSpace, this.graph, this); + for (EdgeLabel edgeLabel : edgeLabels) { + if (edgeLabel.linkWithLabel(vertexLabel)) { + return true; + } + } + return false; + } + + @Override + public VertexLabel vertexLabel(Id id) { + E.checkArgumentNotNull(id, "Vertex label id can't be null"); + if (SchemaElement.OLAP_ID.equals(id)) { + return VertexLabel.OLAP_VL; + } + return schemaDriver.vertexLabel(this.graphSpace, this.graph, id, this); + } + + @Override + public VertexLabel vertexLabel(String name) { + E.checkArgumentNotNull(name, "Vertex label name can't be null"); + E.checkArgument(!name.isEmpty(), "Vertex label name can't be empty"); + if (SchemaElement.OLAP.equals(name)) { + return VertexLabel.OLAP_VL; + } + return schemaDriver.vertexLabel(this.graphSpace, this.graph, name, this); + } + + @Override + public EdgeLabel edgeLabel(Id id) { + return schemaDriver.edgeLabel(this.graphSpace, this.graph, id, this); + } + + @Override + public EdgeLabel edgeLabel(String name) { + return schemaDriver.edgeLabel(this.graphSpace, this.graph, name, this); + } + + @Override + public IndexLabel indexLabel(Id id) { + return schemaDriver.indexLabel(this.graphSpace, this.graph, id, this); + } + + @Override + public Collection indexLabels() { + return schemaDriver.indexLabels(this.graphSpace, this.graph, this); + } + + public IndexLabel indexLabel(String name) { + return schemaDriver.indexLabel(this.graphSpace, this.graph, name, this); + } + + @Override + public String name() { + return String.join("-", this.graphSpace, this.graph); + } +} diff --git a/hugegraph-struct/src/main/java/org/apache/hugegraph/analyzer/Analyzer.java b/hugegraph-struct/src/main/java/org/apache/hugegraph/analyzer/Analyzer.java new file mode 100644 index 0000000000..4edd2ffa9b --- /dev/null +++ b/hugegraph-struct/src/main/java/org/apache/hugegraph/analyzer/Analyzer.java @@ -0,0 +1,27 @@ +/* + * Copyright 2017 HugeGraph Authors + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.analyzer; + +import java.util.Set; + +public interface Analyzer { + + public Set segment(String text); +} diff --git a/hugegraph-struct/src/main/java/org/apache/hugegraph/analyzer/AnalyzerFactory.java b/hugegraph-struct/src/main/java/org/apache/hugegraph/analyzer/AnalyzerFactory.java new file mode 100644 index 0000000000..bff18ab7b0 --- /dev/null +++ b/hugegraph-struct/src/main/java/org/apache/hugegraph/analyzer/AnalyzerFactory.java @@ -0,0 +1,102 @@ +/* + * Copyright 2017 HugeGraph Authors + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.analyzer; + + +import org.apache.hugegraph.exception.HugeException; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +public class AnalyzerFactory { + + private static Map> analyzers; + + static { + analyzers = new ConcurrentHashMap<>(); + } + + public static Analyzer analyzer(String name, String mode) { + name = name.toLowerCase(); + switch (name) { + case "word": + return new WordAnalyzer(mode); + case "ansj": + return new AnsjAnalyzer(mode); + case "hanlp": + return new HanLPAnalyzer(mode); + case "smartcn": + return new SmartCNAnalyzer(mode); + case "jieba": + return new JiebaAnalyzer(mode); + case "jcseg": + return new JcsegAnalyzer(mode); + case "mmseg4j": + return new MMSeg4JAnalyzer(mode); + case "ikanalyzer": + return new IKAnalyzer(mode); + default: + return customizedAnalyzer(name, mode); + } + } + + private static Analyzer customizedAnalyzer(String name, String mode) { + Class clazz = analyzers.get(name); + if (clazz == null) { + throw new HugeException("Not exists analyzer: %s", name); + } + + assert Analyzer.class.isAssignableFrom(clazz); + try { + return clazz.getConstructor(String.class).newInstance(mode); + } catch (Exception e) { + throw new HugeException( + "Failed to construct analyzer '%s' with mode '%s'", + e, name, mode); + } + } + + @SuppressWarnings({ "rawtypes", "unchecked" }) + public static void register(String name, String classPath) { + ClassLoader classLoader = AnalyzerFactory.class.getClassLoader(); + Class clazz; + try { + clazz = classLoader.loadClass(classPath); + } catch (Exception e) { + throw new HugeException("Load class path '%s' failed", + e, classPath); + } + + // Check subclass + if (!Analyzer.class.isAssignableFrom(clazz)) { + throw new HugeException("Class '%s' is not a subclass of " + + "class Analyzer", classPath); + } + + // Check exists + if (analyzers.containsKey(name)) { + throw new HugeException("Exists analyzer: %s(%s)", + name, analyzers.get(name).getName()); + } + + // Register class + analyzers.put(name, (Class) clazz); + } +} diff --git a/hugegraph-struct/src/main/java/org/apache/hugegraph/analyzer/AnsjAnalyzer.java b/hugegraph-struct/src/main/java/org/apache/hugegraph/analyzer/AnsjAnalyzer.java new file mode 100644 index 0000000000..3f041d31f8 --- /dev/null +++ b/hugegraph-struct/src/main/java/org/apache/hugegraph/analyzer/AnsjAnalyzer.java @@ -0,0 +1,87 @@ +/* + * Copyright 2017 HugeGraph Authors + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.analyzer; + +import java.util.List; +import java.util.Set; + +import org.ansj.domain.Result; +import org.ansj.domain.Term; +import org.ansj.splitWord.analysis.BaseAnalysis; +import org.ansj.splitWord.analysis.IndexAnalysis; +import org.ansj.splitWord.analysis.NlpAnalysis; +import org.ansj.splitWord.analysis.ToAnalysis; +import org.apache.hugegraph.config.ConfigException; +import org.apache.hugegraph.util.InsertionOrderUtil; + +import com.google.common.collect.ImmutableList; + +/** + * Reference from https://my.oschina.net/apdplat/blog/412921 + */ +public class AnsjAnalyzer implements Analyzer { + + public static final List SUPPORT_MODES = ImmutableList.of( + "BaseAnalysis", + "IndexAnalysis", + "ToAnalysis", + "NlpAnalysis" + ); + + private String analysis; + + public AnsjAnalyzer(String mode) { + if (!SUPPORT_MODES.contains(mode)) { + throw new ConfigException( + "Unsupported segment mode '%s' for ansj analyzer, " + + "the available values are %s", mode, SUPPORT_MODES); + } + this.analysis = mode; + } + + @Override + public Set segment(String text) { + Result terms = null; + switch (this.analysis) { + case "BaseAnalysis": + terms = BaseAnalysis.parse(text); + break; + case "ToAnalysis": + terms = ToAnalysis.parse(text); + break; + case "NlpAnalysis": + terms = NlpAnalysis.parse(text); + break; + case "IndexAnalysis": + terms = IndexAnalysis.parse(text); + break; + default: + throw new AssertionError(String.format( + "Unsupported segment mode '%s'", this.analysis)); + } + + assert terms != null; + Set result = InsertionOrderUtil.newSet(); + for (Term term : terms) { + result.add(term.getName()); + } + return result; + } +} diff --git a/hugegraph-struct/src/main/java/org/apache/hugegraph/analyzer/HanLPAnalyzer.java b/hugegraph-struct/src/main/java/org/apache/hugegraph/analyzer/HanLPAnalyzer.java new file mode 100644 index 0000000000..b8175e400c --- /dev/null +++ b/hugegraph-struct/src/main/java/org/apache/hugegraph/analyzer/HanLPAnalyzer.java @@ -0,0 +1,108 @@ +/* + * Copyright 2017 HugeGraph Authors + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.analyzer; + +import java.util.List; +import java.util.Set; + + +import com.google.common.collect.ImmutableList; +import com.hankcs.hanlp.seg.Dijkstra.DijkstraSegment; +import com.hankcs.hanlp.seg.NShort.NShortSegment; +import com.hankcs.hanlp.seg.Segment; +import com.hankcs.hanlp.seg.common.Term; +import com.hankcs.hanlp.tokenizer.IndexTokenizer; +import com.hankcs.hanlp.tokenizer.NLPTokenizer; +import com.hankcs.hanlp.tokenizer.SpeedTokenizer; +import com.hankcs.hanlp.tokenizer.StandardTokenizer; + +import org.apache.hugegraph.config.ConfigException; +import org.apache.hugegraph.util.InsertionOrderUtil; + +/** + * Reference from https://my.oschina.net/apdplat/blog/412921 + */ +public class HanLPAnalyzer implements Analyzer { + + public static final List SUPPORT_MODES = + ImmutableList.builder() + .add("standard") + .add("nlp") + .add("index") + .add("nShort") + .add("shortest") + .add("speed") + .build(); + + private static final Segment N_SHORT_SEGMENT = + new NShortSegment().enableCustomDictionary(false) + .enablePlaceRecognize(true) + .enableOrganizationRecognize(true); + private static final Segment DIJKSTRA_SEGMENT = + new DijkstraSegment().enableCustomDictionary(false) + .enablePlaceRecognize(true) + .enableOrganizationRecognize(true); + + private String tokenizer; + + public HanLPAnalyzer(String mode) { + if (!SUPPORT_MODES.contains(mode)) { + throw new ConfigException( + "Unsupported segment mode '%s' for hanlp analyzer, " + + "the available values are %s", mode, SUPPORT_MODES); + } + this.tokenizer = mode; + } + + @Override + public Set segment(String text) { + List terms = null; + switch (this.tokenizer) { + case "standard": + terms = StandardTokenizer.segment(text); + break; + case "nlp": + terms = NLPTokenizer.segment(text); + break; + case "index": + terms = IndexTokenizer.segment(text); + break; + case "nShort": + terms = N_SHORT_SEGMENT.seg(text); + break; + case "shortest": + terms = DIJKSTRA_SEGMENT.seg(text); + break; + case "speed": + terms = SpeedTokenizer.segment(text); + break; + default: + throw new AssertionError(String.format( + "Unsupported segment mode '%s'", this.tokenizer)); + } + + assert terms != null; + Set result = InsertionOrderUtil.newSet(); + for (Term term : terms) { + result.add(term.word); + } + return result; + } +} diff --git a/hugegraph-struct/src/main/java/org/apache/hugegraph/analyzer/IKAnalyzer.java b/hugegraph-struct/src/main/java/org/apache/hugegraph/analyzer/IKAnalyzer.java new file mode 100644 index 0000000000..a938e8e01f --- /dev/null +++ b/hugegraph-struct/src/main/java/org/apache/hugegraph/analyzer/IKAnalyzer.java @@ -0,0 +1,73 @@ +/* + * Copyright 2017 HugeGraph Authors + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.analyzer; + +import com.google.common.collect.ImmutableList; + +import org.apache.hugegraph.config.ConfigException; +import org.apache.hugegraph.exception.HugeException; +import org.apache.hugegraph.util.InsertionOrderUtil; +import org.wltea.analyzer.core.IKSegmenter; +import org.wltea.analyzer.core.Lexeme; + +import java.io.StringReader; +import java.util.List; +import java.util.Set; + +/** + * Reference from https://my.oschina.net/apdplat/blog/412921 + */ +public class IKAnalyzer implements Analyzer { + + public static final List SUPPORT_MODES = ImmutableList.of( + "smart", + "max_word" + ); + + private boolean smartSegMode; + private final IKSegmenter ik; + + public IKAnalyzer(String mode) { + if (!SUPPORT_MODES.contains(mode)) { + throw new ConfigException( + "Unsupported segment mode '%s' for ikanalyzer, " + + "the available values are %s", mode, SUPPORT_MODES); + } + this.smartSegMode = SUPPORT_MODES.get(0).equals(mode); + this.ik = new IKSegmenter(new StringReader(""), + this.smartSegMode); + } + + @Override + public Set segment(String text) { + Set result = InsertionOrderUtil.newSet(); + ik.reset(new StringReader(text)); + try { + Lexeme word = null; + while ((word = ik.next()) != null) { + result.add(word.getLexemeText()); + } + } catch (Exception e) { + throw new HugeException("IKAnalyzer segment text '%s' failed", + e, text); + } + return result; + } +} diff --git a/hugegraph-struct/src/main/java/org/apache/hugegraph/analyzer/JcsegAnalyzer.java b/hugegraph-struct/src/main/java/org/apache/hugegraph/analyzer/JcsegAnalyzer.java new file mode 100644 index 0000000000..0a69af8384 --- /dev/null +++ b/hugegraph-struct/src/main/java/org/apache/hugegraph/analyzer/JcsegAnalyzer.java @@ -0,0 +1,77 @@ +/* + * Copyright 2017 HugeGraph Authors + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.analyzer; + +import java.io.StringReader; +import java.util.List; +import java.util.Set; + +import org.apache.hugegraph.config.ConfigException; +import org.apache.hugegraph.exception.HugeException; +import org.apache.hugegraph.util.InsertionOrderUtil; +import org.lionsoul.jcseg.tokenizer.core.ADictionary; +import org.lionsoul.jcseg.tokenizer.core.DictionaryFactory; +import org.lionsoul.jcseg.tokenizer.core.ISegment; +import org.lionsoul.jcseg.tokenizer.core.IWord; +import org.lionsoul.jcseg.tokenizer.core.JcsegTaskConfig; +import org.lionsoul.jcseg.tokenizer.core.SegmentFactory; +import com.google.common.collect.ImmutableList; + +/** + * Reference from https://my.oschina.net/apdplat/blog/412921 + */ +public class JcsegAnalyzer implements Analyzer { + + public static final List SUPPORT_MODES = ImmutableList.of( + "Simple", + "Complex" + ); + + private static final JcsegTaskConfig CONFIG = new JcsegTaskConfig(); + private static final ADictionary DIC = + DictionaryFactory.createDefaultDictionary(new JcsegTaskConfig()); + + private int segMode; + + public JcsegAnalyzer(String mode) { + if (!SUPPORT_MODES.contains(mode)) { + throw new ConfigException( + "Unsupported segment mode '%s' for jcseg analyzer, " + + "the available values are %s", mode, SUPPORT_MODES); + } + this.segMode = SUPPORT_MODES.indexOf(mode) + 1; + } + + @Override + public Set segment(String text) { + Set result = InsertionOrderUtil.newSet(); + try { + Object[] args = new Object[]{new StringReader(text), CONFIG, DIC}; + ISegment seg = SegmentFactory.createJcseg(this.segMode, args); + IWord word = null; + while ((word = seg.next()) != null) { + result.add(word.getValue()); + } + } catch (Exception e) { + throw new HugeException("Jcseg segment text '%s' failed", e, text); + } + return result; + } +} diff --git a/hugegraph-struct/src/main/java/org/apache/hugegraph/analyzer/JiebaAnalyzer.java b/hugegraph-struct/src/main/java/org/apache/hugegraph/analyzer/JiebaAnalyzer.java new file mode 100644 index 0000000000..70cae33268 --- /dev/null +++ b/hugegraph-struct/src/main/java/org/apache/hugegraph/analyzer/JiebaAnalyzer.java @@ -0,0 +1,63 @@ +/* + * Copyright 2017 HugeGraph Authors + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.analyzer; + +import java.util.List; +import java.util.Set; + +import org.apache.hugegraph.config.ConfigException; +import org.apache.hugegraph.util.InsertionOrderUtil; + +import com.google.common.collect.ImmutableList; +import com.huaban.analysis.jieba.JiebaSegmenter; +import com.huaban.analysis.jieba.SegToken; + +/** + * Reference from https://my.oschina.net/apdplat/blog/412921 + */ +public class JiebaAnalyzer implements Analyzer { + + public static final List SUPPORT_MODES = ImmutableList.of( + "SEARCH", + "INDEX" + ); + + private static final JiebaSegmenter JIEBA_SEGMENTER = new JiebaSegmenter(); + + private JiebaSegmenter.SegMode segMode; + + public JiebaAnalyzer(String mode) { + if (!SUPPORT_MODES.contains(mode)) { + throw new ConfigException( + "Unsupported segment mode '%s' for jieba analyzer, " + + "the available values are %s", mode, SUPPORT_MODES); + } + this.segMode = JiebaSegmenter.SegMode.valueOf(mode); + } + + @Override + public Set segment(String text) { + Set result = InsertionOrderUtil.newSet(); + for (SegToken token : JIEBA_SEGMENTER.process(text, this.segMode)) { + result.add(token.word); + } + return result; + } +} diff --git a/hugegraph-struct/src/main/java/org/apache/hugegraph/analyzer/MMSeg4JAnalyzer.java b/hugegraph-struct/src/main/java/org/apache/hugegraph/analyzer/MMSeg4JAnalyzer.java new file mode 100644 index 0000000000..3316582f73 --- /dev/null +++ b/hugegraph-struct/src/main/java/org/apache/hugegraph/analyzer/MMSeg4JAnalyzer.java @@ -0,0 +1,92 @@ +/* + * Copyright 2017 HugeGraph Authors + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.analyzer; + +import java.io.StringReader; +import java.util.List; +import java.util.Set; + +import org.apache.hugegraph.config.ConfigException; +import org.apache.hugegraph.util.InsertionOrderUtil; + +import org.apache.hugegraph.exception.HugeException; +import com.chenlb.mmseg4j.ComplexSeg; +import com.chenlb.mmseg4j.Dictionary; +import com.chenlb.mmseg4j.MMSeg; +import com.chenlb.mmseg4j.MaxWordSeg; +import com.chenlb.mmseg4j.Seg; +import com.chenlb.mmseg4j.SimpleSeg; +import com.chenlb.mmseg4j.Word; +import com.google.common.collect.ImmutableList; + +/** + * Reference from https://my.oschina.net/apdplat/blog/412921 + */ +public class MMSeg4JAnalyzer implements Analyzer { + + public static final List SUPPORT_MODES = ImmutableList.of( + "Simple", + "Complex", + "MaxWord" + ); + + private static final Dictionary DIC = Dictionary.getInstance(); + + private Seg seg; + + public MMSeg4JAnalyzer(String mode) { + if (!SUPPORT_MODES.contains(mode)) { + throw new ConfigException( + "Unsupported segment mode '%s' for mmseg4j analyzer, " + + "the available values are %s", mode, SUPPORT_MODES); + } + int index = SUPPORT_MODES.indexOf(mode); + switch (index) { + case 0: + this.seg = new SimpleSeg(DIC); + break; + case 1: + this.seg = new ComplexSeg(DIC); + break; + case 2: + this.seg = new MaxWordSeg(DIC); + break; + default: + throw new AssertionError(String.format( + "Unsupported segment mode '%s'", this.seg)); + } + } + + @Override + public Set segment(String text) { + Set result = InsertionOrderUtil.newSet(); + MMSeg mmSeg = new MMSeg(new StringReader(text), this.seg); + try { + Word word = null; + while ((word = mmSeg.next()) != null) { + result.add(word.getString()); + } + } catch (Exception e) { + throw new HugeException("MMSeg4j segment text '%s' failed", + e, text); + } + return result; + } +} diff --git a/hugegraph-struct/src/main/java/org/apache/hugegraph/analyzer/SmartCNAnalyzer.java b/hugegraph-struct/src/main/java/org/apache/hugegraph/analyzer/SmartCNAnalyzer.java new file mode 100644 index 0000000000..34c0ea2fba --- /dev/null +++ b/hugegraph-struct/src/main/java/org/apache/hugegraph/analyzer/SmartCNAnalyzer.java @@ -0,0 +1,66 @@ +/* + * Copyright 2017 HugeGraph Authors + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.analyzer; + +import java.io.Reader; +import java.io.StringReader; +import java.util.List; +import java.util.Set; + +import org.apache.hugegraph.util.InsertionOrderUtil; +import org.apache.lucene.analysis.TokenStream; +import org.apache.lucene.analysis.cn.smart.SmartChineseAnalyzer; +import org.apache.lucene.analysis.tokenattributes.CharTermAttribute; + +import org.apache.hugegraph.exception.HugeException; +import com.google.common.collect.ImmutableList; + +/** + * Reference from https://my.oschina.net/apdplat/blog/412921 + */ +public class SmartCNAnalyzer implements Analyzer { + + public static final List SUPPORT_MODES = ImmutableList.of(); + + private static final SmartChineseAnalyzer ANALYZER = + new SmartChineseAnalyzer(); + + public SmartCNAnalyzer(String mode) { + // pass + } + + @Override + public Set segment(String text) { + Set result = InsertionOrderUtil.newSet(); + Reader reader = new StringReader(text); + try (TokenStream tokenStream = ANALYZER.tokenStream("text", reader)) { + tokenStream.reset(); + CharTermAttribute term = null; + while (tokenStream.incrementToken()) { + term = tokenStream.getAttribute(CharTermAttribute.class); + result.add(term.toString()); + } + } catch (Exception e) { + throw new HugeException("SmartCN segment text '%s' failed", + e, text); + } + return result; + } +} diff --git a/hugegraph-struct/src/main/java/org/apache/hugegraph/analyzer/WordAnalyzer.java b/hugegraph-struct/src/main/java/org/apache/hugegraph/analyzer/WordAnalyzer.java new file mode 100644 index 0000000000..0a7ebd07fc --- /dev/null +++ b/hugegraph-struct/src/main/java/org/apache/hugegraph/analyzer/WordAnalyzer.java @@ -0,0 +1,74 @@ +/* + * Copyright 2017 HugeGraph Authors + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.analyzer; + +import java.util.List; +import java.util.Set; + +import org.apache.hugegraph.config.ConfigException; +import org.apache.hugegraph.util.InsertionOrderUtil; +import org.apdplat.word.WordSegmenter; +import org.apdplat.word.segmentation.SegmentationAlgorithm; +import org.apdplat.word.segmentation.Word; + +import com.google.common.collect.ImmutableList; + +/** + * Reference from https://my.oschina.net/apdplat/blog/412921 + */ +public class WordAnalyzer implements Analyzer { + + public static final List SUPPORT_MODES = + ImmutableList.builder() + .add("MaximumMatching") + .add("ReverseMaximumMatching") + .add("MinimumMatching") + .add("ReverseMinimumMatching") + .add("BidirectionalMaximumMatching") + .add("BidirectionalMinimumMatching") + .add("BidirectionalMaximumMinimumMatching") + .add("FullSegmentation") + .add("MinimalWordCount") + .add("MaxNgramScore") + .add("PureEnglish") + .build(); + + private SegmentationAlgorithm algorithm; + + public WordAnalyzer(String mode) { + try { + this.algorithm = SegmentationAlgorithm.valueOf(mode); + } catch (Exception e) { + throw new ConfigException( + "Unsupported segment mode '%s' for word analyzer, " + + "the available values are %s", e, mode, SUPPORT_MODES); + } + } + + @Override + public Set segment(String text) { + Set result = InsertionOrderUtil.newSet(); + List words = WordSegmenter.segWithStopWords(text, this.algorithm); + for (Word word : words) { + result.add(word.getText()); + } + return result; + } +} diff --git a/hugegraph-struct/src/main/java/org/apache/hugegraph/auth/AuthConstant.java b/hugegraph-struct/src/main/java/org/apache/hugegraph/auth/AuthConstant.java new file mode 100644 index 0000000000..97bd1a0e1c --- /dev/null +++ b/hugegraph-struct/src/main/java/org/apache/hugegraph/auth/AuthConstant.java @@ -0,0 +1,30 @@ +/* + * Copyright 2017 HugeGraph Authors + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.auth; + +public interface AuthConstant { + + /* + * Fields in token + */ + String TOKEN_USER_NAME = "user_name"; + String TOKEN_USER_ID = "user_id"; + String TOKEN_USER_PASSWORD = "user_password"; +} diff --git a/hugegraph-struct/src/main/java/org/apache/hugegraph/auth/TokenGenerator.java b/hugegraph-struct/src/main/java/org/apache/hugegraph/auth/TokenGenerator.java new file mode 100644 index 0000000000..f803894fc2 --- /dev/null +++ b/hugegraph-struct/src/main/java/org/apache/hugegraph/auth/TokenGenerator.java @@ -0,0 +1,70 @@ +/* + * Copyright 2017 HugeGraph Authors + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.auth; + +import org.apache.hugegraph.options.AuthOptions; + +import io.jsonwebtoken.*; +import io.jsonwebtoken.security.Keys; +import jakarta.ws.rs.NotAuthorizedException; + +import org.apache.hugegraph.config.HugeConfig; + +import javax.crypto.SecretKey; + +import java.nio.charset.StandardCharsets; +import java.util.Date; +import java.util.Map; + +public class TokenGenerator { + + private final SecretKey key; + + public TokenGenerator(HugeConfig config) { + String secretKey = config.get(AuthOptions.AUTH_TOKEN_SECRET); + this.key = Keys.hmacShaKeyFor(secretKey.getBytes(StandardCharsets.UTF_8)); + } + + public TokenGenerator(String secretKey) { + this.key = Keys.hmacShaKeyFor(secretKey.getBytes(StandardCharsets.UTF_8)); + } + + public String create(Map payload, long expire) { + return Jwts.builder() + .setClaims(payload) + .setExpiration(new Date(System.currentTimeMillis() + expire)) + .signWith(this.key, SignatureAlgorithm.HS256) + .compact(); + } + + public Claims verify(String token) { + try { + Jws claimsJws = Jwts.parserBuilder() + .setSigningKey(key) + .build() + .parseClaimsJws(token); + return claimsJws.getBody(); + } catch (ExpiredJwtException e) { + throw new NotAuthorizedException("The token is expired", e); + } catch (JwtException e) { + throw new NotAuthorizedException("Invalid token", e); + } + } +} diff --git a/hugegraph-struct/src/main/java/org/apache/hugegraph/backend/BackendColumn.java b/hugegraph-struct/src/main/java/org/apache/hugegraph/backend/BackendColumn.java new file mode 100644 index 0000000000..342f3ff60e --- /dev/null +++ b/hugegraph-struct/src/main/java/org/apache/hugegraph/backend/BackendColumn.java @@ -0,0 +1,69 @@ +/* + * Copyright 2017 HugeGraph Authors + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.backend; + +import java.util.Arrays; + +import org.apache.hugegraph.util.Bytes; + +import org.apache.hugegraph.util.StringEncoding; + +public class BackendColumn implements Comparable { + + public byte[] name; + public byte[] value; + + public static BackendColumn of(byte[] name, byte[] value) { + BackendColumn col = new BackendColumn(); + col.name = name; + col.value = value; + return col; + } + + @Override + public String toString() { + return String.format("%s=%s", + StringEncoding.decode(name), + StringEncoding.decode(value)); + } + + @Override + public int compareTo(BackendColumn other) { + if (other == null) { + return 1; + } + return Bytes.compare(this.name, other.name); + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof BackendColumn)) { + return false; + } + BackendColumn other = (BackendColumn) obj; + return Bytes.equals(this.name, other.name) && + Bytes.equals(this.value, other.value); + } + + @Override + public int hashCode() { + return Arrays.hashCode(this.name) | Arrays.hashCode(this.value); + } +} diff --git a/hugegraph-struct/src/main/java/org/apache/hugegraph/backend/BinaryId.java b/hugegraph-struct/src/main/java/org/apache/hugegraph/backend/BinaryId.java new file mode 100644 index 0000000000..685a934fd7 --- /dev/null +++ b/hugegraph-struct/src/main/java/org/apache/hugegraph/backend/BinaryId.java @@ -0,0 +1,103 @@ +/* + * Copyright 2017 HugeGraph Authors + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.backend; + +import org.apache.hugegraph.id.Id; + +import org.apache.hugegraph.util.Bytes; +import org.apache.hugegraph.util.E; + +import java.nio.ByteBuffer; +import java.util.Arrays; + +public final class BinaryId implements Id { + + private final byte[] bytes; + private final Id id; + + public BinaryId(byte[] bytes, Id id) { + this.bytes = bytes; + this.id = id; + } + + public Id origin() { + return this.id; + } + + @Override + public IdType type() { + return IdType.UNKNOWN; + } + + @Override + public Object asObject() { + return ByteBuffer.wrap(this.bytes); + } + + @Override + public String asString() { + throw new UnsupportedOperationException(); + } + + @Override + public long asLong() { + throw new UnsupportedOperationException(); + } + + @Override + public int compareTo(Id other) { + return Bytes.compare(this.bytes, other.asBytes()); + } + + @Override + public byte[] asBytes() { + return this.bytes; + } + + public byte[] asBytes(int offset) { + E.checkArgument(offset < this.bytes.length, + "Invalid offset %s, must be < length %s", + offset, this.bytes.length); + return Arrays.copyOfRange(this.bytes, offset, this.bytes.length); + } + + @Override + public int length() { + return this.bytes.length; + } + + @Override + public int hashCode() { + return ByteBuffer.wrap(this.bytes).hashCode(); + } + + @Override + public boolean equals(Object other) { + if (!(other instanceof BinaryId)) { + return false; + } + return Arrays.equals(this.bytes, ((BinaryId) other).bytes); + } + + @Override + public String toString() { + return "0x" + Bytes.toHex(this.bytes); + } +} diff --git a/hugegraph-struct/src/main/java/org/apache/hugegraph/backend/Shard.java b/hugegraph-struct/src/main/java/org/apache/hugegraph/backend/Shard.java new file mode 100644 index 0000000000..7d69166c63 --- /dev/null +++ b/hugegraph-struct/src/main/java/org/apache/hugegraph/backend/Shard.java @@ -0,0 +1,71 @@ +/* + * Copyright 2017 HugeGraph Authors + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.backend; + +/** + * Shard is used for backend storage (like cassandra, hbase) scanning + * operations. Each shard represents a range of tokens for a node. + * Reading data from a given shard does not cross multiple nodes. + */ +public class Shard { + + // token range start + private String start; + // token range end + private String end; + // partitions count in this range + private long length; + + public Shard(String start, String end, long length) { + this.start = start; + this.end = end; + this.length = length; + } + + public String start() { + return this.start; + } + + public void start(String start) { + this.start = start; + } + + public String end() { + return this.end; + } + + public void end(String end) { + this.end = end; + } + + public long length() { + return this.length; + } + + public void length(long length) { + this.length = length; + } + + @Override + public String toString() { + return String.format("Shard{start=%s, end=%s, length=%s}", + this.start, this.end, this.length); + } +} diff --git a/hugegraph-struct/src/main/java/org/apache/hugegraph/exception/BackendException.java b/hugegraph-struct/src/main/java/org/apache/hugegraph/exception/BackendException.java new file mode 100644 index 0000000000..3fffd5ea10 --- /dev/null +++ b/hugegraph-struct/src/main/java/org/apache/hugegraph/exception/BackendException.java @@ -0,0 +1,53 @@ +/* + * Copyright 2017 HugeGraph Authors + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.exception; + +public class BackendException extends HugeException { + + private static final long serialVersionUID = -1947589125372576298L; + + public BackendException(String message) { + super(message); + } + + public BackendException(String message, Throwable cause) { + super(message, cause); + } + + public BackendException(String message, Object... args) { + super(message, args); + } + + public BackendException(String message, Throwable cause, Object... args) { + super(message, cause, args); + } + + public BackendException(Throwable cause) { + this("Exception in backend", cause); + } + + public static final void check(boolean expression, + String message, Object... args) + throws BackendException { + if (!expression) { + throw new BackendException(message, args); + } + } +} diff --git a/hugegraph-struct/src/main/java/org/apache/hugegraph/exception/ErrorCodeProvider.java b/hugegraph-struct/src/main/java/org/apache/hugegraph/exception/ErrorCodeProvider.java new file mode 100644 index 0000000000..d5034b703a --- /dev/null +++ b/hugegraph-struct/src/main/java/org/apache/hugegraph/exception/ErrorCodeProvider.java @@ -0,0 +1,27 @@ +/* + * Copyright 2017 HugeGraph Authors + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.exception; + +public interface ErrorCodeProvider { + + public String format(Object... args); + + public String with(String message); +} diff --git a/hugegraph-struct/src/main/java/org/apache/hugegraph/exception/HugeException.java b/hugegraph-struct/src/main/java/org/apache/hugegraph/exception/HugeException.java new file mode 100644 index 0000000000..b7d8a45882 --- /dev/null +++ b/hugegraph-struct/src/main/java/org/apache/hugegraph/exception/HugeException.java @@ -0,0 +1,70 @@ +/* + * Copyright 2017 HugeGraph Authors + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.exception; + +public class HugeException extends RuntimeException { + + private static final long serialVersionUID = -8711375282196157058L; + + public HugeException(String message) { + super(message); + } + + public HugeException(ErrorCodeProvider code, String message) { + super(code.with(message)); + } + + public HugeException(String message, Throwable cause) { + super(message, cause); + } + + public HugeException(ErrorCodeProvider code, String message, Throwable cause) { + super(code.with(message), cause); + } + + public HugeException(String message, Object... args) { + super(String.format(message, args)); + } + + public HugeException(ErrorCodeProvider code, Object... args) { + super(code.format(args)); + } + + public HugeException(String message, Throwable cause, Object... args) { + super(String.format(message, args), cause); + } + + public HugeException(ErrorCodeProvider code, Throwable cause, Object... args) { + super(code.format(args), cause); + } + + public Throwable rootCause() { + return rootCause(this); + } + + public static Throwable rootCause(Throwable e) { + Throwable cause = e; + while (cause.getCause() != null) { + cause = cause.getCause(); + } + return cause; + } + +} diff --git a/hugegraph-struct/src/main/java/org/apache/hugegraph/exception/LimitExceedException.java b/hugegraph-struct/src/main/java/org/apache/hugegraph/exception/LimitExceedException.java new file mode 100644 index 0000000000..10652dca2c --- /dev/null +++ b/hugegraph-struct/src/main/java/org/apache/hugegraph/exception/LimitExceedException.java @@ -0,0 +1,33 @@ +/* + * Copyright 2017 HugeGraph Authors + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.exception; + +public class LimitExceedException extends HugeException { + + private static final long serialVersionUID = 7384276720045597709L; + + public LimitExceedException(String message) { + super(message); + } + + public LimitExceedException(String message, Object... args) { + super(message, args); + } +} diff --git a/hugegraph-struct/src/main/java/org/apache/hugegraph/exception/NotAllowException.java b/hugegraph-struct/src/main/java/org/apache/hugegraph/exception/NotAllowException.java new file mode 100644 index 0000000000..3781b6d482 --- /dev/null +++ b/hugegraph-struct/src/main/java/org/apache/hugegraph/exception/NotAllowException.java @@ -0,0 +1,33 @@ +/* + * Copyright 2017 HugeGraph Authors + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.exception; + +public class NotAllowException extends HugeException { + + private static final long serialVersionUID = -1407924451828873200L; + + public NotAllowException(String message) { + super(message); + } + + public NotAllowException(String message, Object... args) { + super(message, args); + } +} diff --git a/hugegraph-struct/src/main/java/org/apache/hugegraph/exception/NotFoundException.java b/hugegraph-struct/src/main/java/org/apache/hugegraph/exception/NotFoundException.java new file mode 100644 index 0000000000..8567ceb018 --- /dev/null +++ b/hugegraph-struct/src/main/java/org/apache/hugegraph/exception/NotFoundException.java @@ -0,0 +1,37 @@ +/* + * Copyright 2017 HugeGraph Authors + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.exception; + +public class NotFoundException extends HugeException { + + private static final long serialVersionUID = -5912665926327173032L; + + public NotFoundException(String message) { + super(message); + } + + public NotFoundException(String message, Object... args) { + super(message, args); + } + + public NotFoundException(String message, Throwable cause, Object... args) { + super(message, cause, args); + } +} diff --git a/hugegraph-struct/src/main/java/org/apache/hugegraph/exception/NotSupportException.java b/hugegraph-struct/src/main/java/org/apache/hugegraph/exception/NotSupportException.java new file mode 100644 index 0000000000..49d3dad49c --- /dev/null +++ b/hugegraph-struct/src/main/java/org/apache/hugegraph/exception/NotSupportException.java @@ -0,0 +1,34 @@ +/* + * Copyright 2017 HugeGraph Authors + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.exception; + +public class NotSupportException extends HugeException { + + private static final long serialVersionUID = -2914329541122906234L; + private static final String PREFIX = "Not support "; + + public NotSupportException(String message) { + super(PREFIX + message); + } + + public NotSupportException(String message, Object... args) { + super(PREFIX + message, args); + } +} diff --git a/hugegraph-struct/src/main/java/org/apache/hugegraph/id/EdgeId.java b/hugegraph-struct/src/main/java/org/apache/hugegraph/id/EdgeId.java new file mode 100644 index 0000000000..2b03e97d33 --- /dev/null +++ b/hugegraph-struct/src/main/java/org/apache/hugegraph/id/EdgeId.java @@ -0,0 +1,350 @@ +/* + * Copyright 2017 HugeGraph Authors + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.id; + +import org.apache.hugegraph.perf.PerfUtil.Watched; +import org.apache.hugegraph.testutil.Assert; +import org.apache.hugegraph.util.E; + +import org.apache.hugegraph.exception.NotFoundException; +import org.apache.hugegraph.type.HugeType; +import org.apache.hugegraph.type.define.Directions; +import org.apache.hugegraph.type.define.HugeKeys; +import org.apache.hugegraph.util.StringEncoding; + +/** + * Class used to format and parse id of edge, the edge id consists of: + * EdgeId = { source-vertex-id > direction > parentEdgeLabelId > subEdgeLabelId + * >sortKeys > target-vertex-id } + * NOTE: + * 1. for edges with edgeLabel-type=NORMAL,edgelabelId=parentEdgeLabelId=subEdgeLabelId, + * for edges with edgeLabel type=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) + */ +public class EdgeId implements Id { + + public static final HugeKeys[] KEYS = new HugeKeys[] { + HugeKeys.OWNER_VERTEX, + HugeKeys.DIRECTION, + HugeKeys.LABEL, + HugeKeys.SUB_LABEL, + HugeKeys.SORT_VALUES, + HugeKeys.OTHER_VERTEX + }; + + private final Id ownerVertexId; + private final Directions direction; + private final Id edgeLabelId; + private final Id subLabelId; + private final String sortValues; + private final Id otherVertexId; + + private final boolean directed; + private String cache; + + + public EdgeId(Id ownerVertexId, Directions direction, Id edgeLabelId, + Id subLabelId, String sortValues, + Id otherVertexId) { + this(ownerVertexId, direction, edgeLabelId, + subLabelId, sortValues, otherVertexId, false); + } + + 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.sortValues = sortValues; + this.subLabelId = subLabelId; + this.otherVertexId = otherVertexId; + this.directed = directed; + this.cache = null; + } + + @Watched + public EdgeId switchDirection() { + Directions direction = this.direction.opposite(); + 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, + this.subLabelId, this.sortValues, this.otherVertexId, directed); + } + + private Id sourceVertexId() { + return this.direction == Directions.OUT ? + this.ownerVertexId : + this.otherVertexId; + } + + private Id targetVertexId() { + return this.direction == Directions.OUT ? + this.otherVertexId : + this.ownerVertexId; + } + + public Id subLabelId(){ + return this.subLabelId; + } + + public Id ownerVertexId() { + return this.ownerVertexId; + } + + public Id edgeLabelId() { + return this.edgeLabelId; + } + + public Directions direction() { + return this.direction; + } + + public byte directionCode() { + return directionToCode(this.direction); + } + + public String sortValues() { + return this.sortValues; + } + + public Id otherVertexId() { + return this.otherVertexId; + } + + @Override + public Object asObject() { + return this.asString(); + } + + @Override + public String asString() { + if (this.cache != null) { + return this.cache; + } + if (this.directed) { + this.cache = SplicingIdGenerator.concat( + 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())); + } + return this.cache; + } + + @Override + public long asLong() { + throw new UnsupportedOperationException(); + } + + @Override + public byte[] asBytes() { + return StringEncoding.encode(this.asString()); + } + + @Override + public int length() { + return this.asString().length(); + } + + @Override + public IdType type() { + return IdType.EDGE; + } + + @Override + public int compareTo(Id other) { + return this.asString().compareTo(other.asString()); + } + + @Override + public int hashCode() { + if (this.directed) { + 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(); + } + } + + @Override + public boolean equals(Object object) { + if (!(object instanceof EdgeId)) { + return false; + } + EdgeId other = (EdgeId) object; + if (this.directed) { + return this.ownerVertexId.equals(other.ownerVertexId) && + this.direction == other.direction && + this.edgeLabelId.equals(other.edgeLabelId) && + this.sortValues.equals(other.sortValues) && + this.subLabelId.equals(other.subLabelId) && + this.otherVertexId.equals(other.otherVertexId); + } else { + return this.sourceVertexId().equals(other.sourceVertexId()) && + this.edgeLabelId.equals(other.edgeLabelId) && + this.sortValues.equals(other.sortValues) && + this.subLabelId.equals(other.subLabelId) && + this.targetVertexId().equals(other.targetVertexId()); + } + } + + @Override + public String toString() { + return this.asString(); + } + + public static byte directionToCode(Directions direction) { + return direction.type().code(); + } + + public static Directions directionFromCode(byte code) { + return (code == HugeType.EDGE_OUT.code()) ? Directions.OUT : Directions.IN; + } + + public static boolean isOutDirectionFromCode(byte code) { + return code == HugeType.EDGE_OUT.code(); + } + + public static EdgeId parse(String id) throws NotFoundException { + return parse(id, false); + } + + public static EdgeId parse(String id, boolean returnNullIfError) + throws NotFoundException { + String[] idParts = SplicingIdGenerator.split(id); + if (!(idParts.length == 5 || idParts.length == 6)) { + if (returnNullIfError) { + return null; + } + throw new NotFoundException("Edge id must be formatted as 5~6 " + + "parts, but got %s parts: '%s'", + idParts.length, id); + } + try { + if (idParts.length == 5) { + Id ownerVertexId = IdUtil.readString(idParts[0]); + Id edgeLabelId = IdUtil.readLong(idParts[1]); + 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 == 6; + Id ownerVertexId = IdUtil.readString(idParts[0]); + HugeType direction = HugeType.fromString(idParts[1]); + Id edgeLabelId = IdUtil.readLong(idParts[2]); + Id subLabelId = IdUtil.readLong(idParts[3]); + String sortValues = idParts[4]; + Id otherVertexId = IdUtil.readString(idParts[5]); + return new EdgeId(ownerVertexId, Directions.convert(direction), + edgeLabelId, subLabelId, + sortValues, otherVertexId); + } + } catch (Throwable e) { + if (returnNullIfError) { + return null; + } + throw new NotFoundException("Invalid format of edge id '%s'", + e, id); + } + } + + public static Id parseStoredString(String id) { + String[] idParts = split(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); + 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); + } + + public static String asStoredString(Id id) { + EdgeId eid = (EdgeId) id; + return SplicingIdGenerator.concat( + IdUtil.writeStoredString(eid.sourceVertexId()), + IdGenerator.asStoredString(eid.edgeLabelId()), + IdGenerator.asStoredString(eid.subLabelId()), + eid.sortValues(), + IdUtil.writeStoredString(eid.targetVertexId())); + } + + public static String concat(String... ids) { + return SplicingIdGenerator.concat(ids); + } + + public static String[] split(Id id) { + return EdgeId.split(id.asString()); + } + + public static String[] split(String id) { + return SplicingIdGenerator.split(id); + } + + + public static void main(String[] args) { + 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)); + Assert.assertTrue(edgeId2.equals(edgeId1)); + Assert.assertTrue(edgeId1.equals(edgeId3)); + Assert.assertTrue(edgeId3.equals(edgeId1)); + } + +} diff --git a/hugegraph-struct/src/main/java/org/apache/hugegraph/id/Id.java b/hugegraph-struct/src/main/java/org/apache/hugegraph/id/Id.java new file mode 100644 index 0000000000..aeb7810a9d --- /dev/null +++ b/hugegraph-struct/src/main/java/org/apache/hugegraph/id/Id.java @@ -0,0 +1,90 @@ +/* + * Copyright 2017 HugeGraph Authors + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.id; + +import java.io.Serializable; + +import org.apache.hugegraph.util.E; + +public interface Id extends Comparable, Serializable { + + public static final int UUID_LENGTH = 16; + + public Object asObject(); + + public String asString(); + + public long asLong(); + + public byte[] asBytes(); + + public int length(); + + public IdType type(); + + public default boolean number() { + return this.type() == IdType.LONG; + } + + public default boolean uuid() { + return this.type() == IdType.UUID; + } + + public default boolean string() { + return this.type() == IdType.STRING; + } + + public default boolean edge() { + return this.type() == IdType.EDGE; + } + + public enum IdType { + + UNKNOWN, + LONG, + UUID, + STRING, + EDGE; + + public char prefix() { + if (this == UNKNOWN) { + return 'N'; + } + return this.name().charAt(0); + } + + public static IdType valueOfPrefix(String id) { + E.checkArgument(id != null && id.length() > 0, + "Invalid id '%s'", id); + switch (id.charAt(0)) { + case 'L': + return IdType.LONG; + case 'U': + return IdType.UUID; + case 'S': + return IdType.STRING; + case 'E': + return IdType.EDGE; + default: + return IdType.UNKNOWN; + } + } + } +} diff --git a/hugegraph-struct/src/main/java/org/apache/hugegraph/id/IdGenerator.java b/hugegraph-struct/src/main/java/org/apache/hugegraph/id/IdGenerator.java new file mode 100644 index 0000000000..b6687262db --- /dev/null +++ b/hugegraph-struct/src/main/java/org/apache/hugegraph/id/IdGenerator.java @@ -0,0 +1,465 @@ +/* + * Copyright 2017 HugeGraph Authors + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.id; + +import org.apache.hugegraph.serializer.BytesBuffer; +import org.apache.hugegraph.structure.BaseVertex; +import org.apache.hugegraph.util.StringEncoding; +import com.google.common.primitives.Longs; + +import org.apache.hugegraph.util.E; +import org.apache.hugegraph.util.LongEncoding; +import org.apache.hugegraph.util.NumericUtil; + +import java.nio.charset.Charset; +import java.util.Objects; +import java.util.UUID; + +public abstract class IdGenerator { + + public static final Id ZERO = IdGenerator.of(0L); + + public abstract Id generate(BaseVertex vertex); + + public final static Id of(String id) { + return new StringId(id); + } + + public final static Id of(UUID id) { + return new UuidId(id); + } + + public final static Id of(String id, boolean uuid) { + return uuid ? new UuidId(id) : new StringId(id); + } + + public final static Id of(long id) { + return new LongId(id); + } + + public static Id of(Object id) { + if (id instanceof Id) { + return (Id) id; + } else if (id instanceof String) { + return of((String) id); + } else if (id instanceof Number) { + return of(((Number) id).longValue()); + } else if (id instanceof UUID) { + return of((UUID) id); + } + return new ObjectId(id); + } + + public final static Id of(byte[] bytes, Id.IdType type) { + switch (type) { + case LONG: + return new LongId(bytes); + case UUID: + return new UuidId(bytes); + case STRING: + return new StringId(bytes); + default: + throw new AssertionError("Invalid id type " + type); + } + } + + public final static Id ofStoredString(String id, Id.IdType type) { + switch (type) { + case LONG: + return of(LongEncoding.decodeSignedB64(id)); + case UUID: + byte[] bytes = StringEncoding.decodeBase64(id); + return of(bytes, Id.IdType.UUID); + case STRING: + return of(id); + default: + throw new AssertionError("Invalid id type " + type); + } + } + + public final static String asStoredString(Id id) { + switch (id.type()) { + case LONG: + return LongEncoding.encodeSignedB64(id.asLong()); + case UUID: + return StringEncoding.encodeBase64(id.asBytes()); + case STRING: + return id.asString(); + default: + throw new AssertionError("Invalid id type " + id.type()); + } + } + + public final static Id.IdType idType(Id id) { + if (id instanceof LongId) { + return Id.IdType.LONG; + } + if (id instanceof UuidId) { + return Id.IdType.UUID; + } + if (id instanceof StringId) { + return Id.IdType.STRING; + } + if (id instanceof EdgeId) { + return Id.IdType.EDGE; + } + return Id.IdType.UNKNOWN; + } + + private final static int compareType(Id id1, Id id2) { + return idType(id1).ordinal() - idType(id2).ordinal(); + } + + /****************************** id defines ******************************/ + + public static final class StringId implements Id { + + private final String id; + private static final Charset CHARSET = Charset.forName("UTF-8"); + + public StringId(String id) { + E.checkArgument(!id.isEmpty(), "The id can't be empty"); + this.id = id; + } + + public StringId(byte[] bytes) { + this.id = StringEncoding.decode(bytes); + } + + @Override + public IdType type() { + return IdType.STRING; + } + + @Override + public Object asObject() { + return this.id; + } + + @Override + public String asString() { + return this.id; + } + + @Override + public long asLong() { + return Long.parseLong(this.id); + } + + @Override + public byte[] asBytes() { + return this.id.getBytes(CHARSET); + } + + @Override + public int length() { + return this.id.length(); + } + + @Override + public int compareTo(Id other) { + int cmp = compareType(this, other); + if (cmp != 0) { + return cmp; + } + return this.id.compareTo(other.asString()); + } + + @Override + public int hashCode() { + return this.id.hashCode(); + } + + @Override + public boolean equals(Object other) { + if (!(other instanceof StringId)) { + return false; + } + return this.id.equals(((StringId) other).id); + } + + @Override + public String toString() { + return this.id; + } + } + + public static final class LongId extends Number implements Id { + + private static final long serialVersionUID = -7732461469037400190L; + + private final long id; + + public LongId(long id) { + this.id = id; + } + + public LongId(byte[] bytes) { + this.id = NumericUtil.bytesToLong(bytes); + } + + @Override + public IdType type() { + return IdType.LONG; + } + + @Override + public Object asObject() { + return this.id; + } + + @Override + public String asString() { + // TODO: encode with base64 + return Long.toString(this.id); + } + + @Override + public long asLong() { + return this.id; + } + + @Override + public byte[] asBytes() { + return Longs.toByteArray(this.id); + // return NumericUtil.longToBytes(this.id); + } + + @Override + public int length() { + return Long.BYTES; + } + + @Override + public int compareTo(Id other) { + int cmp = compareType(this, other); + if (cmp != 0) { + return cmp; + } + return Long.compare(this.id, other.asLong()); + } + + @Override + public int hashCode() { + return Long.hashCode(this.id); + } + + @Override + public boolean equals(Object other) { + if (!(other instanceof Number)) { + if (idDigitalObject(other)) { + return this.id == (long) Double.parseDouble(other.toString()); + } + return false; + } + return this.id == ((Number) other).longValue(); + } + + private static boolean idDigitalObject(Object object) { + String string = object.toString(); + for (int i = string.length(); --i >= 0; ) { + char c = string.charAt(i); + if (!Character.isDigit(c) && + '.' != c) { + return false; + } + } + return true; + } + + @Override + public String toString() { + return String.valueOf(this.id); + } + + @Override + public int intValue() { + return (int) this.id; + } + + @Override + public long longValue() { + return this.id; + } + + @Override + public float floatValue() { + return this.id; + } + + @Override + public double doubleValue() { + return this.id; + } + } + + public static final class UuidId implements Id { + + private final UUID uuid; + + public UuidId(String string) { + this(StringEncoding.uuid(string)); + } + + public UuidId(byte[] bytes) { + this(fromBytes(bytes)); + } + + public UuidId(UUID uuid) { + E.checkArgument(uuid != null, "The uuid can't be null"); + this.uuid = uuid; + } + + @Override + public IdType type() { + return IdType.UUID; + } + + @Override + public Object asObject() { + return this.uuid; + } + + @Override + public String asString() { + return this.uuid.toString(); + } + + @Override + public long asLong() { + throw new UnsupportedOperationException(); + } + + @Override + public byte[] asBytes() { + BytesBuffer buffer = BytesBuffer.allocate(16); + buffer.writeLong(this.uuid.getMostSignificantBits()); + buffer.writeLong(this.uuid.getLeastSignificantBits()); + return buffer.bytes(); + } + + private static UUID fromBytes(byte[] bytes) { + E.checkArgument(bytes != null, "The UUID can't be null"); + BytesBuffer buffer = BytesBuffer.wrap(bytes); + long high = buffer.readLong(); + long low = buffer.readLong(); + return new UUID(high, low); + } + + @Override + public int length() { + return UUID_LENGTH; + } + + @Override + public int compareTo(Id other) { + E.checkNotNull(other, "compare id"); + int cmp = compareType(this, other); + if (cmp != 0) { + return cmp; + } + return this.uuid.compareTo(((UuidId) other).uuid); + } + + @Override + public int hashCode() { + return this.uuid.hashCode(); + } + + @Override + public boolean equals(Object other) { + if (!(other instanceof UuidId)) { + return false; + } + return this.uuid.equals(((UuidId) other).uuid); + } + + @Override + public String toString() { + return this.uuid.toString(); + } + } + + /** + * This class is just used by backend store for wrapper object as Id + */ + public static final class ObjectId implements Id { + + private final Object object; + + public ObjectId(Object object) { + E.checkNotNull(object, "object"); + this.object = object; + } + + @Override + public IdType type() { + return IdType.UNKNOWN; + } + + @Override + public Object asObject() { + return this.object; + } + + @Override + public String asString() { + throw new UnsupportedOperationException(); + } + + @Override + public long asLong() { + throw new UnsupportedOperationException(); + } + + @Override + public byte[] asBytes() { + throw new UnsupportedOperationException(); + } + + @Override + public int length() { + throw new UnsupportedOperationException(); + } + + @Override + public int compareTo(Id o) { + throw new UnsupportedOperationException(); + } + + @Override + public int hashCode() { + return this.object.hashCode(); + } + + @Override + public boolean equals(Object other) { + if (!(other instanceof ObjectId)) { + return false; + } + return Objects.equals(this.object, ((ObjectId) other).object); + } + + @Override + public String toString() { + return this.object.toString(); + } + } +} diff --git a/hugegraph-struct/src/main/java/org/apache/hugegraph/id/IdUtil.java b/hugegraph-struct/src/main/java/org/apache/hugegraph/id/IdUtil.java new file mode 100644 index 0000000000..b394c79a12 --- /dev/null +++ b/hugegraph-struct/src/main/java/org/apache/hugegraph/id/IdUtil.java @@ -0,0 +1,162 @@ +/* + * Copyright 2017 HugeGraph Authors + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.id; + +import java.nio.ByteBuffer; + +import org.apache.commons.lang3.StringUtils; + +import org.apache.hugegraph.serializer.BytesBuffer; + +public final class IdUtil { + + public static String writeStoredString(Id id) { + String idString; + switch (id.type()) { + case LONG: + case STRING: + case UUID: + idString = IdGenerator.asStoredString(id); + break; + case EDGE: + idString = EdgeId.asStoredString(id); + break; + default: + throw new AssertionError("Invalid id type " + id.type()); + } + return id.type().prefix() + idString; + } + + public static Id readStoredString(String id) { + Id.IdType type = Id.IdType.valueOfPrefix(id); + String idContent = id.substring(1); + switch (type) { + case LONG: + case STRING: + case UUID: + return IdGenerator.ofStoredString(idContent, type); + case EDGE: + return EdgeId.parseStoredString(idContent); + default: + throw new IllegalArgumentException("Invalid id: " + id); + } + } + + public static Object writeBinString(Id id) { + int len = id.edge() ? BytesBuffer.BUF_EDGE_ID : id.length() + 1; + BytesBuffer buffer = BytesBuffer.allocate(len).writeId(id); + buffer.forReadWritten(); + return buffer.asByteBuffer(); + } + + public static Id readBinString(Object id) { + BytesBuffer buffer = BytesBuffer.wrap((ByteBuffer) id); + return buffer.readId(); + } + + public static byte[] asBytes(Id id) { + int len = id.edge() ? BytesBuffer.BUF_EDGE_ID : id.length() + 1; + BytesBuffer buffer = BytesBuffer.allocate(len).writeId(id); + return buffer.bytes(); + } + + public static Id fromBytes(byte[] bytes) { + BytesBuffer buffer = BytesBuffer.wrap(bytes); + return buffer.readId(); + } + + + public static String writeString(Id id) { + String idString = id.asString(); + StringBuilder sb = new StringBuilder(1 + idString.length()); + sb.append(id.type().prefix()).append(idString); + return sb.toString(); + } + + public static Id readString(String id) { + Id.IdType type = Id.IdType.valueOfPrefix(id); + String idContent = id.substring(1); + switch (type) { + case LONG: + return IdGenerator.of(Long.parseLong(idContent)); + case STRING: + case UUID: + return IdGenerator.of(idContent, type == Id.IdType.UUID); + case EDGE: + return EdgeId.parse(idContent); + default: + throw new IllegalArgumentException("Invalid id: " + id); + } + } + + public static String writeLong(Id id) { + return String.valueOf(id.asLong()); + } + + public static Id readLong(String id) { + return IdGenerator.of(Long.parseLong(id)); + } + + public static String escape(char splitor, char escape, String... values) { + int length = values.length + 4; + for (String value : values) { + length += value.length(); + } + StringBuilder escaped = new StringBuilder(length); + // Do escape for every item in values + for (String value : values) { + if (escaped.length() > 0) { + escaped.append(splitor); + } + + if (value.indexOf(splitor) == -1) { + escaped.append(value); + continue; + } + + // Do escape for current item + for (int i = 0, n = value.length(); i < n; i++) { + char ch = value.charAt(i); + if (ch == splitor) { + escaped.append(escape); + } + escaped.append(ch); + } + } + return escaped.toString(); + } + + public static String[] unescape(String id, String splitor, String escape) { + /* + * Note that the `splitor`/`escape` maybe special characters in regular + * expressions, but this is a frequently called method, for faster + * execution, we forbid the use of special characters as delimiter + * or escape sign. + * The `limit` param -1 in split method can ensure empty string be + * splited to a part. + */ + String[] parts = id.split("(?'; + private static final char ID_SPLITOR = ':'; + private static final char NAME_SPLITOR = '!'; + + public static final String ESCAPE_STR = String.valueOf(ESCAPE); + public static final String IDS_SPLITOR_STR = String.valueOf(IDS_SPLITOR); + public static final String ID_SPLITOR_STR = String.valueOf(ID_SPLITOR); + + /****************************** id generate ******************************/ + + /** + * Generate a string id of HugeVertex from Vertex name + */ + @Override + public Id generate(BaseVertex vertex) { + /* + * Hash for row-key which will be evenly distributed. + * We can also use LongEncoding.encode() to encode the int/long hash + * if needed. + * id = String.format("%s%s%s", HashUtil.hash(id), ID_SPLITOR, id); + */ + // TODO: use binary Id with binary fields instead of string id + return splicing(vertex.schemaLabel().id().asString(), vertex.name()); + } + + /** + * Concat multiple ids into one composite id with IDS_SPLITOR + * @param ids the string id values to be concatted + * @return concatted string value + */ + public static String concat(String... ids) { + // NOTE: must support string id when using this method + return IdUtil.escape(IDS_SPLITOR, ESCAPE, ids); + } + + /** + * Split a composite id into multiple ids with IDS_SPLITOR + * @param ids the string id value to be splitted + * @return splitted string values + */ + public static String[] split(String ids) { + return IdUtil.unescape(ids, IDS_SPLITOR_STR, ESCAPE_STR); + } + + /** + * Concat property values with NAME_SPLITOR + * @param values the property values to be concatted + * @return concatted string value + */ + public static String concatValues(List values) { + // Convert the object list to string array + int valuesSize = values.size(); + String[] parts = new String[valuesSize]; + for (int i = 0; i < valuesSize; i++) { + parts[i] = values.get(i).toString(); + } + return IdUtil.escape(NAME_SPLITOR, ESCAPE, parts); + } + + /** + * Concat property values with NAME_SPLITOR + * @param values the property values to be concatted + * @return concatted string value + */ + public static String concatValues(Object... values) { + return concatValues(Arrays.asList(values)); + } + + /** + * Concat multiple parts into a single id with ID_SPLITOR + * @param parts the string id values to be spliced + * @return spliced id object + */ + public static Id splicing(String... parts) { + String escaped = IdUtil.escape(ID_SPLITOR, ESCAPE, parts); + return IdGenerator.of(escaped); + } + + public static Id splicingWithNoEscape(String... parts) { + String escaped = String.join(ID_SPLITOR_STR, parts); + return IdGenerator.of(escaped); + } + + public static Id generateBinaryId(Id id) { + if (id instanceof BinaryId) { + return id; + } + BytesBuffer buffer = BytesBuffer.allocate(1 + id.length()); + BinaryId binaryId = new BinaryId(buffer.writeId(id).bytes(), id); + return binaryId; + } + + /** + * Parse a single id into multiple parts with ID_SPLITOR + * @param id the id object to be parsed + * @return parsed string id parts + */ + public static String[] parse(Id id) { + return IdUtil.unescape(id.asString(), ID_SPLITOR_STR, ESCAPE_STR); + } +} diff --git a/hugegraph-struct/src/main/java/org/apache/hugegraph/options/AuthOptions.java b/hugegraph-struct/src/main/java/org/apache/hugegraph/options/AuthOptions.java new file mode 100644 index 0000000000..3ae732e2e2 --- /dev/null +++ b/hugegraph-struct/src/main/java/org/apache/hugegraph/options/AuthOptions.java @@ -0,0 +1,153 @@ +/* + * Copyright 2017 HugeGraph Authors + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.options; + +import org.apache.hugegraph.config.ConfigListOption; +import org.apache.hugegraph.config.ConfigOption; +import org.apache.hugegraph.config.OptionHolder; + +import java.security.SecureRandom; +import java.util.Base64; + +import static org.apache.hugegraph.config.OptionChecker.*; + +public class AuthOptions extends OptionHolder { + + private AuthOptions() { + super(); + } + + private static volatile AuthOptions instance; + + public static synchronized AuthOptions instance() { + if (instance == null) { + instance = new AuthOptions(); + instance.registerOptions(); + } + return instance; + } + + public static final ConfigOption AUTH_TOKEN_SECRET = + new ConfigOption<>( + "auth.token_secret", + "Secret key of HS256 algorithm.", + disallowEmpty(), + "FXQXbJtbCLxODc6tGci732pkH1cyf8Qg" + ); + + public static final ConfigOption AUTH_AUDIT_LOG_RATE = + new ConfigOption<>( + "auth.audit_log_rate", + "The max rate of audit log output per user, " + + "default value is 1000 records per second.", + rangeDouble(0.0, Double.MAX_VALUE), + 1000.0 + ); + + public static final ConfigOption AUTH_PROXY_CACHE_EXPIRE = + new ConfigOption<>( + "auth.proxy_cache_expire", + "The expiration time in seconds of auth cache in " + + "auth client.", + rangeInt(0L, Long.MAX_VALUE), + (1 * 60L) + ); + + public static final ConfigOption AUTH_CACHE_CAPACITY = + new ConfigOption<>( + "auth.cache_capacity", + "The max cache capacity of each auth cache item.", + rangeInt(0L, Long.MAX_VALUE), + (1024 * 10L) + ); + + public static final ConfigOption AUTHENTICATOR = + new ConfigOption<>( + "auth.authenticator", + "The class path of authenticator implementation. " + + "e.g., org.apache.hugegraph.auth.StandardAuthenticator, " + + "or org.apache.hugegraph.auth.ConfigAuthenticator.", + null, + "" + ); + + public static final ConfigOption AUTH_GRAPH_STORE = + new ConfigOption<>( + "auth.graph_store", + "The name of graph used to store authentication information, " + + "like users, only for org.apache.hugegraph.auth.StandardAuthenticator.", + disallowEmpty(), + "hugegraph" + ); + + public static final ConfigOption AUTH_ADMIN_TOKEN = + new ConfigOption<>( + "auth.admin_token", + "Token for administrator operations, " + + "only for org.apache.hugegraph.auth.ConfigAuthenticator.", + disallowEmpty(), + "162f7848-0b6d-4faf-b557-3a0797869c55" + ); + + public static final ConfigListOption AUTH_USER_TOKENS = + new ConfigListOption<>( + "auth.user_tokens", + "The map of user tokens with name and password, " + + "only for org.apache.hugegraph.auth.ConfigAuthenticator.", + disallowEmpty(), + "hugegraph:9fd95c9c-711b-415b-b85f-d4df46ba5c31" + ); + + public static final ConfigOption AUTH_REMOTE_URL = + new ConfigOption<>( + "auth.remote_url", + "If the address is empty, it provide auth service, " + + "otherwise it is auth client and also provide auth service " + + "through rpc forwarding. The remote url can be set to " + + "multiple addresses, which are concat by ','.", + null, + "" + ); + + public static final ConfigOption AUTH_CACHE_EXPIRE = + new ConfigOption<>( + "auth.cache_expire", + "The expiration time in seconds of auth cache in " + + "auth client and auth server.", + rangeInt(0L, Long.MAX_VALUE), + (60 * 10L) + ); + + public static final ConfigOption AUTH_TOKEN_EXPIRE = + new ConfigOption<>( + "auth.token_expire", + "The expiration time in seconds after token created", + rangeInt(0L, Long.MAX_VALUE), + (3600 * 24L) + ); + + private static String generateRandomBase64Key() { + SecureRandom random = new SecureRandom(); + // 32 bytes for HMAC-SHA256 + byte[] bytes = new byte[32]; + random.nextBytes(bytes); + return Base64.getEncoder().encodeToString(bytes); + } +} diff --git a/hugegraph-struct/src/main/java/org/apache/hugegraph/options/CoreOptions.java b/hugegraph-struct/src/main/java/org/apache/hugegraph/options/CoreOptions.java new file mode 100644 index 0000000000..70fd163e58 --- /dev/null +++ b/hugegraph-struct/src/main/java/org/apache/hugegraph/options/CoreOptions.java @@ -0,0 +1,715 @@ +/* + * 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.options; + +import static org.apache.hugegraph.config.OptionChecker.allowValues; +import static org.apache.hugegraph.config.OptionChecker.disallowEmpty; +import static org.apache.hugegraph.config.OptionChecker.nonNegativeInt; +import static org.apache.hugegraph.config.OptionChecker.positiveInt; +import static org.apache.hugegraph.config.OptionChecker.rangeInt; +import static org.apache.hugegraph.query.Query.COMMIT_BATCH; + +import org.apache.hugegraph.config.ConfigConvOption; +import org.apache.hugegraph.config.ConfigOption; +import org.apache.hugegraph.config.OptionHolder; +import org.apache.hugegraph.query.Query; +import org.apache.hugegraph.type.define.CollectionType; +import org.apache.hugegraph.util.Bytes; + +public class CoreOptions extends OptionHolder { + + public static final int CPUS = Runtime.getRuntime().availableProcessors(); + + private CoreOptions() { + super(); + } + + private static volatile CoreOptions instance; + + public static synchronized CoreOptions instance() { + if (instance == null) { + instance = new CoreOptions(); + // Should initialize all static members first, then register. + instance.registerOptions(); + } + return instance; + } + + public static final ConfigOption GREMLIN_GRAPH = + new ConfigOption<>( + "gremlin.graph", + "Gremlin entrance to create graph.", + disallowEmpty(), + "org.apache.hugegraph.HugeFactory" + ); + + public static final ConfigOption BACKEND = + new ConfigOption<>( + "backend", + "The data store type.", + disallowEmpty(), + "memory" + ); + + public static final ConfigOption STORE = + new ConfigOption<>( + "store", + "The database name like Cassandra Keyspace.", + disallowEmpty(), + "hugegraph" + ); + + public static final ConfigOption STORE_GRAPH = + new ConfigOption<>( + "store.graph", + "The graph table name, which store vertex, edge and property.", + disallowEmpty(), + "g" + ); + + public static final ConfigOption SERIALIZER = + new ConfigOption<>( + "serializer", + "The serializer for backend store, like: text/binary/cassandra.", + disallowEmpty(), + "text" + ); + + public static final ConfigOption RAFT_MODE = + new ConfigOption<>( + "raft.mode", + "Whether the backend storage works in raft mode.", + disallowEmpty(), + false + ); + + public static final ConfigOption RAFT_SAFE_READ = + new ConfigOption<>( + "raft.safe_read", + "Whether to use linearly consistent read.", + disallowEmpty(), + false + ); + + public static final ConfigOption RAFT_PATH = + new ConfigOption<>( + "raft.path", + "The log path of current raft node.", + disallowEmpty(), + "./raftlog" + ); + + public static final ConfigOption RAFT_REPLICATOR_PIPELINE = + new ConfigOption<>( + "raft.use_replicator_pipeline", + "Whether to use replicator line, when turned on it " + + "multiple logs can be sent in parallel, and the next log " + + "doesn't have to wait for the ack message of the current " + + "log to be sent.", + disallowEmpty(), + true + ); + + public static final ConfigOption RAFT_ELECTION_TIMEOUT = + new ConfigOption<>( + "raft.election_timeout", + "Timeout in milliseconds to launch a round of election.", + rangeInt(0, Integer.MAX_VALUE), + 10000 + ); + + public static final ConfigOption RAFT_SNAPSHOT_INTERVAL = + new ConfigOption<>( + "raft.snapshot_interval", + "The interval in seconds to trigger snapshot save.", + rangeInt(0, Integer.MAX_VALUE), + 3600 + ); + + public static final ConfigOption RAFT_SNAPSHOT_THREADS = + new ConfigOption<>( + "raft.snapshot_threads", + "The thread number used to do snapshot.", + rangeInt(0, Integer.MAX_VALUE), + 4 + ); + + public static final ConfigOption RAFT_SNAPSHOT_PARALLEL_COMPRESS = + new ConfigOption<>( + "raft.snapshot_parallel_compress", + "Whether to enable parallel compress.", + disallowEmpty(), + false + ); + + public static final ConfigOption RAFT_SNAPSHOT_COMPRESS_THREADS = + new ConfigOption<>( + "raft.snapshot_compress_threads", + "The thread number used to do snapshot compress.", + rangeInt(0, Integer.MAX_VALUE), + 4 + ); + + public static final ConfigOption RAFT_SNAPSHOT_DECOMPRESS_THREADS = + new ConfigOption<>( + "raft.snapshot_decompress_threads", + "The thread number used to do snapshot decompress.", + rangeInt(0, Integer.MAX_VALUE), + 4 + ); + + public static final ConfigOption RAFT_BACKEND_THREADS = + new ConfigOption<>( + "raft.backend_threads", + "The thread number used to apply task to backend.", + rangeInt(0, Integer.MAX_VALUE), + CPUS + ); + + public static final ConfigOption RAFT_READ_INDEX_THREADS = + new ConfigOption<>( + "raft.read_index_threads", + "The thread number used to execute reading index.", + rangeInt(0, Integer.MAX_VALUE), + 8 + ); + + public static final ConfigOption RAFT_READ_STRATEGY = + new ConfigOption<>( + "raft.read_strategy", + "The linearizability of read strategy.", + allowValues("ReadOnlyLeaseBased", "ReadOnlySafe"), + "ReadOnlyLeaseBased" + ); + + public static final ConfigOption RAFT_APPLY_BATCH = + new ConfigOption<>( + "raft.apply_batch", + "The apply batch size to trigger disruptor event handler.", + positiveInt(), + // jraft default value is 32 + 1 + ); + + public static final ConfigOption RAFT_QUEUE_SIZE = + new ConfigOption<>( + "raft.queue_size", + "The disruptor buffers size for jraft RaftNode, " + + "StateMachine and LogManager.", + positiveInt(), + // jraft default value is 16384 + 16384 + ); + + public static final ConfigOption RAFT_QUEUE_PUBLISH_TIMEOUT = + new ConfigOption<>( + "raft.queue_publish_timeout", + "The timeout in second when publish event into disruptor.", + positiveInt(), + // jraft default value is 10(sec) + 60 + ); + + public static final ConfigOption RAFT_RPC_THREADS = + new ConfigOption<>( + "raft.rpc_threads", + "The rpc threads for jraft RPC layer", + positiveInt(), + // jraft default value is 80 + Math.max(CPUS * 2, 80) + ); + + public static final ConfigOption RAFT_RPC_CONNECT_TIMEOUT = + new ConfigOption<>( + "raft.rpc_connect_timeout", + "The rpc connect timeout for jraft rpc.", + positiveInt(), + // jraft default value is 1000(ms) + 5000 + ); + + public static final ConfigOption RAFT_RPC_TIMEOUT = + new ConfigOption<>( + "raft.rpc_timeout", + "The general rpc timeout in seconds for jraft rpc.", + positiveInt(), + // jraft default value is 5s + 60 + ); + + public static final ConfigOption RAFT_INSTALL_SNAPSHOT_TIMEOUT = + new ConfigOption<>( + "raft.install_snapshot_rpc_timeout", + "The install snapshot rpc timeout in seconds for jraft rpc.", + positiveInt(), + // jraft default value is 5 minutes + 10 * 60 * 60 + ); + + public static final ConfigOption RAFT_RPC_BUF_LOW_WATER_MARK = + new ConfigOption<>( + "raft.rpc_buf_low_water_mark", + "The ChannelOutboundBuffer's low water mark of netty, " + + "when buffer size less than this size, the method " + + "ChannelOutboundBuffer.isWritable() will return true, " + + "it means that low downstream pressure or good network.", + positiveInt(), + 10 * 1024 * 1024 + ); + + public static final ConfigOption RAFT_RPC_BUF_HIGH_WATER_MARK = + new ConfigOption<>( + "raft.rpc_buf_high_water_mark", + "The ChannelOutboundBuffer's high water mark of netty, " + + "only when buffer size exceed this size, the method " + + "ChannelOutboundBuffer.isWritable() will return false, " + + "it means that the downstream pressure is too great to " + + "process the request or network is very congestion, " + + "upstream needs to limit rate at this time.", + positiveInt(), + 20 * 1024 * 1024 + ); + + public static final ConfigOption RATE_LIMIT_WRITE = + new ConfigOption<>( + "rate_limit.write", + "The max rate(items/s) to add/update/delete vertices/edges.", + rangeInt(0, Integer.MAX_VALUE), + 0 + ); + + public static final ConfigOption RATE_LIMIT_READ = + new ConfigOption<>( + "rate_limit.read", + "The max rate(times/s) to execute query of vertices/edges.", + rangeInt(0, Integer.MAX_VALUE), + 0 + ); + + public static final ConfigOption TASK_SCHEDULE_PERIOD = + new ConfigOption<>( + "task.schedule_period", + "Period time when scheduler to schedule task", + rangeInt(0L, Long.MAX_VALUE), + 10L + ); + + public static final ConfigOption TASK_WAIT_TIMEOUT = + new ConfigOption<>( + "task.wait_timeout", + "Timeout in seconds for waiting for the task to " + + "complete, such as when truncating or clearing the " + + "backend.", + rangeInt(0L, Long.MAX_VALUE), + 10L + ); + + public static final ConfigOption TASK_INPUT_SIZE_LIMIT = + new ConfigOption<>( + "task.input_size_limit", + "The job input size limit in bytes.", + rangeInt(0L, Bytes.GB), + 16 * Bytes.MB + ); + + public static final ConfigOption TASK_RESULT_SIZE_LIMIT = + new ConfigOption<>( + "task.result_size_limit", + "The job result size limit in bytes.", + rangeInt(0L, Bytes.GB), + 16 * Bytes.MB + ); + + public static final ConfigOption TASK_TTL_DELETE_BATCH = + new ConfigOption<>( + "task.ttl_delete_batch", + "The batch size used to delete expired data.", + rangeInt(1, 500), + 1 + ); + + public static final ConfigOption SCHEDULER_TYPE = + new ConfigOption<>( + "task.scheduler_type", + "The type of scheduler used in distribution system.", + allowValues("local", "distributed"), + "local" + ); + + public static final ConfigOption TASK_SYNC_DELETION = + new ConfigOption<>( + "task.sync_deletion", + "Whether to delete schema or expired data synchronously.", + disallowEmpty(), + false + ); + + public static final ConfigOption TASK_RETRY = + new ConfigOption<>( + "task.retry", + "Task retry times.", + rangeInt(0, 3), + 0 + ); + + public static final ConfigOption STORE_CONN_DETECT_INTERVAL = + new ConfigOption<>( + "store.connection_detect_interval", + "The interval in seconds for detecting connections, " + + "if the idle time of a connection exceeds this value, " + + "detect it and reconnect if needed before using, " + + "value 0 means detecting every time.", + rangeInt(0L, Long.MAX_VALUE), + 600L + ); + + public static final ConfigOption VERTEX_DEFAULT_LABEL = + new ConfigOption<>( + "vertex.default_label", + "The default vertex label.", + disallowEmpty(), + "vertex" + ); + + public static final ConfigOption VERTEX_CHECK_CUSTOMIZED_ID_EXIST = + new ConfigOption<>( + "vertex.check_customized_id_exist", + "Whether to check the vertices exist for those using " + + "customized id strategy.", + disallowEmpty(), + false + ); + + public static final ConfigOption VERTEX_REMOVE_LEFT_INDEX = + new ConfigOption<>( + "vertex.remove_left_index_at_overwrite", + "Whether remove left index at overwrite.", + disallowEmpty(), + false + ); + + public static final ConfigOption VERTEX_ADJACENT_VERTEX_EXIST = + new ConfigOption<>( + "vertex.check_adjacent_vertex_exist", + "Whether to check the adjacent vertices of edges exist.", + disallowEmpty(), + false + ); + + public static final ConfigOption VERTEX_ADJACENT_VERTEX_LAZY = + new ConfigOption<>( + "vertex.lazy_load_adjacent_vertex", + "Whether to lazy load adjacent vertices of edges.", + disallowEmpty(), + true + ); + + public static final ConfigOption VERTEX_PART_EDGE_COMMIT_SIZE = + new ConfigOption<>( + "vertex.part_edge_commit_size", + "Whether to enable the mode to commit part of edges of " + + "vertex, enabled if commit size > 0, 0 meas disabled.", + rangeInt(0, (int) Query.DEFAULT_CAPACITY), + 5000 + ); + + public static final ConfigOption VERTEX_ENCODE_PK_NUMBER = + new ConfigOption<>( + "vertex.encode_primary_key_number", + "Whether to encode number value of primary key " + + "in vertex id.", + disallowEmpty(), + true + ); + + public static final ConfigOption VERTEX_TX_CAPACITY = + new ConfigOption<>( + "vertex.tx_capacity", + "The max size(items) of vertices(uncommitted) in " + + "transaction.", + rangeInt((int)COMMIT_BATCH, 1000000), + 10000 + ); + + public static final ConfigOption QUERY_IGNORE_INVALID_DATA = + new ConfigOption<>( + "query.ignore_invalid_data", + "Whether to ignore invalid data of vertex or edge.", + disallowEmpty(), + true + ); + + public static final ConfigOption QUERY_OPTIMIZE_AGGR_BY_INDEX = + new ConfigOption<>( + "query.optimize_aggregate_by_index", + "Whether to optimize aggregate query(like count) by index.", + disallowEmpty(), + false + ); + + public static final ConfigOption QUERY_BATCH_SIZE = + new ConfigOption<>( + "query.batch_size", + "The size of each batch when querying by batch.", + rangeInt(1, (int) Query.DEFAULT_CAPACITY), + 1000 + ); + + public static final ConfigOption QUERY_PAGE_SIZE = + new ConfigOption<>( + "query.page_size", + "The size of each page when querying by paging.", + rangeInt(1, (int) Query.DEFAULT_CAPACITY), + 500 + ); + + public static final ConfigOption QUERY_INDEX_INTERSECT_THRESHOLD = + new ConfigOption<>( + "query.index_intersect_threshold", + "The maximum number of intermediate results to " + + "intersect indexes when querying by multiple single " + + "index properties.", + rangeInt(1, (int) Query.DEFAULT_CAPACITY), + 1000 + ); + + public static final ConfigOption QUERY_RAMTABLE_ENABLE = + new ConfigOption<>( + "query.ramtable_enable", + "Whether to enable ramtable for query of adjacent edges.", + disallowEmpty(), + false + ); + + public static final ConfigOption QUERY_RAMTABLE_VERTICES_CAPACITY = + new ConfigOption<>( + "query.ramtable_vertices_capacity", + "The maximum number of vertices in ramtable, " + + "generally the largest vertex id is used as capacity.", + rangeInt(1L, Integer.MAX_VALUE * 2L), + 10000000L + ); + + public static final ConfigOption QUERY_RAMTABLE_EDGES_CAPACITY = + new ConfigOption<>( + "query.ramtable_edges_capacity", + "The maximum number of edges in ramtable, " + + "include OUT and IN edges.", + rangeInt(1, Integer.MAX_VALUE), + 20000000 + ); + + /** + * The schema name rule: + * 1. Not allowed end with spaces + * 2. Not allowed start with '~' + */ + public static final ConfigOption SCHEMA_ILLEGAL_NAME_REGEX = + new ConfigOption<>( + "schema.illegal_name_regex", + "The regex specified the illegal format for schema name.", + disallowEmpty(), + ".*\\s+$|~.*" + ); + + public static final ConfigOption SCHEMA_CACHE_CAPACITY = + new ConfigOption<>( + "schema.cache_capacity", + "The max cache size(items) of schema cache.", + rangeInt(0L, Long.MAX_VALUE), + 10000L + ); + + public static final ConfigOption VERTEX_CACHE_TYPE = + new ConfigOption<>( + "vertex.cache_type", + "The type of vertex cache, allowed values are [l1, l2].", + allowValues("l1", "l2"), + "l2" + ); + + public static final ConfigOption VERTEX_CACHE_CAPACITY = + new ConfigOption<>( + "vertex.cache_capacity", + "The max cache size(items) of vertex cache.", + rangeInt(0L, Long.MAX_VALUE), + (1000 * 1000 * 10L) + ); + + public static final ConfigOption VERTEX_CACHE_EXPIRE = + new ConfigOption<>( + "vertex.cache_expire", + "The expiration time in seconds of vertex cache.", + rangeInt(0, Integer.MAX_VALUE), + (60 * 10) + ); + + public static final ConfigOption EDGE_CACHE_TYPE = + new ConfigOption<>( + "edge.cache_type", + "The type of edge cache, allowed values are [l1, l2].", + allowValues("l1", "l2"), + "l2" + ); + + public static final ConfigOption EDGE_CACHE_CAPACITY = + new ConfigOption<>( + "edge.cache_capacity", + "The max cache size(items) of edge cache.", + rangeInt(0L, Long.MAX_VALUE), + (1000 * 1000 * 1L) + ); + + public static final ConfigOption EDGE_CACHE_EXPIRE = + new ConfigOption<>( + "edge.cache_expire", + "The expiration time in seconds of edge cache.", + rangeInt(0, Integer.MAX_VALUE), + (60 * 10) + ); + + public static final ConfigOption SNOWFLAKE_WORKER_ID = + new ConfigOption<>( + "snowflake.worker_id", + "The worker id of snowflake id generator.", + disallowEmpty(), + 0L + ); + + public static final ConfigOption SNOWFLAKE_DATACENTER_ID = + new ConfigOption<>( + "snowflake.datacenter_id", + "The datacenter id of snowflake id generator.", + disallowEmpty(), + 0L + ); + + public static final ConfigOption SNOWFLAKE_FORCE_STRING = + new ConfigOption<>( + "snowflake.force_string", + "Whether to force the snowflake long id to be a string.", + disallowEmpty(), + false + ); + + public static final ConfigOption TEXT_ANALYZER = + new ConfigOption<>( + "search.text_analyzer", + "Choose a text analyzer for searching the " + + "vertex/edge properties, available type are " + + "[ansj, hanlp, smartcn, jieba, jcseg, " + + "mmseg4j, ikanalyzer].", + disallowEmpty(), + "ikanalyzer" + ); + + public static final ConfigOption TEXT_ANALYZER_MODE = + new ConfigOption<>( + "search.text_analyzer_mode", + "Specify the mode for the text analyzer, " + + "the available mode of analyzer are " + + "ansj: [BaseAnalysis, IndexAnalysis, ToAnalysis, " + + "NlpAnalysis], " + + "hanlp: [standard, nlp, index, nShort, shortest, speed], " + + "smartcn: [], " + + "jieba: [SEARCH, INDEX], " + + "jcseg: [Simple, Complex], " + + "mmseg4j: [Simple, Complex, MaxWord], " + + "ikanalyzer: [smart, max_word]" + + "}.", + disallowEmpty(), + "smart" + ); + + public static final ConfigOption COMPUTER_CONFIG = + new ConfigOption<>( + "computer.config", + "The config file path of computer job.", + disallowEmpty(), + "./conf/computer.yaml" + ); + + public static final ConfigOption OLTP_CONCURRENT_THREADS = + new ConfigOption<>( + "oltp.concurrent_threads", + "Thread number to concurrently execute oltp algorithm.", + rangeInt(0, 65535), + 10 + ); + + public static final ConfigOption OLTP_CONCURRENT_DEPTH = + new ConfigOption<>( + "oltp.concurrent_depth", + "The min depth to enable concurrent oltp algorithm.", + rangeInt(0, 65535), + 10 + ); + + public static final ConfigConvOption OLTP_COLLECTION_TYPE = + new ConfigConvOption<>( + "oltp.collection_type", + "The implementation type of collections " + + "used in oltp algorithm.", + allowValues("JCF", "EC", "FU"), + CollectionType::valueOf, + "EC" + ); + + public static final ConfigOption PD_PEERS = new ConfigOption<>( + "pd.peers", + "The addresses of pd nodes, separated with commas.", + disallowEmpty(), + "127.0.0.1:8686" + ); + + public static final ConfigOption MEMORY_MODE = new ConfigOption<>( + "memory.mode", + "The memory mode used for query in HugeGraph.", + disallowEmpty(), + "off-heap" + ); + + public static final ConfigOption MAX_MEMORY_CAPACITY = new ConfigOption<>( + "memory.max_capacity", + "The maximum memory capacity that can be managed for all queries in HugeGraph.", + nonNegativeInt(), + Bytes.GB + ); + + public static final ConfigOption ONE_QUERY_MAX_MEMORY_CAPACITY = new ConfigOption<>( + "memory.one_query_max_capacity", + "The maximum memory capacity that can be managed for a query in HugeGraph.", + nonNegativeInt(), + Bytes.MB * 100 + ); + + public static final ConfigOption MEMORY_ALIGNMENT = new ConfigOption<>( + "memory.alignment", + "The alignment used for round memory size.", + nonNegativeInt(), + 8L + ); + + public static final ConfigOption GRAPH_SPACE = + new ConfigOption<>( + "graphspace", + "The graph space name.", + null, + "DEFAULT" + ); +} diff --git a/hugegraph-struct/src/main/java/org/apache/hugegraph/query/Aggregate.java b/hugegraph-struct/src/main/java/org/apache/hugegraph/query/Aggregate.java new file mode 100644 index 0000000000..38f1365f67 --- /dev/null +++ b/hugegraph-struct/src/main/java/org/apache/hugegraph/query/Aggregate.java @@ -0,0 +1,61 @@ +/* + * Copyright 2017 HugeGraph Authors + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.query; + +import java.util.Iterator; + +@Deprecated +public class Aggregate

{ + + private final AggregateFuncDefine

func; + private final String column; + + public Aggregate(AggregateFuncDefine func, String column) { + this.func = func; + this.column = column; + } + + public AggregateFuncDefine func() { + return this.func; + } + + public String column() { + return this.column; + } + + public boolean countAll() { + return this.func.countAll() && this.column == null; + } + + public P reduce(Iterator

results) { + return this.func.reduce(results); + } + + public P defaultValue() { + return this.func.defaultValue(); + } + + @Override + public String toString() { + return String.format("%s(%s)", this.func.string(), + this.column == null ? "*" : this.column); + } + +} diff --git a/hugegraph-struct/src/main/java/org/apache/hugegraph/query/AggregateFuncDefine.java b/hugegraph-struct/src/main/java/org/apache/hugegraph/query/AggregateFuncDefine.java new file mode 100644 index 0000000000..2ef23df42a --- /dev/null +++ b/hugegraph-struct/src/main/java/org/apache/hugegraph/query/AggregateFuncDefine.java @@ -0,0 +1,33 @@ +/* + * Copyright 2017 HugeGraph Authors + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.query; + +import java.util.Iterator; + +/** + * 聚合方式定义 + * @param

+ */ +public interface AggregateFuncDefine

{ + String string(); + P defaultValue(); + P reduce(Iterator

results); + boolean countAll(); +} diff --git a/hugegraph-struct/src/main/java/org/apache/hugegraph/query/Condition.java b/hugegraph-struct/src/main/java/org/apache/hugegraph/query/Condition.java new file mode 100644 index 0000000000..52c1a03ee8 --- /dev/null +++ b/hugegraph-struct/src/main/java/org/apache/hugegraph/query/Condition.java @@ -0,0 +1,1045 @@ +/* + * 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.query; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Date; +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.BiPredicate; +import java.util.regex.Pattern; + +import org.apache.commons.lang.ArrayUtils; +import org.apache.commons.text.similarity.LevenshteinDistance; +import org.apache.hugegraph.id.Id; +import org.apache.hugegraph.backend.Shard; +import org.apache.hugegraph.structure.BaseElement; +import org.apache.hugegraph.structure.BaseProperty; +import org.apache.hugegraph.type.define.HugeKeys; +import org.apache.hugegraph.util.Bytes; +import org.apache.hugegraph.util.DateUtil; +import org.apache.hugegraph.util.E; +import org.apache.hugegraph.util.NumericUtil; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; + +public abstract class Condition { + + public enum ConditionType { + NONE, + RELATION, + AND, + OR, + NOT + } + + public enum RelationType implements BiPredicate { + + EQ("==", RelationType::equals), + + GT(">", (v1, v2) -> { + return compare(v1, v2) > 0; + }), + + GTE(">=", (v1, v2) -> { + return compare(v1, v2) >= 0; + }), + + LT("<", (v1, v2) -> { + return compare(v1, v2) < 0; + }), + + LTE("<=", (v1, v2) -> { + return compare(v1, v2) <= 0; + }), + + NEQ("!=", (v1, v2) -> { + return compare(v1, v2) != 0; + }), + + IN("in", null, Collection.class, (v1, v2) -> { + assert v2 != null; + return ((Collection) v2).contains(v1); + }), + + NOT_IN("notin", null, Collection.class, (v1, v2) -> { + assert v2 != null; + return !((Collection) v2).contains(v1); + }), + + PREFIX("prefix", Id.class, Id.class, (v1, v2) -> { + assert v2 != null; + return v1 != null && Bytes.prefixWith(((Id) v2).asBytes(), + ((Id) v1).asBytes()); + }), + + TEXT_ANALYZER_CONTAINS("analyzercontains", String.class, + String.class, (v1, v2) -> { + return v1 != null && + ((String) v1).toLowerCase().contains(((String) v2).toLowerCase()); + }), + + TEXT_CONTAINS("textcontains", String.class, String.class, (v1, v2) -> { + // TODO: support collection-property textcontains + return v1 != null && ((String) v1).contains((String) v2); + }), + TEXT_MATCH_REGEX("textmatchregex", String.class, String.class, + (v1, v2) -> { + return Pattern.matches((String) v2, (String) v1); + }), + + TEXT_MATCH_EDIT_DISTANCE("texteditdistance", String.class, + String.class, (v1, v2) -> { + String content = (String) v2; + String distanceStr = content.substring(0, content.indexOf("#")); + int distance = Integer.valueOf(distanceStr); + String target = content.substring(content.indexOf("#") + 1); + return minEditDistance((String) v1, target) <= distance; + }), + TEXT_NOT_CONTAINS("textnotcontains", String.class, + String.class, (v1, v2) -> { + return v1 == null && v2 != null || + !((String) v1).toLowerCase().contains(((String) v2).toLowerCase()); + }), + TEXT_PREFIX("textprefix", String.class, String.class, (v1, v2) -> { + return ((String) v1).startsWith((String) v2); + }), + TEXT_NOT_PREFIX("textnotprefix", String.class, + String.class, (v1, v2) -> { + return !((String) v1).startsWith((String) v2); + }), + TEXT_SUFFIX("textsuffix", String.class, String.class, (v1, v2) -> { + return ((String) v1).endsWith((String) v2); + }), + TEXT_NOT_SUFFIX("textnotsuffix", String.class, + String.class, (v1, v2) -> { + return !((String) v1).endsWith((String) v2); + }), + + TEXT_CONTAINS_ANY("textcontainsany", String.class, Collection.class, (v1, v2) -> { + assert v2 != null; + if (v1 == null) { + return false; + } + + @SuppressWarnings("unchecked") + Collection words = (Collection) v2; + + for (String word : words) { + if (((String) v1).contains(word)) { + return true; + } + } + return false; + }), + + CONTAINS("contains", Collection.class, null, (v1, v2) -> { + assert v2 != null; + return v1 != null && ((Collection) v1).contains(v2); + }), + + CONTAINS_VALUE("containsv", Map.class, null, (v1, v2) -> { + assert v2 != null; + return v1 != null && ((Map) v1).containsValue(v2); + }), + + CONTAINS_KEY("containsk", Map.class, null, (v1, v2) -> { + assert v2 != null; + return v1 != null && ((Map) v1).containsKey(v2); + }), + + TEXT_CONTAINS_FUZZY("textcontainsfuzzy", String.class, + String.class, (v1, v2) -> { + for (String token : tokenize(((String) v1).toLowerCase())) { + if (isFuzzy(((String) v2).toLowerCase(), token)) { + return true; + } + } + return false; + }), + TEXT_FUZZY("textfuzzy", String.class, String.class, (v1, v2) -> { + return isFuzzy((String) v2, (String) v1); + }), + TEXT_CONTAINS_REGEX("textcontainsregex", String.class, + String.class, (v1, v2) -> { + for (String token : tokenize(((String) v1).toLowerCase())) { + if (token.matches((String) v2)) { + return true; + } + } + return false; + }), + TEXT_REGEX("textregex", String.class, String.class, (v1, v2) -> { + return ((String) v1).matches((String) v2); + }), + + SCAN("scan", (v1, v2) -> { + assert v2 != null; + /* + * TODO: we still have no way to determine accurately, since + * some backends may scan with token(column) like cassandra. + */ + return true; + }); + + private final String operator; + private final BiFunction tester; + private final Class v1Class; + private final Class v2Class; + + RelationType(String op, + BiFunction tester) { + this(op, null, null, tester); + } + + RelationType(String op, Class v1Class, Class v2Class, + BiFunction tester) { + this.operator = op; + this.tester = tester; + this.v1Class = v1Class; + this.v2Class = v2Class; + } + + public String string() { + return this.operator; + } + + protected static int minEditDistance(String source, String target) { + E.checkArgument(source != null, "The source could not be null"); + E.checkArgument(target != null, "The target could not be null"); + + int sourceLen = source.length(); + int targetLen = target.length(); + if(sourceLen == 0){ + return targetLen; + } + if(targetLen == 0){ + return sourceLen; + } + + int[][] arr = new int[sourceLen + 1][targetLen + 1]; + for(int i = 0; i < sourceLen + 1; i++){ + arr[i][0] = i; + } + for(int j = 0; j < targetLen + 1; j++){ + arr[0][j] = j; + } + Character sourceChar = null; + Character targetChar = null; + for(int i = 1; i < sourceLen + 1; i++){ + sourceChar = source.charAt(i - 1); + for(int j = 1; j < targetLen + 1; j++){ + targetChar = target.charAt(j - 1); + if(sourceChar.equals(targetChar)){ + arr[i][j] = arr[i - 1][j - 1]; + }else{ + arr[i][j] = (Math.min(Math.min(arr[i - 1][j], + arr[i][j - 1]), arr[i - 1][j - 1])) + 1; + } + } + } + return arr[sourceLen][targetLen]; + } + + /** + * Determine two values of any type equal + * + * @param first is actual value + * @param second is value in query condition + * @return true if equal, otherwise false + */ + private static boolean equals(final Object first, + final Object second) { + assert second != null; + if (first instanceof Id) { + if (second instanceof String) { + return second.equals(((Id) first).asString()); + } else if (second instanceof Long) { + return second.equals(((Id) first).asLong()); + } + } else if (second instanceof Number) { + return compare(first, second) == 0; + } else if (second.getClass().isArray()) { + return ArrayUtils.isEquals(first, second); + } + + return Objects.equals(first, second); + } + + /** + * Determine two numbers equal + * + * @param first is actual value, might be Number/Date or String, It is + * probably that the `first` is serialized to String. + * @param second is value in query condition, must be Number/Date + * @return the value 0 if first is numerically equal to second; + * a value less than 0 if first is numerically less than + * second; and a value greater than 0 if first is + * numerically greater than second. + */ + private static int compare(final Object first, final Object second) { + assert second != null; + if (second instanceof Number) { + return NumericUtil.compareNumber(first == null ? 0 : first, + (Number) second); + } else if (second instanceof Date) { + return compareDate(first, (Date) second); + } + + throw new IllegalArgumentException(String.format( + "Can't compare between %s(%s) and %s(%s)", first, + first == null ? null : first.getClass().getSimpleName(), + second, second.getClass().getSimpleName())); + } + + private static int compareDate(Object first, Date second) { + if (first == null) { + first = DateUtil.DATE_ZERO; + } + if (first instanceof Date) { + return ((Date) first).compareTo(second); + } + + throw new IllegalArgumentException(String.format( + "Can't compare between %s(%s) and %s(%s)", + first, first.getClass().getSimpleName(), + second, second.getClass().getSimpleName())); + } + + public static List tokenize(String str) { + final ArrayList tokens = new ArrayList<>(); + int previous = 0; + for (int p = 0; p < str.length(); p++) { + if (!Character.isLetterOrDigit(str.charAt(p))) { + if (p > previous + 1) { + tokens.add(str.substring(previous, p)); + } + previous = p + 1; + } + } + if (previous + 1 < str.length()) { + tokens.add(str.substring(previous)); + } + return tokens; + } + + private static final LevenshteinDistance ONE_LEVENSHTEIN_DISTANCE = + new LevenshteinDistance(1); + private static final LevenshteinDistance TWO_LEVENSHTEIN_DISTANCE = + new LevenshteinDistance(2); + + private static boolean isFuzzy(String term, String value){ + int distance; + term = term.trim(); + int length = term.length(); + if (length < 3) { + return term.equals(value); + } else if (length < 6) { + distance = ONE_LEVENSHTEIN_DISTANCE.apply(value, term); + return distance <= 1 && distance >= 0; + } else { + distance = TWO_LEVENSHTEIN_DISTANCE.apply(value, term); + return distance <= 2 && distance >= 0; + } + } + + private void checkBaseType(Object value, Class clazz) { + if (!clazz.isInstance(value)) { + String valueClass = value == null ? "null" : + value.getClass().getSimpleName(); + E.checkArgument(false, + "Can't execute `%s` on type %s, expect %s", + this.operator, valueClass, + clazz.getSimpleName()); + } + } + + private void checkValueType(Object value, Class clazz) { + if (!clazz.isInstance(value)) { + String valueClass = value == null ? "null" : + value.getClass().getSimpleName(); + E.checkArgument(false, + "Can't test '%s'(%s) for `%s`, expect %s", + value, valueClass, this.operator, + clazz.getSimpleName()); + } + } + + @Override + public boolean test(Object first, Object second) { + E.checkState(this.tester != null, "Can't test %s", this.name()); + E.checkArgument(second != null, + "Can't test null value for `%s`", this.operator); + if (this.v1Class != null) { + this.checkBaseType(first, this.v1Class); + } + if (this.v2Class != null) { + this.checkValueType(second, this.v2Class); + } + return this.tester.apply(first, second); + } + + public boolean isFuzzyType() { + return this == TEXT_CONTAINS || this == TEXT_NOT_CONTAINS || + this == TEXT_NOT_PREFIX || this == TEXT_PREFIX || + this == TEXT_SUFFIX || this == TEXT_NOT_SUFFIX || + this == TEXT_CONTAINS_FUZZY || this == TEXT_FUZZY || + this == TEXT_CONTAINS_REGEX || this == TEXT_REGEX || + this == TEXT_CONTAINS_ANY || this == TEXT_MATCH_REGEX || + this == TEXT_MATCH_EDIT_DISTANCE; + } + + public boolean isRangeType() { + return ImmutableSet.of(GT, GTE, LT, LTE).contains(this); + } + + public boolean isSearchType() { + return this == TEXT_CONTAINS || this == TEXT_CONTAINS_ANY; + } + + public boolean isSecondaryType() { + return this == EQ; + } + } + + public abstract ConditionType type(); + + public abstract boolean isSysprop(); + + public abstract List relations(); + + public abstract boolean test(Object value); + + public abstract boolean test(BaseElement element); + + public abstract Condition copy(); + + public abstract Condition replace(Relation from, Relation to); + + public Condition and(Condition other) { + return new And(this, other); + } + + public Condition or(Condition other) { + return new Or(this, other); + } + + public boolean isRelation() { + return this.type() == ConditionType.RELATION; + } + + public boolean isLogic() { + return this.type() == ConditionType.AND || + this.type() == ConditionType.OR || + this.type() == ConditionType.NOT; + } + + public boolean isFlattened() { + return this.isRelation(); + } + + public static Condition and(Condition left, Condition right) { + return new And(left, right); + } + + public static Condition or(Condition left, Condition right) { + return new Or(left, right); + } + + public static Condition not(Condition condition) { + return new Not(condition); + } + + public static Relation eq(HugeKeys key, Object value) { + return new SyspropRelation(key, RelationType.EQ, value); + } + + public static Relation gt(HugeKeys key, Object value) { + return new SyspropRelation(key, RelationType.GT, value); + } + + public static Relation gte(HugeKeys key, Object value) { + return new SyspropRelation(key, RelationType.GTE, value); + } + + public static Relation lt(HugeKeys key, Object value) { + return new SyspropRelation(key, RelationType.LT, value); + } + + public static Relation lte(HugeKeys key, Object value) { + return new SyspropRelation(key, RelationType.LTE, value); + } + + public static Relation neq(HugeKeys key, Object value) { + return new SyspropRelation(key, RelationType.NEQ, value); + } + + public static Condition in(HugeKeys key, List value) { + return new SyspropRelation(key, RelationType.IN, value); + } + + public static Condition nin(HugeKeys key, List value) { + return new SyspropRelation(key, RelationType.NOT_IN, value); + } + + public static Condition prefix(HugeKeys key, Id value) { + return new SyspropRelation(key, RelationType.PREFIX, value); + } + + public static Condition containsValue(HugeKeys key, Object value) { + return new SyspropRelation(key, RelationType.CONTAINS_VALUE, value); + } + + public static Condition containsKey(HugeKeys key, Object value) { + return new SyspropRelation(key, RelationType.CONTAINS_KEY, value); + } + + public static Condition contains(HugeKeys key, Object value) { + return new SyspropRelation(key, RelationType.CONTAINS, value); + } + + public static Condition scan(String start, String end) { + Shard value = new Shard(start, end, 0); + return new SyspropRelation(HugeKeys.ID, RelationType.SCAN, value); + } + + public static Relation eq(Id key, Object value) { + return new UserpropRelation(key, RelationType.EQ, value); + } + + public static Relation gt(Id key, Object value) { + return new UserpropRelation(key, RelationType.GT, value); + } + + public static Relation gte(Id key, Object value) { + return new UserpropRelation(key, RelationType.GTE, value); + } + + public static Relation lt(Id key, Object value) { + return new UserpropRelation(key, RelationType.LT, value); + } + + public static Relation lte(Id key, Object value) { + return new UserpropRelation(key, RelationType.LTE, value); + } + + public static Relation neq(Id key, Object value) { + return new UserpropRelation(key, RelationType.NEQ, value); + } + + public static Relation in(Id key, List value) { + return new UserpropRelation(key, RelationType.IN, value); + } + + public static Relation nin(Id key, List value) { + return new UserpropRelation(key, RelationType.NOT_IN, value); + } + + public static Relation textContains(Id key, String word) { + return new UserpropRelation(key, RelationType.TEXT_CONTAINS, word); + } + + public static Relation textContainsAny(Id key, Set words) { + return new UserpropRelation(key, RelationType.TEXT_CONTAINS_ANY, words); + } + + public static Condition contains(Id key, Object value) { + return new UserpropRelation(key, RelationType.CONTAINS, value); + } + + /** + * Condition defines + */ + public abstract static class BinCondition extends Condition { + + private Condition left; + private Condition right; + + public BinCondition(Condition left, Condition right) { + E.checkNotNull(left, "left condition"); + E.checkNotNull(right, "right condition"); + this.left = left; + this.right = right; + } + + public Condition left() { + return this.left; + } + + public Condition right() { + return this.right; + } + + @Override + public boolean isSysprop() { + return this.left.isSysprop() && this.right.isSysprop(); + } + + @Override + public List relations() { + List list = new ArrayList<>(this.left.relations()); + list.addAll(this.right.relations()); + return list; + } + + @Override + public Condition replace(Relation from, Relation to) { + this.left = this.left.replace(from, to); + this.right = this.right.replace(from, to); + return this; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(64); + sb.append(this.left).append(' '); + sb.append(this.type().name()).append(' '); + sb.append(this.right); + return sb.toString(); + } + + @Override + public boolean equals(Object object) { + if (!(object instanceof BinCondition)) { + return false; + } + BinCondition other = (BinCondition) object; + return this.type().equals(other.type()) && + this.left().equals(other.left()) && + this.right().equals(other.right()); + } + + @Override + public int hashCode() { + return this.type().hashCode() ^ + this.left().hashCode() ^ + this.right().hashCode(); + } + } + + public static class And extends BinCondition { + + public And(Condition left, Condition right) { + super(left, right); + } + + @Override + public ConditionType type() { + return ConditionType.AND; + } + + @Override + public boolean test(Object value) { + return this.left().test(value) && this.right().test(value); + } + + @Override + public boolean test(BaseElement element) { + return this.left().test(element) && this.right().test(element); + } + + @Override + public Condition copy() { + return new And(this.left().copy(), this.right().copy()); + } + } + + public static class Or extends BinCondition { + + public Or(Condition left, Condition right) { + super(left, right); + } + + @Override + public ConditionType type() { + return ConditionType.OR; + } + + @Override + public boolean test(Object value) { + return this.left().test(value) || this.right().test(value); + } + + @Override + public boolean test(BaseElement element) { + return this.left().test(element) || this.right().test(element); + } + + @Override + public Condition copy() { + return new Or(this.left().copy(), this.right().copy()); + } + } + + public static class Not extends Condition { + + Condition condition; + + public Not(Condition condition) { + super(); + this.condition = condition; + } + + public Condition condition() { + return condition; + } + + @Override + public ConditionType type() { + return ConditionType.NOT; + } + + @Override + public boolean test(Object value) { + return !this.condition.test(value); + } + + @Override + public boolean test(BaseElement element) { + return !this.condition.test(element); + } + + @Override + public Condition copy() { + return new Not(this.condition.copy()); + } + + @Override + public boolean isSysprop() { + return this.condition.isSysprop(); + } + + @Override + public List relations() { + return new ArrayList(this.condition.relations()); + } + + @Override + public Condition replace(Relation from, Relation to) { + this.condition = this.condition.replace(from, to); + return this; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(64); + sb.append(this.type().name()).append(' '); + sb.append(this.condition); + return sb.toString(); + } + + @Override + public boolean equals(Object object) { + if (!(object instanceof Not)) { + return false; + } + Not other = (Not) object; + return this.type().equals(other.type()) && + this.condition.equals(other.condition()); + } + + @Override + public int hashCode() { + return this.type().hashCode() ^ + this.condition.hashCode(); + } + } + + public abstract static class Relation extends Condition { + + // Relational operator (like: =, >, <, in, ...) + protected RelationType relation; + // Single-type value or a list of single-type value + protected Object value; + + // The key serialized(code/string) by backend store. + protected Object serialKey; + // The value serialized(code/string) by backend store. + protected Object serialValue; + + protected static final Set UNFLATTEN_RELATION_TYPES = + ImmutableSet.of(RelationType.IN, RelationType.NOT_IN, + RelationType.TEXT_CONTAINS_ANY); + + @Override + public ConditionType type() { + return ConditionType.RELATION; + } + + public RelationType relation() { + return this.relation; + } + + public Object value() { + return this.value; + } + + public void value(Object value) { + this.value = value; + } + + public void serialKey(Object key) { + this.serialKey = key; + } + + public Object serialKey() { + return this.serialKey != null ? this.serialKey : this.key(); + } + + public void serialValue(Object value) { + this.serialValue = value; + } + + public Object serialValue() { + return this.serialValue != null ? this.serialValue : this.value(); + } + + @Override + public boolean test(Object value) { + return this.relation.test(value, this.value()); + } + + @Override + public boolean isFlattened() { + return !UNFLATTEN_RELATION_TYPES.contains(this.relation); + } + + @Override + public List relations() { + return ImmutableList.of(this); + } + + @Override + public Condition replace(Relation from, Relation to) { + if (this == from) { + return to; + } else { + return this; + } + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(64); + sb.append(this.key()).append(' '); + sb.append(this.relation.string()).append(' '); + sb.append(this.value); + return sb.toString(); + } + + @Override + public boolean equals(Object object) { + if (!(object instanceof Relation)) { + return false; + } + Relation other = (Relation) object; + return this.relation().equals(other.relation()) && + this.key().equals(other.key()) && + this.value().equals(other.value()); + } + + @Override + public int hashCode() { + return this.type().hashCode() ^ + this.relation().hashCode() ^ + this.key().hashCode() ^ + this.value().hashCode(); + } + + @Override + public abstract boolean isSysprop(); + + public abstract Object key(); + + @Override + public abstract Relation copy(); + } + + public static class SyspropRelation extends Relation { + + private final HugeKeys key; + + public SyspropRelation(HugeKeys key, Object value) { + this(key, RelationType.EQ, value); + } + + public SyspropRelation(HugeKeys key, RelationType op, Object value) { + E.checkNotNull(op, "relation type"); + this.key = key; + this.relation = op; + this.value = value; + } + + @Override + public HugeKeys key() { + return this.key; + } + + @Override + public boolean isSysprop() { + return true; + } + + @Override + public boolean test(BaseElement element) { + E.checkNotNull(element, "element"); + Object value = element.sysprop(this.key); + return this.relation.test(value, this.value()); + } + + @Override + public Relation copy() { + Relation clone = new SyspropRelation(this.key, this.relation(), + this.value); + clone.serialKey(this.serialKey); + clone.serialValue(this.serialValue); + return clone; + } + } + + public static class FlattenSyspropRelation extends SyspropRelation { + + public FlattenSyspropRelation(SyspropRelation relation) { + super(relation.key(), relation.relation(), relation.value()); + } + + @Override + public boolean isFlattened() { + return true; + } + } + + public static class UserpropRelation extends Relation { + + // Id of property key + private final Id key; + + public UserpropRelation(Id key, Object value) { + this(key, RelationType.EQ, value); + } + + public UserpropRelation(Id key, RelationType op, Object value) { + E.checkNotNull(op, "relation type"); + this.key = key; + this.relation = op; + this.value = value; + } + + @Override + public Id key() { + return this.key; + } + + @Override + public boolean isSysprop() { + return false; + } + + @Override + public boolean test(BaseElement element) { + BaseProperty prop = element.getProperty(this.key); + Object value = prop != null ? prop.value() : null; + if (value == null) { + /* + * Fix #611 + * TODO: It's possible some scenes can't be returned false + * directly, such as: EQ with p1 == null, it should be returned + * true, but the query has(p, null) is not allowed by + * TraversalUtil.validPredicateValue(). + */ + return false; + } + return this.relation.test(value, this.value()); + } + + @Override + public Relation copy() { + Relation clone = new UserpropRelation(this.key, this.relation(), + this.value); + clone.serialKey(this.serialKey); + clone.serialValue(this.serialValue); + return clone; + } + } + + public static class RangeConditions { + + private Object keyEq = null; + private Object keyMin = null; + private boolean keyMinEq = false; + private Object keyMax = null; + private boolean keyMaxEq = false; + + public RangeConditions(List conditions) { + for (Condition c : conditions) { + Relation r = (Relation) c; + switch (r.relation()) { + case EQ: + this.keyEq = r.value(); + break; + case GTE: + this.keyMinEq = true; + this.keyMin = r.value(); + break; + case GT: + this.keyMin = r.value(); + break; + case LTE: + this.keyMaxEq = true; + this.keyMax = r.value(); + break; + case LT: + this.keyMax = r.value(); + break; + default: + E.checkArgument(false, "Unsupported relation '%s'", + r.relation()); + } + } + } + + public Object keyEq() { + return this.keyEq; + } + + public Object keyMin() { + return this.keyMin; + } + + public Object keyMax() { + return this.keyMax; + } + + public boolean keyMinEq() { + return this.keyMinEq; + } + + public boolean keyMaxEq() { + return this.keyMaxEq; + } + + public boolean hasRange() { + return this.keyMin != null || this.keyMax != null; + } + } +} diff --git a/hugegraph-struct/src/main/java/org/apache/hugegraph/query/ConditionQuery.java b/hugegraph-struct/src/main/java/org/apache/hugegraph/query/ConditionQuery.java new file mode 100644 index 0000000000..5719f745d9 --- /dev/null +++ b/hugegraph-struct/src/main/java/org/apache/hugegraph/query/ConditionQuery.java @@ -0,0 +1,1239 @@ +/* + * 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.query; + +import java.math.BigDecimal; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; + +import org.apache.hugegraph.exception.BackendException; +import org.apache.hugegraph.id.Id; +import org.apache.hugegraph.id.SplicingIdGenerator; +import org.apache.hugegraph.perf.PerfUtil.Watched; +import org.apache.hugegraph.query.Condition.Relation; +import org.apache.hugegraph.query.Condition.RelationType; +import org.apache.hugegraph.query.serializer.QueryAdapter; +import org.apache.hugegraph.query.serializer.QueryIdAdapter; +import org.apache.hugegraph.structure.BaseElement; +import org.apache.hugegraph.structure.BaseProperty; +import org.apache.hugegraph.type.HugeType; +import org.apache.hugegraph.type.define.CollectionType; +import org.apache.hugegraph.type.define.HugeKeys; +import org.apache.hugegraph.util.CollectionUtil; +import org.apache.hugegraph.util.E; +import org.apache.hugegraph.util.InsertionOrderUtil; +import org.apache.hugegraph.util.LongEncoding; +import org.apache.hugegraph.util.NumericUtil; +import org.apache.hugegraph.util.collection.CollectionFactory; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Sets; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; + +public class ConditionQuery extends IdQuery { + + public static final char INDEX_SYM_MIN = '\u0000'; + public static final String INDEX_SYM_ENDING = "\u0000"; + public static final String INDEX_SYM_NULL = "\u0001"; + public static final String INDEX_SYM_EMPTY = "\u0002"; + public static final char INDEX_SYM_MAX = '\u0003'; + + private static int indexStringValueLength = 20; + + // Note: here we use "new String" to distinguish normal string code + public static final String INDEX_VALUE_NULL = ""; + public static final String INDEX_VALUE_EMPTY = ""; + + public static final Set IGNORE_SYM_SET; + + static { + List list = new ArrayList<>(INDEX_SYM_MAX - INDEX_SYM_MIN); + for (char ch = INDEX_SYM_MIN; ch <= INDEX_SYM_MAX; ch++) { + list.add(String.valueOf(ch)); + } + IGNORE_SYM_SET = ImmutableSet.copyOf(list); + } + + private static final List EMPTY_CONDITIONS = ImmutableList.of(); + + private static final Gson gson = new GsonBuilder() + .registerTypeAdapter(Condition.class, new QueryAdapter()) + .registerTypeAdapter(Id.class, new QueryIdAdapter()) + .setDateFormat("yyyy-MM-dd HH:mm:ss.SSS") + .create(); + + // Conditions will be contacted with `and` by default + private List conditions = EMPTY_CONDITIONS; + + private OptimizedType optimizedType = OptimizedType.NONE; + + private ResultsFilter resultsFilter = null; + // 2023-03-30 + // conditon 条件查询下沉,不需要序列化该字段 + private transient Element2IndexValueMap element2IndexValueMap = null; + private boolean shard; + + // 存储当前 ConditionQuery 命中的索引 + private transient MatchedIndex matchedIndex; + + public MatchedIndex matchedIndex() { + return matchedIndex; + } + + public void matchedIndex(MatchedIndex matchedIndex) { + this.matchedIndex = matchedIndex; + } + + public void shard(boolean shard) { + this.shard = shard; + } + + public boolean shard() { + return this.shard; + } + + public ConditionQuery(HugeType resultType) { + super(resultType); + } + + public ConditionQuery(HugeType resultType, Query originQuery) { + super(resultType, originQuery); + } + + private void ensureElement2IndexValueMap() { + if (this.element2IndexValueMap == null) { + this.element2IndexValueMap = new Element2IndexValueMap(); + } + } + + /** + * 索引、联合索引截取 + * @param values + * @return + */ + public static String concatValuesLimitLength(List values) { + List newValues = new ArrayList<>(values.size()); + for (Object v : values) { + v = convertLargeValue(v); + newValues.add(convertNumberIfNeeded(v)); + } + return SplicingIdGenerator.concatValues(newValues); + } + + /** + * 索引、联合索引截取 + * @param value + * @return + */ + public static String concatValuesLimitLength(Object value) { + if (value instanceof List) { + return concatValuesLimitLength((List)value); + } + + if (needConvertNumber(value)) { + return LongEncoding.encodeNumber(value); + } + value = convertLargeValue(value); + return value.toString(); + } + + public static int getIndexStringValueLength() { + return indexStringValueLength; + } + + /** + * 针对 String value 进行截取 + * @param v + * @return + */ + private static Object convertLargeValue(Object v) { + + if (Objects.nonNull(v) && v instanceof String && + ((String) v).length() > getIndexStringValueLength()) { + + v = ((String) v).substring(0, getIndexStringValueLength()); + + } + + return v; + } + + private static Object convertNumberIfNeeded(Object value) { + if (needConvertNumber(value)) { + return LongEncoding.encodeNumber(value); + } + return value; + } + + public ConditionQuery query(Condition condition) { + // Query by id (HugeGraph-259) + if (condition instanceof Relation) { + Relation relation = (Relation) condition; + if (relation.key().equals(HugeKeys.ID) && + relation.relation() == RelationType.EQ) { + E.checkArgument(relation.value() instanceof Id, + "Invalid id value '%s'", relation.value()); + super.query((Id) relation.value()); + return this; + } + } + + if (this.conditions == EMPTY_CONDITIONS) { + this.conditions = InsertionOrderUtil.newList(); + } + this.conditions.add(condition); + return this; + } + + public ConditionQuery query(List conditions) { + for (Condition condition : conditions) { + this.query(condition); + } + return this; + } + + public ConditionQuery eq(HugeKeys key, Object value) { + // Filter value by key + return this.query(Condition.eq(key, value)); + } + + public ConditionQuery gt(HugeKeys key, Object value) { + return this.query(Condition.gt(key, value)); + } + + public ConditionQuery gte(HugeKeys key, Object value) { + return this.query(Condition.gte(key, value)); + } + + public ConditionQuery lt(HugeKeys key, Object value) { + return this.query(Condition.lt(key, value)); + } + + public ConditionQuery lte(HugeKeys key, Object value) { + return this.query(Condition.lte(key, value)); + } + + public ConditionQuery neq(HugeKeys key, Object value) { + return this.query(Condition.neq(key, value)); + } + + public ConditionQuery prefix(HugeKeys key, Id value) { + return this.query(Condition.prefix(key, value)); + } + + public ConditionQuery key(HugeKeys key, Object value) { + return this.query(Condition.containsKey(key, value)); + } + + public ConditionQuery scan(String start, String end) { + return this.query(Condition.scan(start, end)); + } + + @Override + public int conditionsSize() { + return this.conditions.size(); + } + + @Override + public Collection conditions() { + return Collections.unmodifiableList(this.conditions); + } + + public void resetConditions(List conditions) { + this.conditions = conditions; + } + + public void resetConditions() { + this.conditions = EMPTY_CONDITIONS; + } + + public void recordIndexValue(Id propertyId, Id id, Object indexValue) { + this.ensureElement2IndexValueMap(); + this.element2IndexValueMap().addIndexValue(propertyId, id, indexValue); + } + + public void selectedIndexField(Id indexField) { + this.ensureElement2IndexValueMap(); + this.element2IndexValueMap().selectedIndexField(indexField); + } + + public Set getElementLeftIndex(Id elementId) { + if (this.element2IndexValueMap == null) { + return null; + } + return this.element2IndexValueMap.getLeftIndex(elementId); + } + + public void removeElementLeftIndex(Id elementId) { + if (this.element2IndexValueMap == null) { + return; + } + this.element2IndexValueMap.removeElementLeftIndex(elementId); + } + + + + private static boolean removeValue(Set values, Object value){ + for (Object compareValue : values) { + if (numberEquals(compareValue, value)) { + values.remove(compareValue); + return true; + } + } + return false; + } + + private static boolean numberEquals(Object number1, Object number2) { + // Same class compare directly + if (number1.getClass().equals(number2.getClass())) { + return number1.equals(number2); + } + // Otherwise convert to BigDecimal to make two numbers comparable + Number n1 = NumericUtil.convertToNumber(number1); + Number n2 = NumericUtil.convertToNumber(number2); + BigDecimal b1 = new BigDecimal(n1.doubleValue()); + BigDecimal b2 = new BigDecimal(n2.doubleValue()); + return b1.compareTo(b2) == 0; + } + + public ConditionQuery removeSysproCondition(HugeKeys sysproKey) { + for (Condition c : this.syspropConditions(sysproKey)) { + this.removeCondition(c); + } + return this; + } + + public ConditionQuery removeUserproCondition(Id key) { + for (Condition c : this.userpropConditions(key)) { + this.removeCondition(c); + } + return this; + } + + public ConditionQuery removeCondition(Condition condition) { + this.conditions.remove(condition); + return this; + } + + public boolean existLeftIndex(Id elementId) { + return this.getLeftIndexOfElement(elementId) != null; + } + + public Set getLeftIndexOfElement(Id elementId) { + if (this.element2IndexValueMap == null) { + return null; + } + return this.element2IndexValueMap.getLeftIndex(elementId); + } + + private Element2IndexValueMap element2IndexValueMap() { + if (this.element2IndexValueMap == null) { + this.element2IndexValueMap = new Element2IndexValueMap(); + } + return this.element2IndexValueMap; + } + + public List relations() { + List relations = new ArrayList<>(); + for (Condition c : this.conditions) { + relations.addAll(c.relations()); + } + return relations; + } + + public Relation relation(Id key){ + for (Relation r : this.relations()) { + if (r.key().equals(key)) { + return r; + } + } + return null; + } + + public Relation relation(HugeKeys key) { + for (Condition c : this.conditions) { + if (c.isRelation()) { + Condition.Relation r = (Condition.Relation) c; + if (r.key().equals(key)) { + return r; + } + } + } + return null; + } + + public boolean containsLabelOrUserpropRelation() { + for (Condition c : this.conditions) { + while (c instanceof Condition.Not) { + c = ((Condition.Not) c).condition(); + } + if (c.isLogic()) { + Condition.BinCondition binCondition = + (Condition.BinCondition) c; + ConditionQuery query = new ConditionQuery(HugeType.EDGE); + query.query(binCondition.left()); + query.query(binCondition.right()); + if (query.containsLabelOrUserpropRelation()) { + return true; + } + } else { + Condition.Relation r = (Condition.Relation) c; + if (r.key().equals(HugeKeys.LABEL) || + c instanceof Condition.UserpropRelation) { + return true; + } + } + } + return false; + } + + @Watched + public T condition(Object key) { + List valuesEQ = InsertionOrderUtil.newList(); + List valuesIN = InsertionOrderUtil.newList(); + for (Condition c : this.conditions) { + if (c.isRelation()) { + Condition.Relation r = (Condition.Relation) c; + if (r.key().equals(key)) { + if (r.relation() == RelationType.EQ) { + valuesEQ.add(r.value()); + } else if (r.relation() == RelationType.IN) { + Object value = r.value(); + assert value instanceof List; + valuesIN.add(value); + } + } + } + } + if (valuesEQ.isEmpty() && valuesIN.isEmpty()) { + return null; + } + if (valuesEQ.size() == 1 && valuesIN.isEmpty()) { + @SuppressWarnings("unchecked") + T value = (T) valuesEQ.get(0); + return value; + } + if (valuesEQ.isEmpty() && valuesIN.size() == 1) { + @SuppressWarnings("unchecked") + T value = (T) valuesIN.get(0); + return value; + } + + Set intersectValues = InsertionOrderUtil.newSet(); + for (Object value : valuesEQ) { + List valueAsList = ImmutableList.of(value); + if (intersectValues.isEmpty()) { + intersectValues.addAll(valueAsList); + } else { + CollectionUtil.intersectWithModify(intersectValues, + valueAsList); + } + } + for (Object value : valuesIN) { + @SuppressWarnings("unchecked") + List valueAsList = (List) value; + if (intersectValues.isEmpty()) { + intersectValues.addAll(valueAsList); + } else { + CollectionUtil.intersectWithModify(intersectValues, + valueAsList); + } + } + + if (intersectValues.isEmpty()) { + return null; + } + E.checkState(intersectValues.size() == 1, + "Illegal key '%s' with more than one value: %s", + key, intersectValues); + @SuppressWarnings("unchecked") + T value = (T) intersectValues.iterator().next(); + return value; + } + + public void unsetCondition(Object key) { + this.conditions.removeIf(c -> c.isRelation() && ((Relation) c).key().equals(key)); + } + + public boolean containsCondition(HugeKeys key) { + for (Condition c : this.conditions) { + if (c.isRelation()) { + Condition.Relation r = (Condition.Relation) c; + if (r.key().equals(key)) { + return true; + } + } + } + return false; + } + + public boolean containsCondition(Condition.RelationType type) { + for (Relation r : this.relations()) { + if (r.relation().equals(type)) { + return true; + } + } + return false; + } + + public boolean containsScanCondition() { + return this.containsCondition(Condition.RelationType.SCAN); + } + + public boolean containsRelation(HugeKeys key, Condition.RelationType type) { + for (Relation r : this.relations()) { + if (r.key().equals(key) && r.relation().equals(type)) { + return true; + } + } + return false; + } + + public boolean containsRelation(Condition.RelationType type) { + for (Relation r : this.relations()) { + if (r.relation().equals(type)) { + return true; + } + } + return false; + } + + public boolean containsScanRelation() { + return this.containsRelation(Condition.RelationType.SCAN); + } + + public boolean containsContainsCondition(Id key) { + for (Relation r : this.relations()) { + if (r.key().equals(key)) { + return r.relation().equals(RelationType.CONTAINS) || + r.relation().equals(RelationType.TEXT_CONTAINS); + } + } + return false; + } + + public boolean allSysprop() { + for (Condition c : this.conditions) { + if (!c.isSysprop()) { + return false; + } + } + return true; + } + + public boolean allRelation() { + for (Condition c : this.conditions) { + if (!c.isRelation()) { + return false; + } + } + return true; + } + + public List syspropConditions() { + this.checkFlattened(); + List conds = new ArrayList<>(); + for (Condition c : this.conditions) { + if (c.isSysprop()) { + conds.add(c); + } + } + return conds; + } + + public List syspropConditions(HugeKeys key) { + this.checkFlattened(); + List conditions = new ArrayList<>(); + for (Condition condition : this.conditions) { + Relation relation = (Relation) condition; + if (relation.key().equals(key)) { + conditions.add(relation); + } + } + return conditions; + } + + public List userpropConditions() { + this.checkFlattened(); + List conds = new ArrayList<>(); + for (Condition c : this.conditions) { + if (!c.isSysprop()) { + conds.add(c); + } + } + return conds; + } + + public List userpropConditions(Id key) { + this.checkFlattened(); + List conditions = new ArrayList<>(); + for (Condition condition : this.conditions) { + Relation relation = (Relation) condition; + if (relation.key().equals(key)) { + conditions.add(relation); + } + } + return conditions; + } + + public List userpropRelations() { + List relations = new ArrayList<>(); + for (Relation r : this.relations()) { + if (!r.isSysprop()) { + relations.add(r); + } + } + return relations; + } + + public void resetUserpropConditions() { + this.conditions.removeIf(condition -> !condition.isSysprop()); + } + + public Set userpropKeys() { + Set keys = new LinkedHashSet<>(); + for (Relation r : this.relations()) { + if (!r.isSysprop()) { + Condition.UserpropRelation ur = (Condition.UserpropRelation) r; + keys.add(ur.key()); + } + } + return keys; + } + + /** + * This method is only used for secondary index scenario, + * its relation must be EQ + * + * @param fields the user property fields + * @return the corresponding user property serial values of fields + */ + public String userpropValuesString(List fields) { + List values = new ArrayList<>(fields.size()); + for (Id field : fields) { + boolean got = false; + for (Relation r : this.userpropRelations()) { + if (r.key().equals(field) && !r.isSysprop()) { + E.checkState(r.relation == RelationType.EQ || + r.relation == RelationType.CONTAINS, + "Method userpropValues(List) only " + + "used for secondary index, " + + "relation must be EQ or CONTAINS, but got %s", + r.relation()); + values.add(r.serialValue()); + got = true; + } + } + if (!got) { + throw new BackendException( + "No such userprop named '%s' in the query '%s'", + field, this); + } + } + return concatValues(values); + } + + public String userpropValuesStringForIndex(List fields) { + List values = new ArrayList<>(fields.size()); + for (Id field : fields) { + boolean got = false; + for (Relation r : this.userpropRelations()) { + if (r.key().equals(field) && !r.isSysprop()) { + E.checkState(r.relation() == RelationType.EQ || + r.relation() == RelationType.CONTAINS, + "Method userpropValues(List) only " + + "used for secondary index, " + + "relation must be EQ or CONTAINS, but got %s", + r.relation()); + values.add(r.serialValue()); + got = true; + } + } + if (!got) { + throw new BackendException( + "No such userprop named '%s' in the query '%s'", + field, this); + } + } + return concatValuesLimitLength(values); + } + + public Set userpropValues(Id field) { + Set values = new HashSet<>(); + for (Relation r : this.userpropRelations()) { + if (r.key().equals(field)) { + values.add(r.serialValue()); + } + } + return values; + } + + public Object userpropValue(Id field) { + Set values = this.userpropValues(field); + if (values.isEmpty()) { + return null; + } + E.checkState(values.size() == 1, + "Expect one user-property value of field '%s', " + + "but got '%s'", field, values.size()); + return values.iterator().next(); + } + + public boolean hasRangeCondition() { + // NOTE: we need to judge all the conditions, including the nested + for (Condition.Relation r : this.relations()) { + if (r.relation().isRangeType()) { + return true; + } + } + return false; + } + + public boolean hasShardCondition() { + return this.shard; + } + + public boolean hasSearchCondition() { + // NOTE: we need to judge all the conditions, including the nested + for (Condition.Relation r : this.relations()) { + if (r.relation().isSearchType()) { + return true; + } + } + return false; + } + + public boolean hasSecondaryCondition() { + // NOTE: we need to judge all the conditions, including the nested + for (Condition.Relation r : this.relations()) { + if (r.relation().isSecondaryType()) { + return true; + } + } + return false; + } + + public boolean hasNeqCondition() { + // NOTE: we need to judge all the conditions, including the nested + for (Condition.Relation r : this.relations()) { + if (r.relation() == RelationType.NEQ) { + return true; + } + } + return false; + } + + public boolean matchUserpropKeys(List keys) { + Set conditionKeys = this.userpropKeys(); + return !keys.isEmpty() && conditionKeys.containsAll(keys); + } + + @Override + public ConditionQuery copy() { + ConditionQuery query = (ConditionQuery) super.copy(); + query.originQuery(this); + if (query.conditions != EMPTY_CONDITIONS) { + query.conditions = InsertionOrderUtil.newList(this.conditions); + } + query.optimizedType = OptimizedType.NONE; + query.resultsFilter = null; + + return query; + } + + public ConditionQuery deepCopy() { + ConditionQuery query = (ConditionQuery) super.copy(); + query.originQuery(this); + + List newConds = CollectionFactory.newList(CollectionType.EC); + for (Condition c : this.conditions) { + newConds.add(c); + } + query.resetConditions(newConds); + + query.optimizedType = OptimizedType.NONE; + query.resultsFilter = null; + + return query; + } + + public ConditionQuery copyAndResetUnshared() { + ConditionQuery query = this.copy(); + // These fields should not be shared by multiple sub-query + query.optimizedType = OptimizedType.NONE; + query.resultsFilter = null; + return query; + } + + public Condition.Relation copyRelationAndUpdateQuery(Object key) { + Condition.Relation copyRes = null; + for (int i = 0; i < this.conditions.size(); i++) { + Condition c = this.conditions.get(i); + if (c.isRelation()) { + Condition.Relation r = (Condition.Relation) c; + if (r.key().equals(key)) { + copyRes = r.copy(); + this.conditions.set(i, copyRes); + break; + } + } + } + E.checkArgument(copyRes != null, "Failed to copy Condition.Relation: %s", key); + return copyRes; + } + + @Override + public boolean test(BaseElement element) { + if (!this.ids().isEmpty() && !super.test(element)) { + return false; + } + + /* + * Currently results-filter is used to filter unmatched results returned + * by search index, and there may be multiple results-filter for every + * sub-query like within() + Text.contains(). + * We can't use sub-query results-filter here for fresh element which is + * not committed to backend store, because it's not from a sub-query. + */ + if (this.resultsFilter != null && !element.fresh()) { + return this.resultsFilter.test(element); + } + + /* + * NOTE: seems need to keep call checkRangeIndex() for each condition, + * so don't break early even if test() return false. + */ + boolean valid = true; + for (Condition cond : this.conditions) { + valid &= cond.test(element); + valid &= this.element2IndexValueMap == null || + this.element2IndexValueMap.checkRangeIndex(element, cond); + } + return valid; + } + + public void checkFlattened() { + E.checkState(this.isFlattened(), + "Query has none-flatten condition: %s", this); + } + + public boolean isFlattened() { + for (Condition condition : this.conditions) { + if (!condition.isFlattened()) { + return false; + } + } + return true; + } + + public boolean mayHasDupKeys(Set keys) { + Map keyCounts = new HashMap<>(); + for (Condition condition : this.conditions) { + if (!condition.isRelation()) { + // Assume may exist duplicate keys when has nested conditions + return true; + } + Relation relation = (Relation) condition; + if (keys.contains(relation.key())) { + int keyCount = keyCounts.getOrDefault(relation.key(), 0); + if (++keyCount > 1) { + return true; + } + keyCounts.put((HugeKeys) relation.key(), keyCount); + } + } + return false; + } + + public void optimized(OptimizedType optimizedType) { + assert this.optimizedType.ordinal() <= optimizedType.ordinal() : + this.optimizedType + " !<= " + optimizedType; + this.optimizedType = optimizedType; + + Query originQuery = this.originQuery(); + if (originQuery instanceof ConditionQuery) { + ConditionQuery cq = ((ConditionQuery) originQuery); + /* + * Two sub-query(flatten) will both set optimized of originQuery, + * here we just keep the higher one, this may not be a perfect way + */ + if (optimizedType.ordinal() > cq.optimized().ordinal()) { + cq.optimized(optimizedType); + } + } + } + + public OptimizedType optimized() { + return this.optimizedType; + } + + public void registerResultsFilter(ResultsFilter filter) { + assert this.resultsFilter == null; + this.resultsFilter = filter; + } + + public void updateResultsFilter() { + Query originQuery = this.originQuery(); + if (originQuery instanceof ConditionQuery) { + ConditionQuery originCQ = ((ConditionQuery) originQuery); + if (this.resultsFilter != null) { + originCQ.updateResultsFilter(this.resultsFilter); + } else { + originCQ.updateResultsFilter(); + } + } + } + + protected void updateResultsFilter(ResultsFilter filter) { + this.resultsFilter = filter; + Query originQuery = this.originQuery(); + if (originQuery instanceof ConditionQuery) { + ConditionQuery originCQ = ((ConditionQuery) originQuery); + originCQ.updateResultsFilter(filter); + } + } + + public ConditionQuery originConditionQuery() { + Query originQuery = this.originQuery(); + if (!(originQuery instanceof ConditionQuery)) { + return null; + } + + while (originQuery.originQuery() instanceof ConditionQuery) { + originQuery = originQuery.originQuery(); + } + return (ConditionQuery) originQuery; + } + + public static String concatValues(List values) { + assert !values.isEmpty(); + List newValues = new ArrayList<>(values.size()); + for (Object v : values) { + newValues.add(concatValues(v)); + } + return SplicingIdGenerator.concatValues(newValues); + } + + public static String concatValues(Object value) { + if (value instanceof String) { + return escapeSpecialValueIfNeeded((String) value); + } + if (value instanceof List) { + return concatValues((List) value); + } else if (needConvertNumber(value)) { + return LongEncoding.encodeNumber(value); + } else { + return escapeSpecialValueIfNeeded(value.toString()); + } + } + + public static ConditionQuery fromBytes(byte[] bytes) { + Gson gson = new GsonBuilder() + .registerTypeAdapter(Condition.class, new QueryAdapter()) + .registerTypeAdapter(Id.class, new QueryIdAdapter()) + .setDateFormat("yyyy-MM-dd HH:mm:ss.SSS") + .create(); + String cqs = new String(bytes, StandardCharsets.UTF_8); + ConditionQuery conditionQuery = gson.fromJson(cqs, ConditionQuery.class); + + return conditionQuery; + } + + private static boolean needConvertNumber(Object value) { + // Numeric or date values should be converted to number from string + return NumericUtil.isNumber(value) || value instanceof Date; + } + + private static String escapeSpecialValueIfNeeded(String value) { + if (value.isEmpty()) { + // Escape empty String to INDEX_SYM_EMPTY (char `\u0002`) + value = INDEX_SYM_EMPTY; + } else if (value == INDEX_VALUE_EMPTY) { + value = ""; + } else if (value == INDEX_VALUE_NULL) { + value = INDEX_SYM_NULL; + } else { + char ch = value.charAt(0); + if (ch <= INDEX_SYM_MAX) { + /* + * Special symbols can't be used due to impossible to parse, + * and treat it as illegal value for the origin text property. + * TODO: escape special symbols + */ + E.checkArgument(false, + "Illegal leading char '\\u%s' " + + "in index property: '%s'", + (int) ch, value); + } + } + return value; + } + + public enum OptimizedType { + NONE, + PRIMARY_KEY, + SORT_KEYS, + INDEX, + INDEX_FILTER + } + + public static final class Element2IndexValueMap { + + private final Map> leftIndexMap; + private final Map>> filed2IndexValues; + private Id selectedIndexField; + + public Element2IndexValueMap() { + this.filed2IndexValues = new HashMap<>(); + this.leftIndexMap = new HashMap<>(); + } + + public void addIndexValue(Id indexField, Id elementId, + Object indexValue) { + if (!this.filed2IndexValues.containsKey(indexField)) { + this.filed2IndexValues.putIfAbsent(indexField, new HashMap<>()); + } + Map> element2IndexValueMap = + this.filed2IndexValues.get(indexField); + if (element2IndexValueMap.containsKey(elementId)) { + element2IndexValueMap.get(elementId).add(indexValue); + } else { + element2IndexValueMap.put(elementId, + Sets.newHashSet(indexValue)); + } + } + + public void selectedIndexField(Id indexField) { + this.selectedIndexField = indexField; + } + + public Set toRemoveIndexValues(Id indexField, Id elementId) { + if (!this.filed2IndexValues.containsKey(indexField)) { + return null; + } + return this.filed2IndexValues.get(indexField).get(elementId); + } + + public Set removeIndexValues(Id indexField, Id elementId) { + if (!this.filed2IndexValues.containsKey(indexField)) { + return null; + } + return this.filed2IndexValues.get(indexField).get(elementId); + } + + public void addLeftIndex(Id elementId, Id indexField, + Set indexValues) { + LeftIndex leftIndex = new LeftIndex(indexValues, indexField); + if (this.leftIndexMap.containsKey(elementId)) { + this.leftIndexMap.get(elementId).add(leftIndex); + } else { + this.leftIndexMap.put(elementId, Sets.newHashSet(leftIndex)); + } + } + + public Set getLeftIndex(Id elementId) { + return this.leftIndexMap.get(elementId); + } + + public void addLeftIndex(Id indexField, Set indexValues, + Id elementId) { + LeftIndex leftIndex = new LeftIndex(indexValues, indexField); + if (this.leftIndexMap.containsKey(elementId)) { + this.leftIndexMap.get(elementId).add(leftIndex); + } else { + this.leftIndexMap.put(elementId, Sets.newHashSet(leftIndex)); + } + } + + public void removeElementLeftIndex(Id elementId) { + this.leftIndexMap.remove(elementId); + } + + public boolean checkRangeIndex(BaseElement element, Condition cond) { + // Not UserpropRelation + if (!(cond instanceof Condition.UserpropRelation)) { + return true; + } + + Condition.UserpropRelation propRelation = + (Condition.UserpropRelation) cond; + Id propId = propRelation.key(); + Set fieldValues = this.toRemoveIndexValues(propId, + element.id()); + if (fieldValues == null) { + // Not range index + return true; + } + + BaseProperty property = element.getProperty(propId); + if (property == null) { + // Property value has been deleted, so it's not matched + this.addLeftIndex(element.id(), propId, fieldValues); + return false; + } + + /* + * NOTE: If removing successfully means there is correct index, + * else we should add left-index values to left index map to + * wait the left-index to be removed. + */ + boolean hasRightValue = removeFieldValue(fieldValues, + property.value()); + if (!fieldValues.isEmpty()) { + this.addLeftIndex(element.id(), propId, fieldValues); + } + + /* + * NOTE: When query by more than one range index field, + * if current field is not the selected one, it can only be used to + * determine whether the index values matched, can't determine + * the element is valid or not. + */ + if (this.selectedIndexField != null) { + return !propId.equals(this.selectedIndexField) || hasRightValue; + } + + return hasRightValue; + } + + private static boolean removeFieldValue(Set values, + Object value) { + for (Object elem : values) { + if (numberEquals(elem, value)) { + values.remove(elem); + return true; + } + } + return false; + } + + private static boolean removeValue(Set values, Object value){ + for (Object compareValue : values) { + if (numberEquals(compareValue, value)) { + values.remove(compareValue); + return true; + } + } + return false; + } + + private static boolean numberEquals(Object number1, Object number2) { + // Same class compare directly + if (number1.getClass().equals(number2.getClass())) { + return number1.equals(number2); + } + + // Otherwise convert to BigDecimal to make two numbers comparable + Number n1 = NumericUtil.convertToNumber(number1); + Number n2 = NumericUtil.convertToNumber(number2); + BigDecimal b1 = BigDecimal.valueOf(n1.doubleValue()); + BigDecimal b2 = BigDecimal.valueOf(n2.doubleValue()); + return b1.compareTo(b2) == 0; + } + + public boolean validRangeIndex(BaseElement element, Condition cond) { + // Not UserpropRelation + if (!(cond instanceof Condition.UserpropRelation)) { + return true; + } + + Condition.UserpropRelation propRelation = + (Condition.UserpropRelation) cond; + Id propId = propRelation.key(); + Set fieldValues = this.removeIndexValues(propId, + element.id()); + if (fieldValues == null) { + // Not range index + return true; + } + + BaseProperty hugeProperty = element.getProperty(propId); + if (hugeProperty == null) { + // Property value has been deleted + this.addLeftIndex(propId, fieldValues, element.id()); + return false; + } + + /* + * NOTE: If success remove means has correct index, + * we should add left index values to left index map + * waiting to be removed + */ + boolean hasRightValue = removeValue(fieldValues, hugeProperty.value()); + if (fieldValues.size() > 0) { + this.addLeftIndex(propId, fieldValues, element.id()); + } + + /* + * NOTE: When query by more than one range index field, + * if current field is not the selected one, it can only be used to + * determine whether the index values matched, can't determine + * the element is valid or not + */ + if (this.selectedIndexField != null) { + return !propId.equals(this.selectedIndexField) || hasRightValue; + } + + return hasRightValue; + } + } + + public static final class LeftIndex { + + private final Set indexFieldValues; + private final Id indexField; + + public LeftIndex(Set indexFieldValues, Id indexField) { + this.indexFieldValues = indexFieldValues; + this.indexField = indexField; + } + + public Set indexFieldValues() { + return this.indexFieldValues; + } + + public Id indexField() { + return this.indexField; + } + } + + public interface ResultsFilter { + + boolean test(BaseElement element); + } + + public byte[] bytes() { + String cqs = gson.toJson(this); + return cqs.getBytes(StandardCharsets.UTF_8); + } + + +} diff --git a/hugegraph-struct/src/main/java/org/apache/hugegraph/query/IdQuery.java b/hugegraph-struct/src/main/java/org/apache/hugegraph/query/IdQuery.java new file mode 100644 index 0000000000..1235dfebc0 --- /dev/null +++ b/hugegraph-struct/src/main/java/org/apache/hugegraph/query/IdQuery.java @@ -0,0 +1,127 @@ +/* + * 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.query; + +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Set; + +import org.apache.hugegraph.id.Id; +import org.apache.hugegraph.structure.BaseElement; +import org.apache.hugegraph.type.HugeType; +import org.apache.hugegraph.util.E; +import org.apache.hugegraph.util.InsertionOrderUtil; + +import com.google.common.collect.ImmutableList; + +public class IdQuery extends Query { + + private static final List EMPTY_IDS = ImmutableList.of(); + + // The id(s) will be concated with `or` + private List ids = EMPTY_IDS; + private boolean mustSortByInput = true; + + public IdQuery(HugeType resultType) { + super(resultType); + } + + public IdQuery(HugeType resultType, Query originQuery) { + super(resultType, originQuery); + } + + public IdQuery(HugeType resultType, Set ids) { + this(resultType); + this.query(ids); + } + + public IdQuery(HugeType resultType, Id id) { + this(resultType); + this.query(id); + } + + public IdQuery(Query originQuery, Id id) { + this(originQuery.resultType(), originQuery); + this.query(id); + } + + public IdQuery(Query originQuery, Set ids) { + this(originQuery.resultType(), originQuery); + this.query(ids); + } + + public boolean mustSortByInput() { + return this.mustSortByInput; + } + + public void mustSortByInput(boolean mustSortedByInput) { + this.mustSortByInput = mustSortedByInput; + } + + @Override + public int idsSize() { + return this.ids.size(); + } + + @Override + public Collection ids() { + return Collections.unmodifiableList(this.ids); + } + + public void resetIds() { + this.ids = EMPTY_IDS; + } + + public IdQuery query(Id id) { + E.checkArgumentNotNull(id, "Query id can't be null"); + if (this.ids == EMPTY_IDS) { + this.ids = InsertionOrderUtil.newList(); + } + + int last = this.ids.size() - 1; + if (last >= 0 && id.equals(this.ids.get(last))) { + // The same id as the previous one, just ignore it + return this; + } + + this.ids.add(id); + this.checkCapacity(this.ids.size()); + return this; + } + + public IdQuery query(Set ids) { + for (Id id : ids) { + this.query(id); + } + return this; + } + + @Override + public boolean test(BaseElement element) { + return this.ids.contains(element.id()); + } + + @Override + public IdQuery copy() { + IdQuery query = (IdQuery) super.copy(); + query.ids = this.ids == EMPTY_IDS ? EMPTY_IDS : + InsertionOrderUtil.newList(this.ids); + return query; + } +} diff --git a/hugegraph-struct/src/main/java/org/apache/hugegraph/query/MatchedIndex.java b/hugegraph-struct/src/main/java/org/apache/hugegraph/query/MatchedIndex.java new file mode 100644 index 0000000000..6d63114ed7 --- /dev/null +++ b/hugegraph-struct/src/main/java/org/apache/hugegraph/query/MatchedIndex.java @@ -0,0 +1,81 @@ +/* + * Copyright 2017 HugeGraph Authors + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.query; + +import java.util.Collections; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; + +import org.apache.hugegraph.schema.IndexLabel; +import org.apache.hugegraph.schema.SchemaLabel; + +public class MatchedIndex { + + private final SchemaLabel schemaLabel; + private final Set indexLabels; + + public MatchedIndex(SchemaLabel schemaLabel, + Set indexLabels) { + this.schemaLabel = schemaLabel; + this.indexLabels = indexLabels; + } + + public SchemaLabel schemaLabel() { + return this.schemaLabel; + } + + public Set indexLabels() { + return Collections.unmodifiableSet(this.indexLabels); + } + + + public boolean containsSearchIndex() { + for (IndexLabel il : this.indexLabels) { + if (il.indexType().isSearch()) { + return true; + } + } + return false; + } + + @Override + public int hashCode() { + return indexLabels.hashCode(); + } + + @Override + public boolean equals(Object other) { + if (!(other instanceof MatchedIndex)) { + return false; + } + Set indexLabels = ((MatchedIndex) other).indexLabels; + return Objects.equals(this.indexLabels, indexLabels); + } + + @Override + public String toString() { + String strIndexLabels = + indexLabels.stream().map(i -> i.name()).collect(Collectors.joining(",")); + + return "MatchedIndex{schemaLabel=" + schemaLabel.name() + + ", indexLabels=" + strIndexLabels + '}'; + } +} diff --git a/hugegraph-struct/src/main/java/org/apache/hugegraph/query/Query.java b/hugegraph-struct/src/main/java/org/apache/hugegraph/query/Query.java new file mode 100644 index 0000000000..2151cd6d0b --- /dev/null +++ b/hugegraph-struct/src/main/java/org/apache/hugegraph/query/Query.java @@ -0,0 +1,720 @@ +/* + * 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.query; + +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; + +import com.google.common.base.Joiner; + +import org.apache.commons.lang3.tuple.ImmutablePair; +import org.apache.hugegraph.exception.BackendException; +import org.apache.hugegraph.exception.LimitExceedException; +import org.apache.hugegraph.id.Id; +import org.apache.hugegraph.structure.BaseElement; +import org.apache.hugegraph.type.HugeType; +import org.apache.hugegraph.type.define.CollectionType; +import org.apache.hugegraph.type.define.HugeKeys; +import org.apache.hugegraph.util.CollectionUtil; +import org.apache.hugegraph.util.E; +import org.apache.hugegraph.util.InsertionOrderUtil; +import org.apache.hugegraph.util.Log; +import org.apache.hugegraph.util.collection.IdSet; +import org.slf4j.Logger; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; + +public class Query implements Cloneable { + + private static final Logger LOG = Log.logger(Query.class); + // TODO: we should better not use Long.Max as the unify limit number + public static final long NO_LIMIT = Long.MAX_VALUE; + + public static final long COMMIT_BATCH = 500L; + public static final long QUERY_BATCH = 100L; + + public static final long NO_CAPACITY = -1L; + public static final long DEFAULT_CAPACITY = 800000L; // HugeGraph-777 + + private static final ThreadLocal CAPACITY_CONTEXT = new ThreadLocal<>(); + + protected static final Query NONE = new Query(HugeType.UNKNOWN); + + private static final Set EMPTY_OLAP_PKS = ImmutableSet.of(); + + private HugeType resultType; + private Map orders; + private long offset; + private long actualOffset; + private long actualStoreOffset; + private long limit; + private long skipDegree; + private String page; + private long capacity; + private boolean showHidden; + private boolean showDeleting; + private boolean showExpired; + private boolean olap; + private boolean withProperties; + private OrderType orderType; + private Set olapPks; + + private List selects = InsertionOrderUtil.newList(); + + @Deprecated + private transient Aggregate aggregate; + + private Query originQuery; + + private List groups = InsertionOrderUtil.newList(); + private boolean groupByLabel = false; + + // V3.7 aggs + private List> aggs = + InsertionOrderUtil.newList(); + + public Query() { + + } + + private static final ThreadLocal capacityContext = new ThreadLocal<>(); + + private static int indexStringValueLength = 20; + + public Query(HugeType resultType) { + this(resultType, null); + } + + public Query(HugeType resultType, Query originQuery) { + this.resultType = resultType; + this.originQuery = originQuery; + + this.orders = null; + + this.offset = 0L; + this.actualOffset = 0L; + this.actualStoreOffset = 0L; + this.limit = NO_LIMIT; + this.skipDegree = NO_LIMIT; + this.page = null; + + this.capacity = defaultCapacity(); + + this.showHidden = false; + this.showDeleting = false; + + this.withProperties = true; + this.orderType = OrderType.ORDER_STRICT; + + this.aggregate = null; + this.showExpired = false; + this.olap = false; + this.olapPks = EMPTY_OLAP_PKS; + } + + public void copyBasic(Query query) { + E.checkNotNull(query, "query"); + this.offset = query.offset(); + this.limit = query.limit(); + this.skipDegree = query.skipDegree(); + this.page = query.page(); + this.capacity = query.capacity(); + this.showHidden = query.showHidden(); + this.showDeleting = query.showDeleting(); + this.withProperties = query.withProperties(); + this.orderType = query.orderType(); + this.aggregate = query.aggregate(); + this.showExpired = query.showExpired(); + this.olap = query.olap(); + if (query.orders != null) { + this.orders(query.orders); + } + } + + public HugeType resultType() { + return this.resultType; + } + + public void resultType(HugeType resultType) { + this.resultType = resultType; + } + + public Query originQuery() { + return this.originQuery; + } + + public void setOriginQuery(Query query) { + this.originQuery = query; + } + + public Query rootOriginQuery() { + Query root = this; + while (root.originQuery != null) { + root = root.originQuery; + } + return root; + } + + protected void originQuery(Query originQuery) { + this.originQuery = originQuery; + } + + public Map orders() { + return Collections.unmodifiableMap(this.getOrNewOrders()); + } + + public void orders(Map orders) { + this.orders = InsertionOrderUtil.newMap(orders); + } + + public void order(HugeKeys key, Order order) { + this.getOrNewOrders().put(key, order); + } + + protected Map getOrNewOrders() { + if (this.orders != null) { + return this.orders; + } + this.orders = InsertionOrderUtil.newMap(); + return this.orders; + } + + public long offset() { + return this.offset; + } + + public void offset(long offset) { + E.checkArgument(offset >= 0L, "Invalid offset %s", offset); + this.offset = offset; + } + + public void copyOffset(Query parent) { + assert this.offset == 0L || this.offset == parent.offset; + assert this.actualOffset == 0L || + this.actualOffset == parent.actualOffset; + this.offset = parent.offset; + this.actualOffset = parent.actualOffset; + } + + public long actualOffset() { + return this.actualOffset; + } + + public void resetActualOffset() { + this.actualOffset = 0L; + this.actualStoreOffset = 0L; + } + + public long goOffset(long offset) { + E.checkArgument(offset >= 0L, "Invalid offset value: %s", offset); + if (this.originQuery != null) { + this.goParentOffset(offset); + } + return this.goSelfOffset(offset); + } + + private void goParentOffset(long offset) { + assert offset >= 0L; + Query parent = this.originQuery; + while (parent != null) { + parent.actualOffset += offset; + parent = parent.originQuery; + } + } + + private long goSelfOffset(long offset) { + assert offset >= 0L; + if (this.originQuery != null) { + this.originQuery.goStoreOffsetBySubQuery(offset); + } + this.actualOffset += offset; + return this.actualOffset; + } + + private long goStoreOffsetBySubQuery(long offset) { + Query parent = this.originQuery; + while (parent != null) { + parent.actualStoreOffset += offset; + parent = parent.originQuery; + } + this.actualStoreOffset += offset; + return this.actualStoreOffset; + } + + public Set skipOffsetIfNeeded(Set elems) { + /* + * Skip index(index query with offset) for performance optimization. + * We assume one result is returned by each index, but if there are + * overridden index it will cause confusing offset and results. + */ + long fromIndex = this.offset() - this.actualOffset(); + if (fromIndex < 0L) { + // Skipping offset is overhead, no need to skip + fromIndex = 0L; + } else if (fromIndex > 0L) { + this.goOffset(fromIndex); + } + E.checkArgument(fromIndex <= Integer.MAX_VALUE, + "Offset must be <= 0x7fffffff, but got '%s'", + fromIndex); + + if (fromIndex >= elems.size()) { + return ImmutableSet.of(); + } + long toIndex = this.total(); + if (this.noLimit() || toIndex > elems.size()) { + toIndex = elems.size(); + } + if (fromIndex == 0L && toIndex == elems.size()) { + return elems; + } + assert fromIndex < elems.size(); + assert toIndex <= elems.size(); + return CollectionUtil.subSet(elems, (int) fromIndex, (int) toIndex); + } + + public long remaining() { + if (this.limit == NO_LIMIT) { + return NO_LIMIT; + } else { + return this.total() - this.actualOffset(); + } + } + + public long total() { + if (this.limit == NO_LIMIT) { + return NO_LIMIT; + } else { + return this.offset + this.limit; + } + } + + public long limit() { + if (this.capacity != NO_CAPACITY) { + E.checkArgument(this.limit == Query.NO_LIMIT || + this.limit <= this.capacity, + "Invalid limit %s, must be <= capacity(%s)", + this.limit, this.capacity); + } + return this.limit; + } + + public void limit(long limit) { + E.checkArgument(limit >= 0L || limit == NO_LIMIT, + "Invalid limit %s", limit); + this.limit = limit; + } + + public boolean noLimit() { + return this.limit() == NO_LIMIT; + } + + public boolean noLimitAndOffset() { + return this.limit() == NO_LIMIT && this.offset() == 0L; + } + + public boolean reachLimit(long count) { + long limit = this.limit(); + if (limit == NO_LIMIT) { + return false; + } + return count >= (limit + this.offset()); + } + + /** + * Set or update the offset and limit by a range [start, end) + * NOTE: it will use the min range one: max start and min end + * + * @param start the range start, include it + * @param end the range end, exclude it + */ + public long range(long start, long end) { + // Update offset + long offset = this.offset(); + start = Math.max(start, offset); + this.offset(start); + + // Update limit + if (end != -1L) { + if (!this.noLimit()) { + end = Math.min(end, offset + this.limit()); + } else { + assert end < Query.NO_LIMIT; + } + E.checkArgument(end >= start, + "Invalid range: [%s, %s)", start, end); + this.limit(end - start); + } else { + // Keep the origin limit + assert this.limit() <= Query.NO_LIMIT; + } + return this.limit; + } + + public String page() { + if (this.page != null) { + E.checkState(this.limit() != 0L, + "Can't set limit=0 when using paging"); + E.checkState(this.offset() == 0L, + "Can't set offset when using paging, but got '%s'", + this.offset()); + } + return this.page; + } + + public String pageWithoutCheck() { + return this.page; + } + + public void page(String page) { + this.page = page; + } + + public boolean paging() { + return this.page != null; + } + + @Deprecated + public void olap(boolean olap) { + this.olap = olap; + } + + @Deprecated + public boolean olap() { + return this.olap; + } + + public void olapPks(Set olapPks) { + for (Id olapPk : olapPks) { + this.olapPk(olapPk); + } + } + + public void olapPk(Id olapPk) { + if (this.olapPks == EMPTY_OLAP_PKS) { + this.olapPks = new IdSet(CollectionType.EC); + } + this.olapPks.add(olapPk); + } + + public Set olapPks() { + return this.olapPks; + } + + public long capacity() { + return this.capacity; + } + + public void capacity(long capacity) { + this.capacity = capacity; + } + + public boolean bigCapacity() { + return this.capacity == NO_CAPACITY || this.capacity > DEFAULT_CAPACITY; + } + + public void checkCapacity(long count) throws LimitExceedException { + // Throw LimitExceedException if reach capacity + if (this.capacity != Query.NO_CAPACITY && count > this.capacity) { + final int MAX_CHARS = 256; + String query = this.toString(); + if (query.length() > MAX_CHARS) { + query = query.substring(0, MAX_CHARS) + "..."; + } + throw new LimitExceedException( + "Too many records(must <= %s) for the query: %s", + this.capacity, query); + } + } + + public Aggregate aggregate() { + return this.aggregate; + } + + public Aggregate aggregateNotNull() { + E.checkArgument(this.aggregate != null, + "The aggregate must be set for number query"); + return this.aggregate; + } + + public void aggregate(AggregateFuncDefine func, String property) { + this.aggregate = new Aggregate(func, property); + } + + public void aggregate(Aggregate aggregate) { + this.aggregate = aggregate; + } + + public boolean showHidden() { + return this.showHidden; + } + + public void showHidden(boolean showHidden) { + this.showHidden = showHidden; + } + + public boolean showDeleting() { + return this.showDeleting; + } + + public void showDeleting(boolean showDeleting) { + this.showDeleting = showDeleting; + } + + public long skipDegree() { + return this.skipDegree; + } + + public void skipDegree(long skipDegree) { + this.skipDegree = skipDegree; + } + + public boolean withProperties() { + return this.withProperties; + } + + public void withProperties(boolean withProperties) { + this.withProperties = withProperties; + } + + public OrderType orderType() { + return this.orderType; + } + + public void orderType(OrderType orderType) { + this.orderType = orderType; + } + + public boolean showExpired() { + return this.showExpired; + } + + public void showExpired(boolean showExpired) { + this.showExpired = showExpired; + } + + public Collection ids() { + return ImmutableList.of(); + } + + public Collection conditions() { + return ImmutableList.of(); + } + + public int idsSize() { + return 0; + } + + public int conditionsSize() { + return 0; + } + + public boolean empty() { + return this.idsSize() == 0 && this.conditionsSize() == 0; + } + + public boolean test(BaseElement element) { + return true; + } + + public Query copy() { + try { + return (Query) this.clone(); + } catch (CloneNotSupportedException e) { + throw new BackendException(e); + } + } + + @Override + public boolean equals(Object object) { + if (!(object instanceof Query)) { + return false; + } + Query other = (Query) object; + return this.resultType.equals(other.resultType) && + this.orders().equals(other.orders()) && + this.offset == other.offset && + this.limit == other.limit && + Objects.equals(this.page, other.page) && + this.ids().equals(other.ids()) && + this.conditions().equals(other.conditions()) && + this.withProperties == other.withProperties; + } + + @Override + public int hashCode() { + int hash = this.orders().hashCode() ^ + Long.hashCode(this.offset) ^ + Long.hashCode(this.limit) ^ + Objects.hashCode(this.page) ^ + this.ids().hashCode() ^ + this.conditions().hashCode() ^ + this.selects().hashCode() ^ + Boolean.hashCode(this.withProperties); + if (this.resultType == null) { + return hash; + } else { + return this.resultType.hashCode() ^ hash; + } + } + + @Override + public String toString() { + Map pairs = InsertionOrderUtil.newMap(); + if (this.page != null) { + pairs.put("page", String.format("'%s'", this.page)); + } + if (this.offset != 0) { + pairs.put("offset", this.offset); + } + if (this.limit != NO_LIMIT) { + pairs.put("limit", this.limit); + } + if (!this.orders().isEmpty()) { + pairs.put("order by", this.orders()); + } + + StringBuilder sb = new StringBuilder(128); + sb.append("`Query "); + if (this.aggregate != null) { + sb.append(this.aggregate); + } else { + sb.append('*'); + } + sb.append(" from ").append(this.resultType); + for (Map.Entry entry : pairs.entrySet()) { + sb.append(' ').append(entry.getKey()) + .append(' ').append(entry.getValue()).append(','); + } + if (!pairs.isEmpty()) { + // Delete last comma + sb.deleteCharAt(sb.length() - 1); + } + + if (!this.empty()) { + sb.append(" where"); + } + + // Append ids + if (!this.ids().isEmpty()) { + sb.append(" id in ").append(this.ids()); + } + + // Append conditions + if (!this.conditions().isEmpty()) { + if (!this.ids().isEmpty()) { + sb.append(" and"); + } + sb.append(" ").append(this.conditions()); + } + + if (!this.groups.isEmpty()) { + sb.append(" group by ").append(Joiner.on(",").join(this.groups)); + } + + sb.append('`'); + return sb.toString(); + } + + public static long defaultCapacity(long capacity) { + Long old = CAPACITY_CONTEXT.get(); + CAPACITY_CONTEXT.set(capacity); + return old != null ? old : DEFAULT_CAPACITY; + } + + public static long defaultCapacity() { + Long capacity = CAPACITY_CONTEXT.get(); + return capacity != null ? capacity : DEFAULT_CAPACITY; + } + + public static void checkForceCapacity(long count) + throws LimitExceedException { + if (count > DEFAULT_CAPACITY) { + throw new LimitExceedException( + "Too many records(must <= %s) for one query", + DEFAULT_CAPACITY); + } + } + + public boolean isTaskQuery() { + if (this.resultType() == HugeType.TASK || + this.resultType == HugeType.VARIABLE) { + return true; + } + + return false; + } + + public static int getIndexStringValueLength() { + return indexStringValueLength; + } + + public static void setIndexStringValueLength(int indexStringValueLengthTmp) { + if (indexStringValueLengthTmp <= 1) { + indexStringValueLengthTmp = 20; + } + indexStringValueLength = indexStringValueLengthTmp; + } + + public void select(Id id) { + if (!this.selects.contains(id)) { + this.selects.add(id); + } else { + LOG.warn("id already in selects: {}", id); + } + } + + public List selects() { + return this.selects; + } + + public void group(Id id) { + if (!this.groups.contains(id)) { + this.groups.add(id); + } else { + LOG.warn("id already in groups: {}", id); + } + } + + public enum OrderType { + // Under batch interface, the requirement for return order + ORDER_NONE, // Allow unordered + ORDER_WITHIN_VERTEX, // Edges within a vertex will not be broken, but there is no order between different vertices. + ORDER_STRICT // Ensure the original input point order + } + + public enum Order { + ASC, + DESC + } + + public enum AggType { + COUNT, + MAX, + MIN, + AVG, + SUM; + } + + +} diff --git a/hugegraph-struct/src/main/java/org/apache/hugegraph/query/serializer/AbstractSerializerAdapter.java b/hugegraph-struct/src/main/java/org/apache/hugegraph/query/serializer/AbstractSerializerAdapter.java new file mode 100644 index 0000000000..053f4ff14e --- /dev/null +++ b/hugegraph-struct/src/main/java/org/apache/hugegraph/query/serializer/AbstractSerializerAdapter.java @@ -0,0 +1,62 @@ +/* + * 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.query.serializer; + +import java.lang.reflect.Type; +import java.util.Map; + +import org.apache.hugegraph.exception.BackendException; + +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonDeserializer; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; +import com.google.gson.JsonPrimitive; +import com.google.gson.JsonSerializationContext; +import com.google.gson.JsonSerializer; + +// TODO: optimize by binary protocol +public abstract class AbstractSerializerAdapter implements JsonSerializer, + JsonDeserializer { + + //Note: By overriding the method to get the mapping + public abstract Map validType(); + + @Override + public T deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws + JsonParseException { + JsonObject object = json.getAsJsonObject(); + String type = object.get("cls").getAsString(); + JsonElement element = object.get("el"); + try { + return context.deserialize(element, validType().get(type)); + } catch (Exception e) { + throw new BackendException("Unknown element type: " + type, e); + } + } + + @Override + public JsonElement serialize(T src, Type typeOfSrc, JsonSerializationContext context) { + JsonObject result = new JsonObject(); + Class clazz = src.getClass(); + result.add("cls", new JsonPrimitive(clazz.getSimpleName().substring(0, 1).toUpperCase())); + result.add("el", context.serialize(src, clazz)); + return result; + } +} diff --git a/hugegraph-struct/src/main/java/org/apache/hugegraph/query/serializer/QueryAdapter.java b/hugegraph-struct/src/main/java/org/apache/hugegraph/query/serializer/QueryAdapter.java new file mode 100644 index 0000000000..e9975f57cd --- /dev/null +++ b/hugegraph-struct/src/main/java/org/apache/hugegraph/query/serializer/QueryAdapter.java @@ -0,0 +1,148 @@ +/* + * 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.query.serializer; + +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.Map; + +import org.apache.hugegraph.query.Condition; +import org.apache.hugegraph.type.define.Directions; + +import com.google.common.collect.ImmutableMap; +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; +import com.google.gson.JsonPrimitive; +import com.google.gson.JsonSerializationContext; +import com.google.gson.reflect.TypeToken; + +public class QueryAdapter extends AbstractSerializerAdapter { + + static ImmutableMap cls = + ImmutableMap.builder() + // TODO: uncomment later + .put("N", Condition.Not.class) + .put("A", Condition.And.class) + .put("O", Condition.Or.class) + .put("S", Condition.SyspropRelation.class) + .put("U", Condition.UserpropRelation.class) + .build(); + + static boolean isPrimitive(Class clz) { + try { + return (clz == Date.class) || ((Class) clz.getField("TYPE").get(null)).isPrimitive(); + } catch (Exception e) { + return false; + } + } + + @Override + public Map validType() { + return cls; + } + + @Override + public Condition deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) + throws JsonParseException { + Condition condition = super.deserialize(json, typeOfT, context); + if (condition instanceof Condition.Relation) { + JsonObject object = json.getAsJsonObject(); + if (object.has("el")) { + JsonElement elElement = object.get("el"); + JsonElement valueElement = elElement.getAsJsonObject().get("value"); + if (valueElement.isJsonObject()) { + String cls = valueElement.getAsJsonObject().get("cls").getAsString(); + try { + Class actualClass = Class.forName(cls); + Object obj = context.deserialize(valueElement, actualClass); + ((Condition.Relation) condition).value(obj); + } catch (ClassNotFoundException e) { + throw new JsonParseException(e.getMessage()); + } + } else if (elElement.getAsJsonObject().has("valuecls")) { + if (valueElement.isJsonArray()) { + String cls = elElement.getAsJsonObject().get("valuecls").getAsString(); + try { + Class actualClass = Class.forName(cls); + Type type = TypeToken.getParameterized(ArrayList.class, actualClass) + .getType(); + Object value = context.deserialize(valueElement, type); + ((Condition.Relation) condition).value(value); + } catch (ClassNotFoundException e) { + throw new JsonParseException(e.getMessage()); + } + } else { + String cls = elElement.getAsJsonObject().get("valuecls").getAsString(); + try { + Class actualClass = Class.forName(cls); + Object obj = context.deserialize(valueElement, actualClass); + ((Condition.Relation) condition).value(obj); + } catch (ClassNotFoundException e) { + throw new JsonParseException(e.getMessage()); + } + } + + } else if (valueElement.isJsonPrimitive() && + valueElement.getAsJsonPrimitive().isString()) { + switch ((String) ((Condition.Relation) condition).value()) { + case "OUT": + ((Condition.Relation) condition).value(Directions.OUT); + break; + case "IN": + ((Condition.Relation) condition).value(Directions.IN); + break; + default: + break; + } + } + } + } + return condition; + } + + @Override + public JsonElement serialize(Condition src, Type typeOfSrc, JsonSerializationContext context) { + JsonElement result = super.serialize(src, typeOfSrc, context); + if (src instanceof Condition.Relation) { + JsonObject object = result.getAsJsonObject(); + JsonElement valueElement = object.get("el").getAsJsonObject().get("value"); + if (valueElement.isJsonObject()) { + valueElement.getAsJsonObject() + .add("cls", + new JsonPrimitive( + ((Condition.Relation) src).value().getClass().getName())); + } else if (isPrimitive(((Condition.Relation) src).value().getClass())) { + object.get("el").getAsJsonObject() + .add("valuecls", + new JsonPrimitive( + ((Condition.Relation) src).value().getClass().getName())); + } else if (valueElement.isJsonArray()) { + if (((Condition.Relation) src).value() instanceof List) { + String valueCls = + ((List) ((Condition.Relation) src).value()).get(0).getClass().getName(); + object.get("el").getAsJsonObject().add("valuecls", new JsonPrimitive(valueCls)); + } + } + } + return result; + } +} diff --git a/hugegraph-struct/src/main/java/org/apache/hugegraph/query/serializer/QueryIdAdapter.java b/hugegraph-struct/src/main/java/org/apache/hugegraph/query/serializer/QueryIdAdapter.java new file mode 100644 index 0000000000..53f4145122 --- /dev/null +++ b/hugegraph-struct/src/main/java/org/apache/hugegraph/query/serializer/QueryIdAdapter.java @@ -0,0 +1,46 @@ +/* + * 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.query.serializer; + +import java.lang.reflect.Type; +import java.util.Map; + +import org.apache.hugegraph.backend.BinaryId; +import org.apache.hugegraph.id.EdgeId; +import org.apache.hugegraph.id.Id; +import org.apache.hugegraph.id.IdGenerator; + +import com.google.common.collect.ImmutableMap; + +public class QueryIdAdapter extends AbstractSerializerAdapter { + + static ImmutableMap cls = + ImmutableMap.builder() + .put("E", EdgeId.class) + .put("S", IdGenerator.StringId.class) + .put("L", IdGenerator.LongId.class) + .put("U", IdGenerator.UuidId.class) + .put("O", IdGenerator.ObjectId.class) + .put("B", BinaryId.class) + .build(); + + @Override + public Map validType() { + return cls; + } +} diff --git a/hugegraph-struct/src/main/java/org/apache/hugegraph/schema/EdgeLabel.java b/hugegraph-struct/src/main/java/org/apache/hugegraph/schema/EdgeLabel.java new file mode 100644 index 0000000000..443b55421a --- /dev/null +++ b/hugegraph-struct/src/main/java/org/apache/hugegraph/schema/EdgeLabel.java @@ -0,0 +1,449 @@ +/* + * 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.schema; + +import java.util.ArrayList; +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.HugeGraphSupplier; +import org.apache.hugegraph.id.Id; +import org.apache.hugegraph.id.IdGenerator; +import org.apache.hugegraph.schema.builder.SchemaBuilder; +import org.apache.hugegraph.type.HugeType; +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.SchemaStatus; +import org.apache.hugegraph.util.E; + +import com.google.common.base.Objects; + +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 = EdgeLabelType.NORMAL; + private Id fatherId; + + public EdgeLabel(final HugeGraphSupplier graph, Id id, String name) { + super(graph, id, name); + this.frequency = Frequency.DEFAULT; + this.sortKeys = new ArrayList<>(); + } + + @Override + public HugeType type() { + return HugeType.EDGE_LABEL; + } + + 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 boolean general() { + return this.edgeLabelType.general(); + } + + 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; + } + + public boolean directed() { + // TODO: implement (do we need this method?) + return true; + } + + public String sourceLabelName() { + 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() { + if (links.size() == 1) { + return links.iterator().next().getLeft(); + } + return NONE_ID; + } + + public void sourceLabel(Id 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() { + 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() { + if (links.size() == 1) { + return links.iterator().next().getRight(); + } + return NONE_ID; + } + + public void targetLabel(Id 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) { + 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) { + 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.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() { + return !this.sortKeys.isEmpty(); + } + + public List sortKeys() { + return Collections.unmodifiableList(this.sortKeys); + } + + public void sortKey(Id id) { + this.sortKeys.add(id); + } + + public void sortKeys(Id... ids) { + this.sortKeys.addAll(Arrays.asList(ids)); + } + + public boolean hasSameContent(EdgeLabel other) { + return super.hasSameContent(other) && + this.frequency == other.frequency && + Objects.equal(this.sourceLabelName(), other.sourceLabelName()) && + Objects.equal(this.targetLabelName(), other.targetLabelName()) && + Objects.equal(this.graph.mapPkId2Name(this.sortKeys), + other.graph.mapPkId2Name(other.sortKeys)); + } + + public static EdgeLabel undefined(HugeGraphSupplier graph, Id id) { + return new EdgeLabel(graph, id, UNDEF); + } + + 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(); + + Builder multiTimes(); + + Builder sortKeys(String... keys); + + Builder properties(String... properties); + + Builder nullableKeys(String... keys); + + Builder frequency(Frequency frequency); + + Builder ttl(long ttl); + + Builder ttlStartTime(String ttlStartTime); + + Builder enableLabelIndex(boolean enable); + + Builder userdata(String key, Object value); + + Builder userdata(Map userdata); + } + + @Override + public Map asMap() { + Map map = new HashMap<>(); + + if (this.sourceLabel() != null && this.sourceLabel() != NONE_ID) { + map.put(P.SOURCE_LABEL, this.sourceLabel().asString()); + } + + if (this.targetLabel() != null && this.targetLabel() != NONE_ID) { + map.put(P.TARGET_LABEL, this.targetLabel().asString()); + } + + if (this.properties() != null) { + map.put(P.PROPERTIES, this.properties()); + } + + if (this.nullableKeys() != null) { + map.put(P.NULLABLE_KEYS, this.nullableKeys()); + } + + if (this.indexLabels() != null) { + map.put(P.INDEX_LABELS, this.indexLabels()); + } + + if (this.ttlStartTime() != null) { + map.put(P.TT_START_TIME, this.ttlStartTime().asString()); + } + + if (this.sortKeys() != null) { + 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.ENABLE_LABEL_INDEX, this.enableLabelIndex()); + map.put(P.TTL, String.valueOf(this.ttl())); + map.put(P.LINKS, this.links()); + map.put(P.FREQUENCY, this.frequency().toString()); + + return super.asMap(map); + } + + @SuppressWarnings("unchecked") + public static EdgeLabel fromMap(Map map, HugeGraphSupplier graph) { + Id id = IdGenerator.of((int) map.get(EdgeLabel.P.ID)); + String name = (String) map.get(EdgeLabel.P.NAME); + EdgeLabel edgeLabel = new EdgeLabel(graph, id, name); + for (Map.Entry entry : map.entrySet()) { + switch (entry.getKey()) { + case P.ID: + case P.NAME: + break; + case P.STATUS: + edgeLabel.status( + SchemaStatus.valueOf(((String) entry.getValue()).toUpperCase())); + break; + case P.USERDATA: + edgeLabel.userdata(new Userdata((Map) entry.getValue())); + break; + case P.PROPERTIES: + Set ids = ((List) entry.getValue()).stream().map( + IdGenerator::of).collect(Collectors.toSet()); + edgeLabel.properties(ids); + break; + case P.NULLABLE_KEYS: + ids = ((List) entry.getValue()).stream().map( + IdGenerator::of).collect(Collectors.toSet()); + edgeLabel.nullableKeys(ids); + break; + case P.INDEX_LABELS: + ids = ((List) entry.getValue()).stream().map( + IdGenerator::of).collect(Collectors.toSet()); + edgeLabel.addIndexLabels(ids.toArray(new Id[0])); + break; + case P.ENABLE_LABEL_INDEX: + boolean enableLabelIndex = (Boolean) entry.getValue(); + edgeLabel.enableLabelIndex(enableLabelIndex); + break; + case P.TTL: + long ttl = Long.parseLong((String) entry.getValue()); + edgeLabel.ttl(ttl); + break; + case P.TT_START_TIME: + long ttlStartTime = + 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.SOURCE_LABEL: + long sourceLabel = + Long.parseLong((String) entry.getValue()); + edgeLabel.sourceLabel(IdGenerator.of(sourceLabel)); + break; + case P.TARGET_LABEL: + long targetLabel = + 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.FREQUENCY: + Frequency frequency = + Frequency.valueOf(((String) entry.getValue()).toUpperCase()); + edgeLabel.frequency(frequency); + break; + case P.SORT_KEYS: + ids = ((List) entry.getValue()).stream().map( + IdGenerator::of).collect(Collectors.toSet()); + edgeLabel.sortKeys(ids.toArray(new Id[0])); + break; + default: + throw new AssertionError(String.format( + "Invalid key '%s' for edge label", + entry.getKey())); + } + } + return edgeLabel; + } + + public static final class P { + + public static final String ID = "id"; + public static final String NAME = "name"; + + public static final String STATUS = "status"; + public static final String USERDATA = "userdata"; + + public static final String PROPERTIES = "properties"; + public static final String NULLABLE_KEYS = "nullableKeys"; + public static final String INDEX_LABELS = "indexLabels"; + + public static final String ENABLE_LABEL_INDEX = "enableLabelIndex"; + public static final String TTL = "ttl"; + public static final String TT_START_TIME = "ttlStartTime"; + public static final String LINKS = "links"; + public static final String SOURCE_LABEL = "sourceLabel"; + public static final String TARGET_LABEL = "targetLabel"; + public static final String EDGELABEL_TYPE = "edgeLabelType"; + public static final String FATHER_ID = "fatherId"; + public static final String FREQUENCY = "frequency"; + public static final String SORT_KEYS = "sortKeys"; + } +} diff --git a/hugegraph-struct/src/main/java/org/apache/hugegraph/schema/IndexLabel.java b/hugegraph-struct/src/main/java/org/apache/hugegraph/schema/IndexLabel.java new file mode 100644 index 0000000000..c3a49467c7 --- /dev/null +++ b/hugegraph-struct/src/main/java/org/apache/hugegraph/schema/IndexLabel.java @@ -0,0 +1,498 @@ +/* + * 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.schema; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import org.apache.hugegraph.HugeGraphSupplier; +import org.apache.hugegraph.id.Id; +import org.apache.hugegraph.id.IdGenerator; +import org.apache.hugegraph.schema.builder.SchemaBuilder; +import org.apache.hugegraph.type.define.IndexType; +import org.apache.hugegraph.type.define.SchemaStatus; +import org.apache.hugegraph.type.HugeType; +import org.apache.hugegraph.util.GraphUtils; +import org.apache.hugegraph.util.E; + +import com.google.common.base.Objects; + +public class IndexLabel extends SchemaElement { + + private HugeType baseType; + private Id baseValue; + private IndexType indexType; + private List indexFields; + + public IndexLabel(final HugeGraphSupplier graph, Id id, String name) { + super(graph, id, name); + this.baseType = HugeType.SYS_SCHEMA; + this.baseValue = NONE_ID; + this.indexType = IndexType.SECONDARY; + this.indexFields = new ArrayList<>(); + } + + protected IndexLabel(long id, String name) { + this(null, IdGenerator.of(id), name); + } + + @Override + public HugeType type() { + return HugeType.INDEX_LABEL; + } + + public HugeType baseType() { + return this.baseType; + } + + public void baseType(HugeType baseType) { + this.baseType = baseType; + } + + public Id baseValue() { + return this.baseValue; + } + + public void baseValue(Id id) { + this.baseValue = id; + } + + public IndexType indexType() { + return this.indexType; + } + + public void indexType(IndexType indexType) { + this.indexType = indexType; + } + + public HugeType queryType() { + switch (this.baseType) { + case VERTEX_LABEL: + return HugeType.VERTEX; + case EDGE_LABEL: + return HugeType.EDGE; + case SYS_SCHEMA: + return HugeType.SYS_SCHEMA; + default: + throw new AssertionError(String.format( + "Query type of index label is either '%s' or '%s', " + + "but '%s' is used", + HugeType.VERTEX_LABEL, HugeType.EDGE_LABEL, + this.baseType)); + } + } + + public List indexFields() { + return Collections.unmodifiableList(this.indexFields); + } + + public void indexFields(Id... ids) { + this.indexFields.addAll(Arrays.asList(ids)); + } + + public void indexField(Id id) { + this.indexFields.add(id); + } + + public Id indexField() { + E.checkState(this.indexFields.size() == 1, + "There should be only one field in %s index label, " + + "but got: %s", this.indexType.string(), this.indexFields); + return this.indexFields.get(0); + } + + public SchemaLabel baseLabel() { + return getBaseLabel(this.graph, this.baseType, this.baseValue); + } + + public SchemaLabel baseElement() { + return getElement(this.graph, this.baseType, this.baseValue); + } + + public boolean hasSameContent(IndexLabel other) { + return super.hasSameContent(other) && + this.indexType == other.indexType && + this.baseType == other.baseType && + Objects.equal(this.graph.mapPkId2Name(this.indexFields), + other.graph.mapPkId2Name(other.indexFields)); + } + + public boolean olap() { + return VertexLabel.OLAP_VL.id().equals(this.baseValue); + } + + public Object validValue(Object value) { + if (!(value instanceof Number)) { + return value; + } + + Number number = (Number) value; + switch (this.indexType()) { + case RANGE_INT: + return number.intValue(); + case RANGE_LONG: + return number.longValue(); + case RANGE_FLOAT: + return number.floatValue(); + case RANGE_DOUBLE: + return number.doubleValue(); + default: + return value; + } + } + + // Label index + private static final IndexLabel VL_IL = new IndexLabel(VL_IL_ID, "~vli"); + private static final IndexLabel EL_IL = new IndexLabel(EL_IL_ID, "~eli"); + + // Schema name index + private static final IndexLabel PKN_IL = new IndexLabel(PKN_IL_ID, "~pkni"); + private static final IndexLabel VLN_IL = new IndexLabel(VLN_IL_ID, "~vlni"); + private static final IndexLabel ELN_IL = new IndexLabel(ELN_IL_ID, "~elni"); + private static final IndexLabel ILN_IL = new IndexLabel(ILN_IL_ID, "~ilni"); + + public static IndexLabel label(HugeType type) { + switch (type) { + case TASK: + case SERVER: + case VERTEX: + return VL_IL; + case EDGE: + case EDGE_OUT: + case EDGE_IN: + return EL_IL; + case PROPERTY_KEY: + return PKN_IL; + case VERTEX_LABEL: + return VLN_IL; + case EDGE_LABEL: + return ELN_IL; + case INDEX_LABEL: + return ILN_IL; + default: + throw new AssertionError(String.format( + "No primitive index label for '%s'", type)); + } + } + + public static IndexLabel label(HugeGraphSupplier graph, Id id) { + // Primitive IndexLabel first + if (id.asLong() < 0 && id.asLong() > -NEXT_PRIMITIVE_SYS_ID) { + switch ((int) id.asLong()) { + case VL_IL_ID: + return VL_IL; + case EL_IL_ID: + return EL_IL; + case PKN_IL_ID: + return PKN_IL; + case VLN_IL_ID: + return VLN_IL; + case ELN_IL_ID: + return ELN_IL; + case ILN_IL_ID: + return ILN_IL; + default: + throw new AssertionError(String.format( + "No primitive index label for '%s'", id)); + } + } + return graph.indexLabel(id); + } + + public static SchemaLabel getBaseLabel(HugeGraphSupplier graph, + HugeType baseType, + Object baseValue) { + E.checkNotNull(baseType, "base type", "index label"); + E.checkNotNull(baseValue, "base value", "index label"); + E.checkArgument(baseValue instanceof String || baseValue instanceof Id, + "The base value must be instance of String or Id, " + + "but got %s(%s)", baseValue, + baseValue.getClass().getSimpleName()); + + SchemaLabel label; + switch (baseType) { + case VERTEX_LABEL: + if (baseValue instanceof String) { + label = graph.vertexLabel((String) baseValue); + } else { + assert baseValue instanceof Id; + label = graph.vertexLabel((Id) baseValue); + } + break; + case EDGE_LABEL: + if (baseValue instanceof String) { + label = graph.edgeLabel((String) baseValue); + } else { + assert baseValue instanceof Id; + label = graph.edgeLabel((Id) baseValue); + } + break; + default: + throw new AssertionError(String.format( + "Unsupported base type '%s' of index label", + baseType)); + } + + E.checkArgumentNotNull(label, "Can't find the %s with name '%s'", + baseType.readableName(), baseValue); + return label; + } + + public static SchemaLabel getElement(HugeGraphSupplier graph, + HugeType baseType, Object baseValue) { + E.checkNotNull(baseType, "base type", "index label"); + E.checkNotNull(baseValue, "base value", "index label"); + E.checkArgument(baseValue instanceof String || baseValue instanceof Id, + "The base value must be instance of String or Id, " + + "but got %s(%s)", baseValue, + baseValue.getClass().getSimpleName()); + + SchemaLabel label; + switch (baseType) { + case VERTEX_LABEL: + if (baseValue instanceof String) { + label = graph.vertexLabel((String) baseValue); + } else { + assert baseValue instanceof Id; + label = graph.vertexLabel((Id) baseValue); + } + break; + case EDGE_LABEL: + if (baseValue instanceof String) { + label = graph.edgeLabel((String) baseValue); + } else { + assert baseValue instanceof Id; + label = graph.edgeLabel((Id) baseValue); + } + break; + default: + throw new AssertionError(String.format( + "Unsupported base type '%s' of index label", + baseType)); + } + + E.checkArgumentNotNull(label, "Can't find the %s with name '%s'", + baseType.readableName(), baseValue); + return label; + } + + public String convert2Groovy(boolean attachIdFlag) { + StringBuilder builder = new StringBuilder(SCHEMA_PREFIX); + + // Name + if (!attachIdFlag) { + builder.append("indexLabel").append("('") + .append(this.name()) + .append("')"); + } else { + builder.append("indexLabel").append("(") + .append(longId()).append(", '") + .append(this.name()) + .append("')"); + } + + // On + switch (this.baseType()) { + case VERTEX_LABEL: + VertexLabel vl = this.graph.vertexLabel(this.baseValue); + builder.append(".onV('") + .append(vl.name()) + .append("')"); + break; + case EDGE_LABEL: + EdgeLabel el = this.graph.edgeLabel(this.baseValue); + builder.append(".onE('") + .append(el.name()) + .append("')"); + break; + default: + throw new AssertionError(String.format( + "Invalid base type '%s'", this.baseType())); + } + + // By + builder.append(".by("); + List properties = this.indexFields(); + int size = properties.size(); + for (Id id : properties) { + PropertyKey pk = this.graph.propertyKey(id); + builder.append("'") + .append(pk.name()) + .append("'"); + if (--size > 0) { + builder.append(","); + } + } + builder.append(")"); + + // Index type + builder.append("."); + switch (this.indexType()) { + case SECONDARY: + builder.append("secondary()"); + break; + case RANGE_INT: + case RANGE_LONG: + case RANGE_FLOAT: + case RANGE_DOUBLE: + builder.append("range()"); + break; + case SEARCH: + builder.append("search()"); + break; + case SHARD: + builder.append("shard()"); + break; + case UNIQUE: + builder.append("unique()"); + break; + default: + throw new AssertionError(String.format( + "Invalid index type '%s'", this.indexType())); + } + + // User data + Map userdata = this.userdata(); + if (userdata.isEmpty()) { + return builder.toString(); + } + for (Map.Entry entry : userdata.entrySet()) { + if (GraphUtils.isHidden(entry.getKey())) { + continue; + } + builder.append(".userdata('") + .append(entry.getKey()) + .append("',") + .append(entry.getValue()) + .append(")"); + } + + builder.append(".ifNotExist().create();"); + return builder.toString(); + } + + public interface Builder extends SchemaBuilder { + + TaskWithSchema createWithTask(); + + Id rebuild(); + + Builder onV(String baseValue); + + Builder onE(String baseValue); + + Builder by(String... fields); + + Builder secondary(); + + Builder range(); + + Builder search(); + + Builder shard(); + + Builder unique(); + + Builder on(HugeType baseType, String baseValue); + + Builder indexType(IndexType indexType); + + Builder userdata(String key, Object value); + + Builder userdata(Map userdata); + + Builder rebuild(boolean rebuild); + } + + @Override + public Map asMap() { + HashMap map = new HashMap<>(); + map.put(P.BASE_TYPE, this.baseType().name()); + map.put(P.BASE_VALUE, this.baseValue().asString()); + map.put(P.INDEX_TYPE, this.indexType().name()); + map.put(P.INDEX_FIELDS, this.indexFields()); + return super.asMap(map); + } + + @SuppressWarnings("unchecked") + public static IndexLabel fromMap(Map map, HugeGraphSupplier graph) { + Id id = IdGenerator.of((int) map.get(IndexLabel.P.ID)); + String name = (String) map.get(IndexLabel.P.NAME); + + IndexLabel indexLabel = new IndexLabel(graph, id, name); + for (Map.Entry entry : map.entrySet()) { + switch (entry.getKey()) { + case P.ID: + case P.NAME: + break; + case P.STATUS: + indexLabel.status( + SchemaStatus.valueOf(((String) entry.getValue()).toUpperCase())); + break; + case P.USERDATA: + indexLabel.userdata(new Userdata((Map) entry.getValue())); + break; + case P.BASE_TYPE: + HugeType hugeType = + HugeType.valueOf(((String) entry.getValue()).toUpperCase()); + indexLabel.baseType(hugeType); + break; + case P.BASE_VALUE: + long sourceLabel = + Long.parseLong((String) entry.getValue()); + indexLabel.baseValue(IdGenerator.of(sourceLabel)); + break; + case P.INDEX_TYPE: + IndexType indexType = + IndexType.valueOf(((String) entry.getValue()).toUpperCase()); + indexLabel.indexType(indexType); + break; + case P.INDEX_FIELDS: + List ids = ((List) entry.getValue()).stream().map( + IdGenerator::of).collect(Collectors.toList()); + indexLabel.indexFields(ids.toArray(new Id[0])); + break; + default: + throw new AssertionError(String.format( + "Invalid key '%s' for index label", + entry.getKey())); + } + } + return indexLabel; + } + + public static final class P { + + public static final String ID = "id"; + public static final String NAME = "name"; + + public static final String STATUS = "status"; + public static final String USERDATA = "userdata"; + + public static final String BASE_TYPE = "baseType"; + public static final String BASE_VALUE = "baseValue"; + public static final String INDEX_TYPE = "indexType"; + public static final String INDEX_FIELDS = "indexFields"; + } + +} diff --git a/hugegraph-struct/src/main/java/org/apache/hugegraph/schema/PropertyKey.java b/hugegraph-struct/src/main/java/org/apache/hugegraph/schema/PropertyKey.java new file mode 100644 index 0000000000..48b1076342 --- /dev/null +++ b/hugegraph-struct/src/main/java/org/apache/hugegraph/schema/PropertyKey.java @@ -0,0 +1,646 @@ +/* + * 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.schema; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + + +import org.apache.hugegraph.HugeGraphSupplier; + +import org.apache.hugegraph.exception.HugeException; +import org.apache.hugegraph.exception.NotSupportException; +import org.apache.hugegraph.id.Id; +import org.apache.hugegraph.id.IdGenerator; +import org.apache.hugegraph.schema.builder.SchemaBuilder; +import org.apache.hugegraph.type.HugeType; + +import org.apache.hugegraph.type.Propfiable; +import org.apache.hugegraph.type.define.AggregateType; +import org.apache.hugegraph.type.define.Cardinality; +import org.apache.hugegraph.type.define.DataType; +import org.apache.hugegraph.type.define.SchemaStatus; +import org.apache.hugegraph.type.define.WriteType; +import org.apache.hugegraph.util.E; +import org.apache.hugegraph.util.GraphUtils; +import org.apache.hugegraph.util.LongEncoding; + +import static org.apache.hugegraph.type.define.WriteType.OLAP_COMMON; +import static org.apache.hugegraph.type.define.WriteType.OLAP_RANGE; +import static org.apache.hugegraph.type.define.WriteType.OLAP_SECONDARY; + +public class PropertyKey extends SchemaElement implements Propfiable { + + private DataType dataType; + private Cardinality cardinality; + private AggregateType aggregateType; + private WriteType writeType; + + public PropertyKey(final HugeGraphSupplier graph, Id id, String name) { + super(graph, id, name); + this.dataType = DataType.TEXT; + this.cardinality = Cardinality.SINGLE; + this.aggregateType = AggregateType.NONE; + this.writeType = WriteType.OLTP; + } + + @Override + public HugeType type() { + return HugeType.PROPERTY_KEY; + } + + public DataType dataType() { + return this.dataType; + } + + public void dataType(DataType dataType) { + this.dataType = dataType; + } + + public Cardinality cardinality() { + return this.cardinality; + } + + public void cardinality(Cardinality cardinality) { + this.cardinality = cardinality; + } + + public AggregateType aggregateType() { + return this.aggregateType; + } + + public void aggregateType(AggregateType aggregateType) { + this.aggregateType = aggregateType; + } + + public void writeType(WriteType writeType) { + this.writeType = writeType; + } + + public WriteType writeType() { + return this.writeType; + } + + public boolean oltp() { + return this.writeType.oltp(); + } + + public boolean olap() { + return this.writeType.olap(); + } + + @Override + public Set properties() { + return Collections.emptySet(); + } + + public PropertyKey properties(Id... properties) { + if (properties.length > 0) { + throw new NotSupportException("PropertyKey.properties(Id)"); + } + return this; + } + + public void defineDefaultValue(Object value) { + // TODO add a field default_value + this.userdata().put(Userdata.DEFAULT_VALUE, value); + } + + public Object defaultValue() { + // TODO add a field default_value + return this.userdata().get(Userdata.DEFAULT_VALUE); + } + + public boolean hasSameContent(PropertyKey other) { + return super.hasSameContent(other) && + this.dataType == other.dataType() && + this.cardinality == other.cardinality() && + this.aggregateType == other.aggregateType() && + this.writeType == other.writeType(); + } + + public String clazz() { + String dataType = this.dataType().clazz().getSimpleName(); + switch (this.cardinality) { + case SINGLE: + return dataType; + // A set of values: Set + case SET: + return String.format("Set<%s>", dataType); + // A list of values: List + case LIST: + return String.format("List<%s>", dataType); + default: + throw new AssertionError(String.format( + "Unsupported cardinality: '%s'", this.cardinality)); + } + } + + public Class implementClazz() { + Class cls; + switch (this.cardinality) { + case SINGLE: + cls = this.dataType().clazz(); + break; + // A set of values: Set + case SET: + cls = LinkedHashSet.class; + break; + // A list of values: List + case LIST: + cls = ArrayList.class; + break; + default: + throw new AssertionError(String.format( + "Unsupported cardinality: '%s'", this.cardinality)); + } + return cls; + } + + @SuppressWarnings("unchecked") + public T newValue() { + switch (this.cardinality) { + case SET: + return (T) new LinkedHashSet<>(); + case LIST: + return (T) new ArrayList<>(); + default: + // pass + break; + } + + try { + return (T) this.implementClazz().newInstance(); + } catch (Exception e) { + throw new HugeException("Failed to new instance of %s: %s", + this.implementClazz(), e.toString()); + } + } + + /** + * Check property value valid + * + * @param value the property value to be checked data type and cardinality + * @param the property value class + * @return true if data type and cardinality satisfy requirements, + * otherwise false + */ + public boolean checkValueType(V value) { + boolean valid; + + switch (this.cardinality) { + case SINGLE: + valid = this.checkDataType(value); + break; + case SET: + valid = value instanceof Set; + valid = valid && this.checkDataType((Set) value); + break; + case LIST: + valid = value instanceof List; + valid = valid && this.checkDataType((List) value); + break; + default: + throw new AssertionError(String.format( + "Unsupported cardinality: '%s'", this.cardinality)); + } + return valid; + } + + /** + * Check type of the value valid + * + * @param value the property value to be checked data type + * @param the property value original data type + * @return true if the value is or can convert to the data type, + * otherwise false + */ + private boolean checkDataType(V value) { + return this.dataType().clazz().isInstance(value); + } + + /** + * Check type of all the values(maybe some list properties) valid + * + * @param values the property values to be checked data type + * @param the property value class + * @return true if all the values are or can convert to the data type, + * otherwise false + */ + private boolean checkDataType(Collection values) { + boolean valid = true; + for (Object o : values) { + if (!this.checkDataType(o)) { + valid = false; + break; + } + } + return valid; + } + + public Object serialValue(V value, boolean encodeNumber) { + V validValue = this.validValue(value); + E.checkArgument(validValue != null, + "Invalid property value '%s' for key '%s'", + value, this.name()); + E.checkArgument(this.cardinality.single(), + "The cardinality can't be '%s' for navigation key '%s'", + this.cardinality, this.name()); + if (this.dataType.isNumber() || this.dataType.isDate()) { + if (encodeNumber) { + return LongEncoding.encodeNumber(validValue); + } else { + return validValue.toString(); + } + } + return validValue; + } + + public V validValueOrThrow(V value) { + V validValue = this.validValue(value); + if (validValue == null) { + E.checkArgument(false, + "Invalid property value '%s' for key '%s', " + + "expect a value of type %s, actual type %s", + value, this.name(), this.clazz(), + value.getClass().getSimpleName()); + } + return validValue; + } + + public V validValue(V value) { + try { + return this.convValue(value); + } catch (RuntimeException e) { + throw new IllegalArgumentException(String.format( + "Invalid property value '%s' for key '%s': %s", + value, this.name(), e.getMessage())); + } + } + + @SuppressWarnings("unchecked") + private V convValue(V value) { + if (value == null) { + return null; + } + if (this.checkValueType(value)) { + // Same as expected type, no conversion required + return value; + } + + V validValue = null; + Collection validValues; + if (this.cardinality.single()) { + validValue = this.convSingleValue(value); + } else if (value instanceof Collection) { + assert this.cardinality.multiple(); + Collection collection = (Collection) value; + if (value instanceof Set) { + validValues = new LinkedHashSet<>(collection.size()); + } else { + assert value instanceof List; + validValues = new ArrayList<>(collection.size()); + } + for (T element : collection) { + element = this.convSingleValue(element); + if (element == null) { + validValues = null; + break; + } + validValues.add(element); + } + validValue = (V) validValues; + } else { + assert this.cardinality.multiple(); + E.checkArgument(false, + "Property value must be %s, but got '%s'(%s)", + this.cardinality, value, + value.getClass().getSimpleName()); + } + return validValue; + } + + private V convSingleValue(V value) { + if (value == null) { + return null; + } + if (this.dataType().isNumber()) { + @SuppressWarnings("unchecked") + V number = (V) this.dataType().valueToNumber(value); + return number; + } else if (this.dataType().isDate()) { + @SuppressWarnings("unchecked") + V date = (V) this.dataType().valueToDate(value); + return date; + } else if (this.dataType().isUUID()) { + @SuppressWarnings("unchecked") + V uuid = (V) this.dataType().valueToUUID(value); + return uuid; + } else if (this.dataType().isBlob()) { + @SuppressWarnings("unchecked") + V blob = (V) this.dataType().valueToBlob(value); + return blob; + } + + if (this.checkDataType(value)) { + return value; + } + return null; + } + + public String convert2Groovy(boolean attachIdFlag) { + StringBuilder builder = new StringBuilder(SCHEMA_PREFIX); + // Name + if (!attachIdFlag) { + builder.append("propertyKey").append("('") + .append(this.name()) + .append("')"); + } else { + builder.append("propertyKey").append("(") + .append(longId()).append(", '") + .append(this.name()) + .append("')"); + } + + // DataType + switch (this.dataType()) { + case INT: + builder.append(".asInt()"); + break; + case LONG: + builder.append(".asLong()"); + break; + case DOUBLE: + builder.append(".asDouble()"); + break; + case BYTE: + builder.append(".asByte()"); + break; + case DATE: + builder.append(".asDate()"); + break; + case FLOAT: + builder.append(".asFloat()"); + break; + case BLOB: + builder.append(".asBlob()"); + break; + case TEXT: + builder.append(".asText()"); + break; + case UUID: + builder.append(".asUUID()"); + break; + case OBJECT: + builder.append(".asObject()"); + break; + case BOOLEAN: + builder.append(".asBoolean()"); + break; + default: + throw new AssertionError(String.format( + "Invalid data type '%s'", this.dataType())); + } + + // Cardinality + switch (this.cardinality()) { + case SINGLE: + // Single is default, prefer not output + break; + case SET: + builder.append(".valueSet()"); + break; + case LIST: + builder.append(".valueList()"); + break; + default: + throw new AssertionError(String.format( + "Invalid cardinality '%s'", this.cardinality())); + } + + // Aggregate type + switch (this.aggregateType()) { + case NONE: + // NONE is default, prefer not output + break; + case MAX: + builder.append(".calcMax()"); + break; + case MIN: + builder.append(".calcMin()"); + break; + case SUM: + builder.append(".calcSum()"); + break; + case LIST: + builder.append(".calcList()"); + break; + case SET: + builder.append(".calcSet()"); + break; + case OLD: + builder.append(".calcOld()"); + break; + default: + throw new AssertionError(String.format( + "Invalid cardinality '%s'", this.aggregateType())); + } + + // Write type + switch (this.writeType()) { + case OLTP: + // OLTP is default, prefer not output + break; + case OLAP_COMMON: + builder.append(".writeType('") + .append(OLAP_COMMON) + .append("')"); + break; + case OLAP_RANGE: + builder.append(".writeType('") + .append(OLAP_RANGE) + .append("')"); + break; + case OLAP_SECONDARY: + builder.append(".writeType('") + .append(OLAP_SECONDARY) + .append("')"); + break; + default: + throw new AssertionError(String.format( + "Invalid write type '%s'", this.writeType())); + } + + // User data + Map userdata = this.userdata(); + if (userdata.isEmpty()) { + return builder.toString(); + } + for (Map.Entry entry : userdata.entrySet()) { + if (GraphUtils.isHidden(entry.getKey())) { + continue; + } + builder.append(".userdata('") + .append(entry.getKey()) + .append("',") + .append(entry.getValue()) + .append(")"); + } + + builder.append(".ifNotExist().create();"); + return builder.toString(); + } + + public interface Builder extends SchemaBuilder { + + TaskWithSchema createWithTask(); + + Builder asText(); + + Builder asInt(); + + Builder asDate(); + + Builder asUUID(); + + Builder asBoolean(); + + Builder asByte(); + + Builder asBlob(); + + Builder asDouble(); + + Builder asFloat(); + + Builder asLong(); + + Builder valueSingle(); + + Builder valueList(); + + Builder valueSet(); + + Builder calcMax(); + + Builder calcMin(); + + Builder calcSum(); + + Builder calcOld(); + + Builder calcSet(); + + Builder calcList(); + + Builder writeType(WriteType writeType); + + Builder cardinality(Cardinality cardinality); + + Builder dataType(DataType dataType); + + Builder aggregateType(AggregateType aggregateType); + + Builder userdata(String key, Object value); + + Builder userdata(Map userdata); + } + + @Override + public Map asMap() { + Map map = new HashMap<>(); + + if (this.dataType != null) { + map.put(P.DATA_TYPE, this.dataType.string()); + } + + if (this.cardinality != null) { + map.put(P.CARDINALITY, this.cardinality.string()); + } + + if (this.aggregateType != null) { + map.put(P.AGGREGATE_TYPE, this.aggregateType.string()); + } + + if (this.writeType != null) { + map.put(P.WRITE_TYPE, this.writeType.string()); + } + + return super.asMap(map); + } + + // change from HugeGraphSupplier HugeGraphSupplier by 2023/3/30 GraphPlatform-2062 core 拆分合入 3.7.0 + @SuppressWarnings("unchecked") + public static PropertyKey fromMap(Map map, HugeGraphSupplier graph) { + Id id = IdGenerator.of((int) map.get(P.ID)); + String name = (String) map.get(P.NAME); + + PropertyKey propertyKey = new PropertyKey(graph, id, name); + for (Map.Entry entry : map.entrySet()) { + switch (entry.getKey()) { + case P.ID: + case P.NAME: + break; + case P.STATUS: + propertyKey.status(SchemaStatus.valueOf(((String) entry.getValue()).toUpperCase())); + break; + case P.USERDATA: + propertyKey.userdata((Map) entry.getValue()); + break; + case P.AGGREGATE_TYPE: + propertyKey.aggregateType(AggregateType.valueOf(((String) entry.getValue()).toUpperCase())); + break; + case P.WRITE_TYPE: + propertyKey.writeType(WriteType.valueOf(((String) entry.getValue()).toUpperCase())); + break; + case P.DATA_TYPE: + propertyKey.dataType(DataType.valueOf(((String) entry.getValue()).toUpperCase())); + break; + case P.CARDINALITY: + propertyKey.cardinality(Cardinality.valueOf(((String) entry.getValue()).toUpperCase())); + break; + default: + throw new AssertionError(String.format( + "Invalid key '%s' for property key", + entry.getKey())); + } + } + return propertyKey; + } + + public static final class P { + + public static final String ID = "id"; + public static final String NAME = "name"; + + public static final String STATUS = "status"; + public static final String USERDATA = "userdata"; + + public static final String DATA_TYPE = "data_type"; + public static final String CARDINALITY = "cardinality"; + + public static final String AGGREGATE_TYPE = "aggregate_type"; + public static final String WRITE_TYPE = "write_type"; + } +} diff --git a/hugegraph-struct/src/main/java/org/apache/hugegraph/schema/SchemaElement.java b/hugegraph-struct/src/main/java/org/apache/hugegraph/schema/SchemaElement.java new file mode 100644 index 0000000000..38946d81e5 --- /dev/null +++ b/hugegraph-struct/src/main/java/org/apache/hugegraph/schema/SchemaElement.java @@ -0,0 +1,259 @@ +/* + * 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.schema; + +import java.util.Collections; +import java.util.Map; + +import org.apache.hugegraph.HugeGraphSupplier; + +import org.apache.hugegraph.exception.HugeException; +import org.apache.hugegraph.id.Id; +import org.apache.hugegraph.id.IdGenerator; +import org.apache.hugegraph.type.Namifiable; +import org.apache.hugegraph.type.Typifiable; +import org.apache.hugegraph.type.define.SchemaStatus; +import org.apache.hugegraph.util.E; + + +import com.google.common.base.Objects; + +import org.apache.hugegraph.util.GraphUtils; + +public abstract class SchemaElement implements Namifiable, Typifiable, + Cloneable { + + public static final int MAX_PRIMITIVE_SYS_ID = 32; + public static final int NEXT_PRIMITIVE_SYS_ID = 8; + + // ABS of system schema id must be below MAX_PRIMITIVE_SYS_ID + protected static final int VL_IL_ID = -1; + protected static final int EL_IL_ID = -2; + protected static final int PKN_IL_ID = -3; + protected static final int VLN_IL_ID = -4; + protected static final int ELN_IL_ID = -5; + protected static final int ILN_IL_ID = -6; + protected static final int OLAP_VL_ID = -7; + + // OLAP_ID means all of vertex label ids + public static final Id OLAP_ID = IdGenerator.of(-7); + // OLAP means all of vertex label names + public static final String OLAP = "~olap"; + + public static final Id NONE_ID = IdGenerator.ZERO; + + public static final String UNDEF = "~undefined"; + + protected static final String SCHEMA_PREFIX = "graph.schema()."; + + protected final HugeGraphSupplier graph; + + private final Id id; + private final String name; + private final Userdata userdata; + private SchemaStatus status; + + public SchemaElement(final HugeGraphSupplier graph, Id id, String name) { + E.checkArgumentNotNull(id, "SchemaElement id can't be null"); + E.checkArgumentNotNull(name, "SchemaElement name can't be null"); + this.graph = graph; + this.id = id; + this.name = name; + this.userdata = new Userdata(); + this.status = SchemaStatus.CREATED; + } + + public HugeGraphSupplier graph() { + return this.graph; + } + + public Id id() { + return this.id; + } + + public long longId() { + return this.id.asLong(); + } + + @Override + public String name() { + return this.name; + } + + public Map userdata() { + return Collections.unmodifiableMap(this.userdata); + } + + public void userdata(String key, Object value) { + E.checkArgumentNotNull(key, "userdata key"); + E.checkArgumentNotNull(value, "userdata value"); + this.userdata.put(key, value); + } + + public void userdata(Userdata userdata) { + this.userdata.putAll(userdata); + } + + public void userdata(Map userdata) { + this.userdata.putAll(userdata); + } + + public void removeUserdata(String key) { + E.checkArgumentNotNull(key, "The userdata key can't be null"); + this.userdata.remove(key); + } + + public void removeUserdata(Userdata userdata) { + for (String key : userdata.keySet()) { + this.userdata.remove(key); + } + } + + public SchemaStatus status() { + return this.status; + } + + public void status(SchemaStatus status) { + this.status = status; + } + + public boolean system() { + return this.longId() < 0L; + } + + public boolean primitive() { + long id = this.longId(); + return -MAX_PRIMITIVE_SYS_ID <= id && id < 0L; + } + + public boolean hidden() { + return GraphUtils.isHidden(this.name()); + } + + public SchemaElement copy() { + try { + return (SchemaElement) super.clone(); + } catch (CloneNotSupportedException e) { + throw new HugeException("Failed to clone schema", e); + } + } + + public boolean hasSameContent(SchemaElement other) { + return Objects.equal(this.name(), other.name()) && + Objects.equal(this.userdata(), other.userdata()); + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof SchemaElement)) { + return false; + } + + SchemaElement other = (SchemaElement) obj; + return this.type() == other.type() && this.id.equals(other.id()); + } + + @Override + public int hashCode() { + return this.type().hashCode() ^ this.id.hashCode(); + } + + @Override + public String toString() { + return String.format("%s(id=%s)", this.name, this.id); + } + + public static int schemaId(Id id) { + long l = id.asLong(); + // Currently we limit the schema id to within 4 bytes + E.checkArgument(Integer.MIN_VALUE <= l && l <= Integer.MAX_VALUE, + "Schema id is out of bound: %s", l); + return (int) l; + } + + public static class TaskWithSchema { + + private SchemaElement schemaElement; + private Id task; + + public TaskWithSchema(SchemaElement schemaElement, Id task) { + E.checkNotNull(schemaElement, "schema element"); + this.schemaElement = schemaElement; + this.task = task; + } + + public void propertyKey(PropertyKey propertyKey) { + E.checkNotNull(propertyKey, "property key"); + this.schemaElement = propertyKey; + } + + public void indexLabel(IndexLabel indexLabel) { + E.checkNotNull(indexLabel, "index label"); + this.schemaElement = indexLabel; + } + + public PropertyKey propertyKey() { + E.checkState(this.schemaElement instanceof PropertyKey, + "Expect property key, but actual schema type is " + + "'%s'", this.schemaElement.getClass()); + return (PropertyKey) this.schemaElement; + } + + public IndexLabel indexLabel() { + E.checkState(this.schemaElement instanceof IndexLabel, + "Expect index label, but actual schema type is " + + "'%s'", this.schemaElement.getClass()); + return (IndexLabel) this.schemaElement; + } + + public SchemaElement schemaElement() { + return this.schemaElement; + } + + public Id task() { + return this.task; + } + } + + public abstract Map asMap(); + + public Map asMap(Map map) { + E.checkState(this.id != null, + "Property key id can't be null"); + E.checkState(this.name != null, + "Property key name can't be null"); + E.checkState(this.status != null, + "Property status can't be null"); + + map.put(P.ID, this.id); + map.put(P.NAME, this.name); + map.put(P.STATUS, this.status.string()); + map.put(P.USERDATA, this.userdata); + + return map; + } + + public static final class P { + + public static final String ID = "id"; + public static final String NAME = "name"; + + public static final String STATUS = "status"; + public static final String USERDATA = "userdata"; + } +} diff --git a/hugegraph-struct/src/main/java/org/apache/hugegraph/schema/SchemaLabel.java b/hugegraph-struct/src/main/java/org/apache/hugegraph/schema/SchemaLabel.java new file mode 100644 index 0000000000..74a059c5ca --- /dev/null +++ b/hugegraph-struct/src/main/java/org/apache/hugegraph/schema/SchemaLabel.java @@ -0,0 +1,204 @@ +/* + * 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.schema; + +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +import org.apache.hugegraph.HugeGraphSupplier; +import org.apache.hugegraph.exception.HugeException; +import org.apache.hugegraph.id.Id; +import org.apache.hugegraph.id.IdGenerator; +import org.apache.hugegraph.type.HugeType; +import org.apache.hugegraph.type.Indexfiable; +import org.apache.hugegraph.type.Propfiable; +import org.apache.hugegraph.util.E; + +import com.google.common.base.Objects; + +public abstract class SchemaLabel extends SchemaElement + implements Indexfiable, Propfiable { + + private final Set properties; + private final Set nullableKeys; + private final Set indexLabels; + private boolean enableLabelIndex; + private long ttl; + private Id ttlStartTime; + + public SchemaLabel(final HugeGraphSupplier graph, Id id, String name) { + super(graph, id, name); + this.properties = new HashSet<>(); + this.nullableKeys = new HashSet<>(); + this.indexLabels = new HashSet<>(); + this.enableLabelIndex = true; + this.ttl = 0L; + this.ttlStartTime = SchemaElement.NONE_ID; + } + + @Override + public Set properties() { + return Collections.unmodifiableSet(this.properties); + } + + public Set extendProperties() { + return this.properties(); + } + + public void properties(Set properties) { + this.properties.addAll(properties); + } + + public SchemaLabel properties(Id... ids) { + this.properties.addAll(Arrays.asList(ids)); + return this; + } + + public void property(Id id) { + this.properties.add(id); + } + + public Set nullableKeys() { + return Collections.unmodifiableSet(this.nullableKeys); + } + + public void nullableKey(Id id) { + this.nullableKeys.add(id); + } + + public void nullableKeys(Id... ids) { + this.nullableKeys.addAll(Arrays.asList(ids)); + } + + public void nullableKeys(Set nullableKeys) { + this.nullableKeys.addAll(nullableKeys); + } + + @Override + public Set indexLabels() { + return Collections.unmodifiableSet(this.indexLabels); + } + + public Set extendIndexLabels() { + return this.indexLabels(); + } + + public void indexLabel(Id id) { + this.indexLabels.add(id); + } + + public void indexLabels(Id... ids) { + this.indexLabels.addAll(Arrays.asList(ids)); + } + + public void addIndexLabel(Id id) { + this.indexLabels.add(id); + } + + public void addIndexLabels(Id... ids) { + this.indexLabels.addAll(Arrays.asList(ids)); + } + + public boolean existsIndexLabel() { + return !this.indexLabels().isEmpty(); + } + + public void removeIndexLabel(Id id) { + this.indexLabels.remove(id); + } + + public boolean enableLabelIndex() { + return this.enableLabelIndex; + } + + public void enableLabelIndex(boolean enable) { + this.enableLabelIndex = enable; + } + + public boolean undefined() { + return this.name() == UNDEF; + } + + public void ttl(long ttl) { + assert ttl >= 0L; + this.ttl = ttl; + } + + public long ttl() { + assert this.ttl >= 0L; + return this.ttl; + } + + public void ttlStartTime(Id id) { + this.ttlStartTime = id; + } + + public Id ttlStartTime() { + return this.ttlStartTime; + } + + public String ttlStartTimeName() { + return NONE_ID.equals(this.ttlStartTime) ? null : + this.graph.propertyKey(this.ttlStartTime).name(); + } + + public boolean hasSameContent(SchemaLabel other) { + return super.hasSameContent(other) && this.ttl == other.ttl && + this.enableLabelIndex == other.enableLabelIndex && + Objects.equal(this.graph.mapPkId2Name(this.properties), + other.graph.mapPkId2Name(other.properties)) && + Objects.equal(this.graph.mapPkId2Name(this.nullableKeys), + other.graph.mapPkId2Name(other.nullableKeys)) && + Objects.equal(this.graph.mapIlId2Name(this.indexLabels), + other.graph.mapIlId2Name(other.indexLabels)) && + Objects.equal(this.ttlStartTimeName(), other.ttlStartTimeName()); + } + + public static Id getLabelId(HugeGraphSupplier graph, HugeType type, Object label) { + E.checkNotNull(graph, "graph"); + E.checkNotNull(type, "type"); + E.checkNotNull(label, "label"); + if (label instanceof Number) { + return IdGenerator.of(((Number) label).longValue()); + } else if (label instanceof String) { + if (type.isVertex()) { + return graph.vertexLabel((String) label).id(); + } else if (type.isEdge()) { + return graph.edgeLabel((String) label).id(); + } else { + throw new HugeException( + "Not support query from '%s' with label '%s'", + type, label); + } + } else { + throw new HugeException( + "The label type must be number or string, but got '%s'", + label.getClass()); + } + } + + public static Id getVertexLabelId(HugeGraphSupplier graph, Object label) { + return SchemaLabel.getLabelId(graph, HugeType.VERTEX, label); + } + + public static Id getEdgeLabelId(HugeGraphSupplier graph, Object label) { + return SchemaLabel.getLabelId(graph, HugeType.EDGE, label); + } +} diff --git a/hugegraph-struct/src/main/java/org/apache/hugegraph/schema/Userdata.java b/hugegraph-struct/src/main/java/org/apache/hugegraph/schema/Userdata.java new file mode 100644 index 0000000000..d485e558b8 --- /dev/null +++ b/hugegraph-struct/src/main/java/org/apache/hugegraph/schema/Userdata.java @@ -0,0 +1,64 @@ +/* + * 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.schema; + +import java.util.HashMap; +import java.util.Map; + +import org.apache.hugegraph.exception.NotAllowException; +import org.apache.hugegraph.type.define.Action; + +public class Userdata extends HashMap { + + private static final long serialVersionUID = -1235451175617197049L; + + public static final String CREATE_TIME = "~create_time"; + public static final String DEFAULT_VALUE = "~default_value"; + + public Userdata() { + } + + public Userdata(Map map) { + this.putAll(map); + } + + public static void check(Userdata userdata, Action action) { + if (userdata == null) { + return; + } + switch (action) { + case INSERT: + case APPEND: + for (Map.Entry e : userdata.entrySet()) { + if (e.getValue() == null) { + throw new NotAllowException( + "Not allowed to pass null userdata value " + + "when create or append schema"); + } + } + break; + case ELIMINATE: + case DELETE: + // pass + break; + default: + throw new AssertionError(String.format( + "Unknown schema action '%s'", action)); + } + } +} diff --git a/hugegraph-struct/src/main/java/org/apache/hugegraph/schema/VertexLabel.java b/hugegraph-struct/src/main/java/org/apache/hugegraph/schema/VertexLabel.java new file mode 100644 index 0000000000..d6dbba29e1 --- /dev/null +++ b/hugegraph-struct/src/main/java/org/apache/hugegraph/schema/VertexLabel.java @@ -0,0 +1,414 @@ +/* + * 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.schema; + +import java.util.ArrayList; +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.hugegraph.HugeGraphSupplier; +import org.apache.hugegraph.id.Id; +import org.apache.hugegraph.id.IdGenerator; +import org.apache.hugegraph.schema.builder.SchemaBuilder; +import org.apache.hugegraph.type.HugeType; +import org.apache.hugegraph.type.define.IdStrategy; +import org.apache.hugegraph.type.define.SchemaStatus; +import org.apache.hugegraph.util.GraphUtils; + +import com.google.common.base.Objects; +import com.google.common.collect.ImmutableSet; + +public class VertexLabel extends SchemaLabel { + + public static final VertexLabel NONE = new VertexLabel(null, NONE_ID, UNDEF); + public static final VertexLabel GENERAL = + new VertexLabel(null, NONE_ID, VertexLabel.GENERAL_VL); + + + // OLAP_VL_ID means all of vertex label ids + private static final Id OLAP_VL_ID = IdGenerator.of(SchemaLabel.OLAP_VL_ID); + // OLAP_VL_NAME means all of vertex label names + private static final String OLAP_VL_NAME = "*olap"; + // OLAP_VL means all of vertex labels + public static final VertexLabel OLAP_VL = new VertexLabel(null, OLAP_VL_ID, + OLAP_VL_NAME); + + public static final String GENERAL_VL = "~general_vl"; + + private IdStrategy idStrategy; + private List primaryKeys; + + public VertexLabel(final HugeGraphSupplier graph, Id id, String name) { + super(graph, id, name); + this.idStrategy = IdStrategy.DEFAULT; + this.primaryKeys = new ArrayList<>(); + } + + @Override + public HugeType type() { + return HugeType.VERTEX_LABEL; + } + + public boolean olap() { + return VertexLabel.OLAP_VL.id().equals(this.id()); + } + + public IdStrategy idStrategy() { + return this.idStrategy; + } + + public void idStrategy(IdStrategy idStrategy) { + this.idStrategy = idStrategy; + } + + public List primaryKeys() { + return Collections.unmodifiableList(this.primaryKeys); + } + + public void primaryKey(Id id) { + this.primaryKeys.add(id); + } + + public void primaryKeys(Id... ids) { + this.primaryKeys.addAll(Arrays.asList(ids)); + } + + + @Override + public Set extendProperties() { + Set properties = new HashSet<>(); + properties.addAll(this.properties()); + properties.addAll(this.primaryKeys); + + this.graph().propertyKeys().stream().forEach(pk -> { + if (pk.olap()) { + properties.add(pk.id()); + } + }); + + return Collections.unmodifiableSet(properties); + } + + @Override + public Set extendIndexLabels() { + Set indexes = new HashSet<>(); + + indexes.addAll(this.indexLabels()); + + for (IndexLabel il : this.graph.indexLabels()) { + if (il.olap()) { + indexes.add(il.id()); + } + } + + return ImmutableSet.copyOf(indexes); + } + + public boolean existsLinkLabel() { + return this.graph().existsLinkLabel(this.id()); + } + + public boolean hasSameContent(VertexLabel other) { + return super.hasSameContent(other) && + this.idStrategy == other.idStrategy && + Objects.equal(this.graph.mapPkId2Name(this.primaryKeys), + other.graph.mapPkId2Name(other.primaryKeys)); + } + + public static VertexLabel undefined(HugeGraphSupplier graph) { + return new VertexLabel(graph, NONE_ID, UNDEF); + } + + public static VertexLabel undefined(HugeGraphSupplier graph, Id id) { + return new VertexLabel(graph, id, UNDEF); + } + + public String convert2Groovy(boolean attachIdFlag) { + StringBuilder builder = new StringBuilder(SCHEMA_PREFIX); + // Name + if (!attachIdFlag) { + builder.append("vertexLabel").append("('") + .append(this.name()) + .append("')"); + } else { + builder.append("vertexLabel").append("(") + .append(longId()).append(", '") + .append(this.name()) + .append("')"); + } + + // Properties + Set properties = this.properties(); + if (!properties.isEmpty()) { + builder.append(".").append("properties("); + + int size = properties.size(); + for (Id id : this.properties()) { + PropertyKey pk = this.graph.propertyKey(id); + builder.append("'") + .append(pk.name()) + .append("'"); + if (--size > 0) { + builder.append(","); + } + } + builder.append(")"); + } + + // Id strategy + switch (this.idStrategy()) { + case PRIMARY_KEY: + builder.append(".primaryKeys("); + List pks = this.primaryKeys(); + int size = pks.size(); + for (Id id : pks) { + PropertyKey pk = this.graph.propertyKey(id); + builder.append("'") + .append(pk.name()) + .append("'"); + if (--size > 0) { + builder.append(","); + } + } + builder.append(")"); + break; + case CUSTOMIZE_STRING: + builder.append(".useCustomizeStringId()"); + break; + case CUSTOMIZE_NUMBER: + builder.append(".useCustomizeNumberId()"); + break; + case CUSTOMIZE_UUID: + builder.append(".useCustomizeUuidId()"); + break; + case AUTOMATIC: + builder.append(".useAutomaticId()"); + break; + default: + throw new AssertionError(String.format( + "Invalid id strategy '%s'", this.idStrategy())); + } + + // Nullable keys + properties = this.nullableKeys(); + if (!properties.isEmpty()) { + builder.append(".").append("nullableKeys("); + int size = properties.size(); + for (Id id : properties) { + PropertyKey pk = this.graph.propertyKey(id); + builder.append("'") + .append(pk.name()) + .append("'"); + if (--size > 0) { + builder.append(","); + } + } + builder.append(")"); + } + + // TTL + if (this.ttl() != 0) { + builder.append(".ttl(") + .append(this.ttl()) + .append(")"); + if (this.ttlStartTime() != null && + !this.ttlStartTime().equals(SchemaLabel.NONE_ID)) { + PropertyKey pk = this.graph.propertyKey(this.ttlStartTime()); + builder.append(".ttlStartTime('") + .append(pk.name()) + .append("')"); + } + } + + // Enable label index + if (this.enableLabelIndex()) { + builder.append(".enableLabelIndex(true)"); + } else { + builder.append(".enableLabelIndex(false)"); + } + + // User data + Map userdata = this.userdata(); + if (userdata.isEmpty()) { + return builder.toString(); + } + for (Map.Entry entry : userdata.entrySet()) { + if (GraphUtils.isHidden(entry.getKey())) { + continue; + } + builder.append(".userdata('") + .append(entry.getKey()) + .append("',") + .append(entry.getValue()) + .append(")"); + } + + builder.append(".ifNotExist().create();"); + return builder.toString(); + } + + public interface Builder extends SchemaBuilder { + + Id rebuildIndex(); + + Builder idStrategy(IdStrategy idStrategy); + + Builder useAutomaticId(); + + Builder usePrimaryKeyId(); + + Builder useCustomizeStringId(); + + Builder useCustomizeNumberId(); + + Builder useCustomizeUuidId(); + + Builder properties(String... properties); + + Builder primaryKeys(String... keys); + + Builder nullableKeys(String... keys); + + Builder ttl(long ttl); + + Builder ttlStartTime(String ttlStartTime); + + Builder enableLabelIndex(boolean enable); + + Builder userdata(String key, Object value); + + Builder userdata(Map userdata); + } + + @Override + public Map asMap() { + HashMap map = new HashMap(); + + map.put(P.PROPERTIES, this.properties()); + + map.put(P.NULLABLE_KEYS, this.nullableKeys()); + + map.put(P.INDEX_LABELS, this.indexLabels()); + + map.put(P.ENABLE_LABEL_INDEX, this.enableLabelIndex()); + + map.put(P.TTL, String.valueOf(this.ttl())); + + map.put(P.TT_START_TIME, this.ttlStartTime().asString()); + + map.put(P.ID_STRATEGY, this.idStrategy().string()); + + map.put(P.PRIMARY_KEYS, this.primaryKeys()); + + return super.asMap(map); + } + + public boolean generalVl(){ + return this.name() == GENERAL_VL; + } + + @SuppressWarnings("unchecked") + public static VertexLabel fromMap(Map map, HugeGraphSupplier graph) { + Id id = IdGenerator.of((int) map.get(VertexLabel.P.ID)); + String name = (String) map.get(VertexLabel.P.NAME); + + VertexLabel vertexLabel = new VertexLabel(graph, id, name); + for (Map.Entry entry : map.entrySet()) { + switch (entry.getKey()) { + case P.ID: + case P.NAME: + break; + case P.STATUS: + vertexLabel.status( + SchemaStatus.valueOf(((String) entry.getValue()).toUpperCase())); + break; + case P.USERDATA: + vertexLabel.userdata(new Userdata((Map) entry.getValue())); + break; + case P.PROPERTIES: + Set ids = ((List) entry.getValue()).stream().map( + IdGenerator::of).collect(Collectors.toSet()); + vertexLabel.properties(ids); + break; + case P.NULLABLE_KEYS: + ids = ((List) entry.getValue()).stream().map( + IdGenerator::of).collect(Collectors.toSet()); + vertexLabel.nullableKeys(ids); + break; + case P.INDEX_LABELS: + ids = ((List) entry.getValue()).stream().map( + IdGenerator::of).collect(Collectors.toSet()); + vertexLabel.addIndexLabels(ids.toArray(new Id[0])); + break; + case P.ENABLE_LABEL_INDEX: + boolean enableLabelIndex = (Boolean) entry.getValue(); + vertexLabel.enableLabelIndex(enableLabelIndex); + break; + case P.TTL: + long ttl = Long.parseLong((String) entry.getValue()); + vertexLabel.ttl(ttl); + break; + case P.TT_START_TIME: + long ttlStartTime = + Long.parseLong((String) entry.getValue()); + vertexLabel.ttlStartTime(IdGenerator.of(ttlStartTime)); + break; + case P.ID_STRATEGY: + IdStrategy idStrategy = + IdStrategy.valueOf(((String) entry.getValue()).toUpperCase()); + vertexLabel.idStrategy(idStrategy); + break; + case P.PRIMARY_KEYS: + ids = ((List) entry.getValue()).stream().map( + IdGenerator::of).collect(Collectors.toSet()); + vertexLabel.primaryKeys(ids.toArray(new Id[0])); + break; + default: + throw new AssertionError(String.format( + "Invalid key '%s' for vertex label", + entry.getKey())); + } + } + return vertexLabel; + } + + public static final class P { + + public static final String ID = "id"; + public static final String NAME = "name"; + + public static final String STATUS = "status"; + public static final String USERDATA = "userdata"; + + public static final String PROPERTIES = "properties"; + public static final String NULLABLE_KEYS = "nullableKeys"; + public static final String INDEX_LABELS = "indexLabels"; + + public static final String ENABLE_LABEL_INDEX = "enableLabelIndex"; + public static final String TTL = "ttl"; + public static final String TT_START_TIME = "ttlStartTime"; + public static final String ID_STRATEGY = "idStrategy"; + public static final String PRIMARY_KEYS = "primaryKeys"; + } +} diff --git a/hugegraph-struct/src/main/java/org/apache/hugegraph/schema/builder/SchemaBuilder.java b/hugegraph-struct/src/main/java/org/apache/hugegraph/schema/builder/SchemaBuilder.java new file mode 100644 index 0000000000..7b65509819 --- /dev/null +++ b/hugegraph-struct/src/main/java/org/apache/hugegraph/schema/builder/SchemaBuilder.java @@ -0,0 +1,42 @@ +/* + * Copyright 2017 HugeGraph Authors + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.schema.builder; + +import org.apache.hugegraph.id.Id; +import org.apache.hugegraph.schema.SchemaElement; + +public interface SchemaBuilder { + + public SchemaBuilder id(long id); + + public T build(); + + public T create(); + + public T append(); + + public T eliminate(); + + public Id remove(); + + public SchemaBuilder ifNotExist(); + + public SchemaBuilder checkExist(boolean checkExist); +} diff --git a/hugegraph-struct/src/main/java/org/apache/hugegraph/serializer/BinaryElementSerializer.java b/hugegraph-struct/src/main/java/org/apache/hugegraph/serializer/BinaryElementSerializer.java new file mode 100644 index 0000000000..8e18ccbba6 --- /dev/null +++ b/hugegraph-struct/src/main/java/org/apache/hugegraph/serializer/BinaryElementSerializer.java @@ -0,0 +1,536 @@ +/* + * Copyright 2017 HugeGraph Authors + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.serializer; + +import static org.apache.hugegraph.schema.SchemaElement.UNDEF; + +import java.util.Arrays; +import java.util.Base64; +import java.util.Collection; +import java.util.Map; + +import org.apache.commons.lang.ArrayUtils; +import org.apache.commons.lang.NotImplementedException; +import org.apache.hugegraph.util.Bytes; +import org.apache.hugegraph.util.E; +import org.apache.hugegraph.util.Log; +import org.slf4j.Logger; + +import org.apache.hugegraph.HugeGraphSupplier; +import org.apache.hugegraph.backend.BackendColumn; +import org.apache.hugegraph.backend.BinaryId; +import org.apache.hugegraph.exception.HugeException; +import org.apache.hugegraph.id.EdgeId; +import org.apache.hugegraph.id.Id; +import org.apache.hugegraph.id.IdGenerator; +import org.apache.hugegraph.schema.EdgeLabel; +import org.apache.hugegraph.schema.PropertyKey; +import org.apache.hugegraph.schema.SchemaElement; +import org.apache.hugegraph.schema.VertexLabel; +import org.apache.hugegraph.structure.BaseEdge; +import org.apache.hugegraph.structure.BaseElement; +import org.apache.hugegraph.structure.BaseProperty; +import org.apache.hugegraph.structure.BaseVertex; +import org.apache.hugegraph.structure.Index; +import org.apache.hugegraph.type.HugeType; +import org.apache.hugegraph.type.define.Cardinality; +import org.apache.hugegraph.type.define.EdgeLabelType; +import org.apache.hugegraph.util.StringEncoding; +import com.google.common.primitives.Longs; + +public class BinaryElementSerializer { + static final BinaryElementSerializer INSTANCE = + new BinaryElementSerializer(); + static Logger log = Log.logger(BinaryElementSerializer.class); + + public static BinaryElementSerializer getInstance() { + return INSTANCE; + } + + protected void parseProperty(HugeGraphSupplier graph, Id pkeyId, + BytesBuffer buffer, + BaseElement owner) { + PropertyKey pkey = graph != null ? + graph.propertyKey(pkeyId) : + new PropertyKey(graph, pkeyId, ""); + // Parse value + Object value = buffer.readProperty(pkey); + // Set properties of vertex/edge + if (pkey.cardinality() == Cardinality.SINGLE) { + owner.addProperty(pkey, value); + } else { + if (!(value instanceof Collection)) { + throw new HugeException( + "Invalid value of non-single property: %s", value); + } + owner.addProperty(pkey, value); + } + } + + public void parseProperties(HugeGraphSupplier graph, BytesBuffer buffer, + BaseElement owner) { + int size = buffer.readVInt(); + assert size >= 0; + for (int i = 0; i < size; i++) { + Id pkeyId = IdGenerator.of(buffer.readVInt()); + this.parseProperty(graph, pkeyId, buffer, owner); + } + } + + /** + * 将顶点 kv 数据反序列成 BaseVertex 类型顶点 + * + * @param vertexCol 必须是顶点数据的 column + * @param vertex vertex==null 时,用于算子下沉,将 col 数据反序列成 BaseVertex ; + * vertex!=null 时,将 col 信息增加到 vertex 中 + */ + public BaseVertex parseVertex(HugeGraphSupplier graph, BackendColumn vertexCol, + BaseVertex vertex) { + if (vertex == null) { + BinaryId binaryId = + BytesBuffer.wrap(vertexCol.name).parseId(HugeType.VERTEX); + vertex = new BaseVertex(binaryId.origin(), VertexLabel.NONE); + } + + if (ArrayUtils.isEmpty(vertexCol.value)) { + // 不需要解析 vertex 的 properties + return vertex; + } + BytesBuffer buffer = BytesBuffer.wrap(vertexCol.value); + Id labelId = buffer.readId(); + // Parse vertex label + if (graph != null) { + VertexLabel label = graph.vertexLabelOrNone(labelId); + vertex.correctVertexLabel(label); + } else { + VertexLabel label = new VertexLabel(null, labelId, UNDEF); + vertex.correctVertexLabel(label); + } + // Parse properties + this.parseProperties(graph, buffer, vertex); + + // Parse vertex expired time if needed + if (buffer.remaining() > 0 /*edge.hasTtl()*/) { + this.parseExpiredTime(buffer, vertex); + } + return vertex; + } + + /** + * 将顶点 kv 数据反序列成 BaseVertex 类型顶点 + * + * @param olapVertexCol 必须是顶点数据的 column + * @param vertex vertex==null 时,用于算子下沉,将 col 数据反序列成 olapBaseVertex ; + * vertex!=null 时,将 col 信息增加到 olapBaseVertex 中 + */ + public BaseVertex parseVertexOlap(HugeGraphSupplier graph, + BackendColumn olapVertexCol, BaseVertex vertex) { + if (vertex == null) { + BytesBuffer idBuffer = BytesBuffer.wrap(olapVertexCol.name); + // read olap property id + idBuffer.readId(); + // read vertex id which olap property belongs to + Id vertexId = idBuffer.readId(); + vertex = new BaseVertex(vertexId, VertexLabel.NONE); + } + + BytesBuffer buffer = BytesBuffer.wrap(olapVertexCol.value); + Id pkeyId = IdGenerator.of(buffer.readVInt()); + this.parseProperty(graph, pkeyId, buffer, vertex); + return vertex; + } + + /** + * @param cols 反序列化出一个完整的顶点有可能需要多个 cols, + * 第一个 col 是代表 g+v 表中的普通顶点信息,之后每个 col 代表 olap 表中存储的 olap 顶点 + */ + public BaseVertex parseVertexFromCols(HugeGraphSupplier graph, + BackendColumn... cols) { + assert cols.length > 0; + BaseVertex vertex = null; + for (int index = 0; index < cols.length; index++) { + BackendColumn col = cols[index]; + if (index == 0) { + vertex = this.parseVertex(graph, col, vertex); + } else { + this.parseVertexOlap(graph, col, vertex); + } + } + return vertex; + } + + public BaseEdge parseEdge(HugeGraphSupplier graph, BackendColumn edgeCol, + BaseVertex ownerVertex, + boolean withEdgeProperties) { + // owner-vertex + dir + edge-label.id() + subLabel.id() + + // + sort-values + other-vertex + + BytesBuffer buffer = BytesBuffer.wrap(edgeCol.name); + // Consume owner-vertex id + Id id = buffer.readId(); + if (ownerVertex == null) { + ownerVertex = new BaseVertex(id, VertexLabel.NONE); + } + + E.checkState(buffer.remaining() > 0, "Missing column type"); + + byte type = buffer.read(); + if (type == HugeType.EDGE_IN.code() || + type == HugeType.EDGE_OUT.code()) { + E.checkState(true, + "Invalid column(%s) with unknown type(%s): 0x%s", + id, type & 0xff, Bytes.toHex(edgeCol.name)); + } + + Id labelId = buffer.readId(); + Id subLabelId = buffer.readId(); + String sortValues = buffer.readStringWithEnding(); + Id otherVertexId = buffer.readId(); + boolean direction = EdgeId.isOutDirectionFromCode(type); + BaseEdge edge; + EdgeLabel edgeLabel; + if (graph == null) { /* when calculation sinking */ + edgeLabel = new EdgeLabel(null, subLabelId, UNDEF); + // 如果这里不相等,需要加上 fatherId,以便正确的算子下沉 + if (subLabelId != labelId) { + edgeLabel.edgeLabelType(EdgeLabelType.SUB); + edgeLabel.fatherId(labelId); + } + + } else { + edgeLabel = graph.edgeLabelOrNone(subLabelId); + } + edge = BaseEdge.constructEdge(graph, ownerVertex, direction, + edgeLabel, sortValues, otherVertexId); + + if (!withEdgeProperties /*&& !edge.hasTtl()*/) { + // only skip properties for edge without ttl + // todo: save expiredTime before properties + return edge; + } + + if (ArrayUtils.isEmpty(edgeCol.value)) { + // There is no edge-properties here. + return edge; + } + + // Parse edge-id + edge-properties + buffer = BytesBuffer.wrap(edgeCol.value); + + // Parse edge properties + this.parseProperties(graph, buffer, edge); + + /* 先跳过 ttl 解析过程 + * 通过 edge 判断不出有没有 ttl 了,需要通过 bytebuffer 长度判断了 */ +// // Parse edge expired time if needed + if (buffer.remaining() > 0 /*edge.hasTtl()*/) { + this.parseExpiredTime(buffer, edge); + } + return edge; + } + + /** + * @param graph 解析索引的时候 graph 不能为 null + * @param index 为 null 时,算子下沉使用,store 可以根据一条 col 数据还原 index + */ + public Index parseIndex(HugeGraphSupplier graph, BackendColumn indexCol, + Index index) { + HugeType indexType = parseIndexType(indexCol); + + BytesBuffer buffer = BytesBuffer.wrap(indexCol.name); + BinaryId indexId = buffer.readIndexId(indexType); + Id elemId = buffer.readId(); + + if (index == null) { + index = Index.parseIndexId(graph, indexType, indexId.asBytes()); + } + + long expiredTime = 0L; + + if (indexCol.value.length > 0) { + + // 获取分隔符地址 + int delimiterIndex = + Bytes.indexOf(indexCol.value, BytesBuffer.STRING_ENDING_BYTE); + + if (delimiterIndex >= 0) { + // 分隔符在数据中,则需要从数据中解析 + // 1. field value 真是内容 + byte[] fieldValueBytes = + Arrays.copyOfRange(indexCol.value, 0, delimiterIndex); + if (fieldValueBytes.length > 0) { + index.fieldValues(StringEncoding.decode(fieldValueBytes)); + } + + // 2. 过期时间 + byte[] expiredTimeBytes = + Arrays.copyOfRange(indexCol.value, delimiterIndex + 1, + indexCol.value.length); + + if (expiredTimeBytes.length > 0) { + byte[] rawBytes = + Base64.getDecoder().decode(expiredTimeBytes); + if (rawBytes.length >= Longs.BYTES) { + expiredTime = Longs.fromByteArray(rawBytes); + } + } + } else { + // 仅有 field value 数据 + index.fieldValues(StringEncoding.decode(indexCol.value)); + } + } + + index.elementIds(elemId, expiredTime); + return index; + } + + public BackendColumn parseIndex(BackendColumn indexCol) { + // 自解析索引 + throw new NotImplementedException( + "BinaryElementSerializer.parseIndex"); + } + + + public BackendColumn writeVertex(BaseVertex vertex) { + if (vertex.olap()) { + return this.writeOlapVertex(vertex); + } + + BytesBuffer bufferName = BytesBuffer.allocate(vertex.id().length()); + bufferName.writeId(vertex.id()); + + int propsCount = vertex.getProperties().size(); + BytesBuffer buffer = BytesBuffer.allocate(8 + 16 * propsCount); + + // Write vertex label + buffer.writeId(vertex.schemaLabel().id()); + + // Write all properties of the vertex + this.formatProperties(vertex.getProperties().values(), buffer); + + // Write vertex expired time if needed + if (vertex.hasTtl()) { + this.formatExpiredTime(vertex.expiredTime(), buffer); + } + + return BackendColumn.of(bufferName.bytes(), buffer.bytes()); + } + + public BackendColumn writeOlapVertex(BaseVertex vertex) { + BytesBuffer buffer = BytesBuffer.allocate(8 + 16); + + BaseProperty baseProperty = vertex.getProperties().values() + .iterator().next(); + PropertyKey propertyKey = baseProperty.propertyKey(); + buffer.writeVInt(SchemaElement.schemaId(propertyKey.id())); + buffer.writeProperty(propertyKey.cardinality(), propertyKey.dataType(), + baseProperty.value()); + + // olap 表合并,key 为 {property_key_id}{vertex_id} + BytesBuffer bufferName = + BytesBuffer.allocate(1 + propertyKey.id().length() + 1 + + vertex.id().length()); + bufferName.writeId(propertyKey.id()); + bufferName.writeId(vertex.id()).bytes(); + + return BackendColumn.of(bufferName.bytes(), buffer.bytes()); + } + + public BackendColumn writeEdge(BaseEdge edge) { + byte[] name = this.formatEdgeName(edge); + byte[] value = this.formatEdgeValue(edge); + return BackendColumn.of(name, value); + } + + /** + * 将一个索引数据转换为一个 BackendColumn + */ + public BackendColumn writeIndex(Index index) { + return BackendColumn.of(formatIndexName(index), + formatIndexValue(index)); + } + + private byte[] formatIndexName(Index index) { + BytesBuffer buffer; + Id elemId = index.elementId(); + Id indexId = index.id(); + HugeType type = index.type(); + int idLen = 1 + elemId.length() + 1 + indexId.length(); + buffer = BytesBuffer.allocate(idLen); + // Write index-id + buffer.writeIndexId(indexId, type); + // Write element-id + buffer.writeId(elemId); + + return buffer.bytes(); + } + + /** + * @param index value + * @return + * + * format + * | empty(field-value) | 0x00 | base64(expiredtime) | + */ + private byte[] formatIndexValue(Index index) { + if (index.hasTtl()) { + BytesBuffer valueBuffer = BytesBuffer.allocate(14); + + valueBuffer.write(BytesBuffer.STRING_ENDING_BYTE); + byte[] ttlBytes = + Base64.getEncoder().encode(Longs.toByteArray(index.expiredTime())); + valueBuffer.write(ttlBytes); + + return valueBuffer.bytes(); + } + + return null; + } + + public BackendColumn mergeCols(BackendColumn vertexCol, BackendColumn... olapVertexCols) { + if (olapVertexCols.length == 0) { + return vertexCol; + } + BytesBuffer mergedBuffer = BytesBuffer.allocate( + vertexCol.value.length + olapVertexCols.length * 16); + + BytesBuffer buffer = BytesBuffer.wrap(vertexCol.value); + Id vl = buffer.readId(); + int size = buffer.readVInt(); + + mergedBuffer.writeId(vl); + mergedBuffer.writeVInt(size + olapVertexCols.length); + // 优先写入 vertexCol 的属性,因为 vertexCol 可能包含 ttl + for (BackendColumn olapVertexCol : olapVertexCols) { + mergedBuffer.write(olapVertexCol.value); + } + mergedBuffer.write(buffer.remainingBytes()); + + return BackendColumn.of(vertexCol.name, mergedBuffer.bytes()); + } + + public BaseElement index2Element(HugeGraphSupplier graph, + BackendColumn indexCol) { + throw new NotImplementedException( + "BinaryElementSerializer.index2Element"); + } + + public byte[] formatEdgeName(BaseEdge edge) { + // owner-vertex + dir + edge-label + sort-values + other-vertex + return BytesBuffer.allocate(BytesBuffer.BUF_EDGE_ID) + .writeEdgeId(edge.id()).bytes(); + } + + + protected byte[] formatEdgeValue(BaseEdge edge) { + Map> properties = edge.getProperties(); + int propsCount = properties.size(); + BytesBuffer buffer = BytesBuffer.allocate(4 + 16 * propsCount); + + // Write edge properties + this.formatProperties(properties.values(), buffer); + + // Write edge expired time if needed + if (edge.hasTtl()) { + this.formatExpiredTime(edge.expiredTime(), buffer); + } + + return buffer.bytes(); + } + + public void formatProperties(Collection> props, + BytesBuffer buffer) { + // Write properties size + buffer.writeVInt(props.size()); + + // Write properties data + for (BaseProperty property : props) { + PropertyKey pkey = property.propertyKey(); + buffer.writeVInt(SchemaElement.schemaId(pkey.id())); + buffer.writeProperty(pkey.cardinality(), pkey.dataType(), + property.value()); + } + } + + + public void formatExpiredTime(long expiredTime, BytesBuffer buffer) { + buffer.writeVLong(expiredTime); + } + + protected void parseExpiredTime(BytesBuffer buffer, BaseElement element) { + element.expiredTime(buffer.readVLong()); + } + + private HugeType parseIndexType(BackendColumn col) { + /** + * 参考 formatIndexName 方法 + * range 类型索引 col.name 第一位写的是 type.code (1 byte) + * 其他类型索引将会写入头两位写入 type.name (2 byte) + */ + byte first = col.name[0]; + byte second = col.name[1]; + if (first < 0) { + return HugeType.fromCode(first); + } + assert second >= 0; + String type = new String(new byte[]{first, second}); + return HugeType.fromString(type); + } + + /** 计算点/边的ownerid + * @param element + * @return + */ + public static Id ownerId(BaseElement element) { + if (element instanceof BaseVertex) { + return element.id(); + } else if (element instanceof BaseEdge) { + return ((EdgeId) element.id()).ownerVertexId(); + } else { + throw new IllegalArgumentException("Only support get ownerid" + + " of BaseVertex or BaseEdge"); + } + } + + /** + * 计算索引的 ownerid + * @param index + * @return + */ + public static Id ownerId(Index index) { + Id elementId = index.elementId(); + + Id ownerId = null; + if (elementId instanceof EdgeId) { + // 边 ID + EdgeId edgeId = (EdgeId) elementId; + ownerId = edgeId.ownerVertexId(); + } else { + // olap 索引 + // 普通 vertex 索引 + // 普通二级索引 + // 点/边 LabelIndex + + ownerId = elementId; + } + + return ownerId; + } +} diff --git a/hugegraph-struct/src/main/java/org/apache/hugegraph/serializer/BytesBuffer.java b/hugegraph-struct/src/main/java/org/apache/hugegraph/serializer/BytesBuffer.java new file mode 100644 index 0000000000..d1d26819a7 --- /dev/null +++ b/hugegraph-struct/src/main/java/org/apache/hugegraph/serializer/BytesBuffer.java @@ -0,0 +1,1012 @@ +/* + * Copyright 2017 HugeGraph Authors + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.serializer; + +import java.io.OutputStream; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Date; +import java.util.LinkedHashSet; +import java.util.UUID; + +import org.apache.hugegraph.backend.BinaryId; +import org.apache.hugegraph.id.EdgeId; +import org.apache.hugegraph.id.Id; +import org.apache.hugegraph.id.Id.IdType; +import org.apache.hugegraph.id.IdGenerator; +import org.apache.hugegraph.schema.PropertyKey; +import org.apache.hugegraph.type.HugeType; +import org.apache.hugegraph.type.define.Cardinality; +import org.apache.hugegraph.type.define.DataType; +import org.apache.hugegraph.type.define.SerialEnum; +import org.apache.hugegraph.util.Blob; +import org.apache.hugegraph.util.Bytes; +import org.apache.hugegraph.util.E; +import org.apache.hugegraph.util.StringEncoding; + +/** + * class BytesBuffer is a util for read/write binary + */ +public class BytesBuffer extends OutputStream { + + public static final int BYTE_LEN = Byte.BYTES; + public static final int SHORT_LEN = Short.BYTES; + public static final int INT_LEN = Integer.BYTES; + public static final int LONG_LEN = Long.BYTES; + public static final int CHAR_LEN = Character.BYTES; + public static final int FLOAT_LEN = Float.BYTES; + public static final int DOUBLE_LEN = Double.BYTES; + public static final int BLOB_LEN = 4; + + public static final int UINT8_MAX = ((byte) -1) & 0xff; + public static final int UINT16_MAX = ((short) -1) & 0xffff; + public static final long UINT32_MAX = (-1) & 0xffffffffL; + public static final long WRITE_BYTES_MAX_LENGTH = 10 * Bytes.MB; + + // NOTE: +1 to let code 0 represent length 1 + public static final int ID_LEN_MAX = 0x7fff + 1; + public static final int BIG_ID_LEN_MAX = 0xfffff + 1; + + public static final byte STRING_ENDING_BYTE = (byte) 0x00; + public static final byte STRING_ENDING_BYTE_FF = (byte) 0xff; + public static final int STRING_LEN_MAX = UINT16_MAX; + public static final long BLOB_LEN_MAX = 1 * Bytes.GB; + + // The value must be in range [8, ID_LEN_MAX] + public static final int INDEX_HASH_ID_THRESHOLD = 32; + + public static final int DEFAULT_CAPACITY = 64; + public static final int MAX_BUFFER_CAPACITY = 128 * 1024 * 1024; // 128M + + public static final int BUF_EDGE_ID = 128; + public static final int BUF_PROPERTY = 64; + + private ByteBuffer buffer; + private final boolean resize; + + public BytesBuffer() { + this(DEFAULT_CAPACITY); + } + + public BytesBuffer(int capacity) { + E.checkArgument(capacity <= MAX_BUFFER_CAPACITY, + "Capacity exceeds max buffer capacity: %s", + MAX_BUFFER_CAPACITY); + this.buffer = ByteBuffer.allocate(capacity); + this.resize = true; + } + + public BytesBuffer(ByteBuffer buffer) { + E.checkNotNull(buffer, "buffer"); + this.buffer = buffer; + this.resize = false; + } + + public static BytesBuffer allocate(int capacity) { + return new BytesBuffer(capacity); + } + + public static BytesBuffer wrap(ByteBuffer buffer) { + return new BytesBuffer(buffer); + } + + public static BytesBuffer wrap(byte[] array) { + return new BytesBuffer(ByteBuffer.wrap(array)); + } + + public static BytesBuffer wrap(byte[] array, int offset, int length) { + return new BytesBuffer(ByteBuffer.wrap(array, offset, length)); + } + + public ByteBuffer asByteBuffer() { + return this.buffer; + } + + public BytesBuffer forReadWritten() { + this.buffer.flip(); + return this; + } + + public BytesBuffer forReadAll() { + this.buffer.position(this.buffer.limit()); + return this; + } + + public byte[] array() { + return this.buffer.array(); + } + + public byte[] bytes() { + byte[] bytes = this.buffer.array(); + int position = this.buffer.position(); + if (position == bytes.length) { + return bytes; + } else { + return Arrays.copyOf(bytes, position); + } + } + + public int position() { + return this.buffer.position(); + } + + public BytesBuffer copyFrom(BytesBuffer other) { + this.write(other.bytes()); + return this; + } + + public int remaining() { + return this.buffer.remaining(); + } + + private void require(int size) { + // Does need to resize? + if (this.buffer.limit() - this.buffer.position() >= size) { + return; + } + // Can't resize for wrapped buffer since will change the origin ref + E.checkState(this.resize, "Can't resize for wrapped buffer"); + + // Extra capacity as buffer + int newcapacity = size + this.buffer.limit() + DEFAULT_CAPACITY; + E.checkArgument(newcapacity <= MAX_BUFFER_CAPACITY, + "Capacity exceeds max buffer capacity: %s", + MAX_BUFFER_CAPACITY); + ByteBuffer newBuffer = ByteBuffer.allocate(newcapacity); + this.buffer.flip(); + newBuffer.put(this.buffer); + this.buffer = newBuffer; + } + + public BytesBuffer write(byte val) { + require(BYTE_LEN); + this.buffer.put(val); + return this; + } + + @Override + public void write(int val) { + assert val <= UINT8_MAX; + require(BYTE_LEN); + this.buffer.put((byte) val); + } + + @Override + public void write(byte[] val) { + require(BYTE_LEN * val.length); + this.buffer.put(val); + } + + @Override + public void write(byte[] val, int offset, int length) { + require(BYTE_LEN * length); + this.buffer.put(val, offset, length); + } + + public BytesBuffer writeBoolean(boolean val) { + this.write(val ? 1 : 0); + return this; + } + + public BytesBuffer writeChar(char val) { + require(CHAR_LEN); + this.buffer.putChar(val); + return this; + } + + public BytesBuffer writeShort(short val) { + require(SHORT_LEN); + this.buffer.putShort(val); + return this; + } + + public BytesBuffer writeInt(int val) { + require(INT_LEN); + this.buffer.putInt(val); + return this; + } + + public BytesBuffer writeLong(long val) { + require(LONG_LEN); + this.buffer.putLong(val); + return this; + } + + public BytesBuffer writeFloat(float val) { + require(FLOAT_LEN); + this.buffer.putFloat(val); + return this; + } + + public BytesBuffer writeDouble(double val) { + require(DOUBLE_LEN); + this.buffer.putDouble(val); + return this; + } + + public byte peek() { + return this.buffer.get(this.buffer.position()); + } + + public byte peekLast() { + return this.buffer.get(this.buffer.capacity() - 1); + } + + public byte read() { + return this.buffer.get(); + } + + public byte[] read(int length) { + byte[] bytes = new byte[length]; + this.buffer.get(bytes); + return bytes; + } + + public byte[] readToEnd() { + byte[] bytes = new byte[this.remaining()]; + this.buffer.get(bytes); + return bytes; + } + + public boolean readBoolean() { + return this.buffer.get() == 0 ? false : true; + } + + public char readChar() { + return this.buffer.getChar(); + } + + public short readShort() { + return this.buffer.getShort(); + } + + public int readInt() { + return this.buffer.getInt(); + } + + public long readLong() { + return this.buffer.getLong(); + } + + public float readFloat() { + return this.buffer.getFloat(); + } + + public double readDouble() { + return this.buffer.getDouble(); + } + + public BytesBuffer writeBytes(byte[] bytes) { + // 原限制如上,考虑这个限制可能是因为 多种存储后端的时候,基于性能的考虑。 + // 上述限制 在往 property 写 value 超过限制的时候 会出错。所以调整大小为 5M + E.checkArgument(bytes.length <= WRITE_BYTES_MAX_LENGTH, + "The max length of bytes is %s, but got %s", + WRITE_BYTES_MAX_LENGTH, bytes.length); + require(SHORT_LEN + bytes.length); + this.writeVInt(bytes.length); + this.write(bytes); + return this; + } + + public byte[] readBytes() { + int length = this.readVInt(); + assert length >= 0; + byte[] bytes = this.read(length); + return bytes; + } + + public BytesBuffer writeBigBytes(byte[] bytes) { + E.checkArgument(bytes.length <= BLOB_LEN_MAX, + "The max length of bytes is %s, but got %s", + BLOB_LEN_MAX, bytes.length); + require(BLOB_LEN + bytes.length); + this.writeVInt(bytes.length); + this.write(bytes); + return this; + } + + public byte[] readBigBytes() { + int length = this.readVInt(); + assert length >= 0; + byte[] bytes = this.read(length); + return bytes; + } + + public BytesBuffer writeStringRaw(String val) { + this.write(StringEncoding.encode(val)); + return this; + } + + public BytesBuffer writeString(String val) { + byte[] bytes = StringEncoding.encode(val); + this.writeBytes(bytes); + return this; + } + + public String readString() { + return StringEncoding.decode(this.readBytes()); + } + + public BytesBuffer writeStringWithEnding(String value) { + if (!value.isEmpty()) { + byte[] bytes = StringEncoding.encode(value); + /* + * assert '0x00'/'0xFF' not exist in string index id + * NOTE: + * 0x00 is NULL in UTF8(or ASCII) bytes + * 0xFF is not a valid byte in UTF8 bytes + */ + assert !Bytes.contains(bytes, STRING_ENDING_BYTE_FF) : + "Invalid UTF8 bytes: " + value; + if (Bytes.contains(bytes, STRING_ENDING_BYTE)) { + E.checkArgument(false, + "Can't contains byte '0x00' in string: '%s'", + value); + } + this.write(bytes); + } + /* + * Choose 0x00 as ending symbol (see #1057) + * The following is out of date: + * A reasonable ending symbol should be 0x00(to ensure order), but + * considering that some backends like PG do not support 0x00 string, + * so choose 0xFF currently. + */ + this.write(STRING_ENDING_BYTE); + return this; + } + + public String readStringWithEnding() { + return StringEncoding.decode(this.readBytesWithEnding()); + } + public String skipBytesWithEnding(){ + boolean foundEnding = false; + while (this.remaining() > 0) { + byte current = this.read(); + if (current == STRING_ENDING_BYTE) { + foundEnding = true; + break; + } + } + return ""; + } + + public BytesBuffer writeStringToRemaining(String value) { + byte[] bytes = StringEncoding.encode(value); + this.write(bytes); + return this; + } + + public String readStringFromRemaining() { + byte[] bytes = new byte[this.buffer.remaining()]; + this.buffer.get(bytes); + return StringEncoding.decode(bytes); + } + + public BytesBuffer writeUInt8(int val) { + assert val <= UINT8_MAX; + this.write(val); + return this; + } + + public int readUInt8() { + return this.read() & 0x000000ff; + } + + public BytesBuffer writeUInt16(int val) { + assert val <= UINT16_MAX; + this.writeShort((short) val); + return this; + } + + public int readUInt16() { + return this.readShort() & 0x0000ffff; + } + + public BytesBuffer writeUInt32(long val) { + assert val <= UINT32_MAX; + this.writeInt((int) val); + return this; + } + + public long readUInt32() { + return this.readInt() & 0xffffffffL; + } + + public BytesBuffer writeVInt(int value) { + // NOTE: negative numbers are not compressed + if (value > 0x0fffffff || value < 0) { + this.write(0x80 | ((value >>> 28) & 0x7f)); + } + if (value > 0x1fffff || value < 0) { + this.write(0x80 | ((value >>> 21) & 0x7f)); + } + if (value > 0x3fff || value < 0) { + this.write(0x80 | ((value >>> 14) & 0x7f)); + } + if (value > 0x7f || value < 0) { + this.write(0x80 | ((value >>> 7) & 0x7f)); + } + this.write(value & 0x7f); + + return this; + } + + public int readVInt() { + byte leading = this.read(); + int value = leading & 0x7f; + if (leading >= 0) { + assert (leading & 0x80) == 0; + return value; + } + + int i = 1; + for (; i < 5; i++) { + byte b = this.read(); + if (b >= 0) { + value = b | (value << 7); + break; + } else { + value = (b & 0x7f) | (value << 7); + } + } + + return value; + } + + public BytesBuffer writeVLong(long value) { + if (value < 0) { + this.write((byte) 0x81); + } + if (value > 0xffffffffffffffL || value < 0L) { + this.write(0x80 | ((int) (value >>> 56) & 0x7f)); + } + if (value > 0x1ffffffffffffL || value < 0L) { + this.write(0x80 | ((int) (value >>> 49) & 0x7f)); + } + if (value > 0x3ffffffffffL || value < 0L) { + this.write(0x80 | ((int) (value >>> 42) & 0x7f)); + } + if (value > 0x7ffffffffL || value < 0L) { + this.write(0x80 | ((int) (value >>> 35) & 0x7f)); + } + if (value > 0xfffffffL || value < 0L) { + this.write(0x80 | ((int) (value >>> 28) & 0x7f)); + } + if (value > 0x1fffffL || value < 0L) { + this.write(0x80 | ((int) (value >>> 21) & 0x7f)); + } + if (value > 0x3fffL || value < 0L) { + this.write(0x80 | ((int) (value >>> 14) & 0x7f)); + } + if (value > 0x7fL || value < 0L) { + this.write(0x80 | ((int) (value >>> 7) & 0x7f)); + } + this.write((int) value & 0x7f); + + return this; + } + + public long readVLong() { + byte leading = this.read(); + E.checkArgument(leading != 0x80, + "Unexpected varlong with leading byte '0x%s'", + Bytes.toHex(leading)); + long value = leading & 0x7fL; + if (leading >= 0) { + assert (leading & 0x80) == 0; + return value; + } + + int i = 1; + for (; i < 10; i++) { + byte b = this.read(); + if (b >= 0) { + value = b | (value << 7); + break; + } else { + value = (b & 0x7f) | (value << 7); + } + } + + E.checkArgument(i < 10, + "Unexpected varlong %s with too many bytes(%s)", + value, i + 1); + E.checkArgument(i < 9 || (leading & 0x7e) == 0, + "Unexpected varlong %s with leading byte '0x%s'", + value, Bytes.toHex(leading)); + return value; + } + + public T newValue(Cardinality cardinality) { + switch (cardinality) { + case SET: + return (T) new LinkedHashSet<>(); + case LIST: + return (T) new ArrayList<>(); + default: + // pass + break; + } + return null; + } + + private byte getCardinalityAndType(int cardinality, int type){ + return (byte) ((cardinality << 6) | type); + } + + public static byte getCardinality(int value){ + return (byte) ((value & 0xc0) >> 6); + } + + public static byte getType(int value){ + return (byte) (value & 0x3f); + } + + public BytesBuffer writeProperty(PropertyKey pkey, Object value) { + return writeProperty(pkey.cardinality(), pkey.dataType(), value); + } + + public BytesBuffer writeProperty(Cardinality cardinality, DataType dataType, Object value) { + this.write(getCardinalityAndType(cardinality.code(),dataType.code())); + if (cardinality == Cardinality.SINGLE) { + this.writeProperty(dataType, value); + return this; + } + assert cardinality == Cardinality.LIST || + cardinality == Cardinality.SET; + Collection values = (Collection) value; + this.writeVInt(values.size()); + for (Object o : values) { + this.writeProperty(dataType, o); + } + return this; + } + + public Object readProperty(PropertyKey propertyKey) { + byte cardinalityAndType = this.read(); + Cardinality cardinality; + DataType type; + cardinality = SerialEnum.fromCode(Cardinality.class, + getCardinality(cardinalityAndType)); + + type = SerialEnum.fromCode(DataType.class, getType(cardinalityAndType)); + propertyKey.cardinality(cardinality); + propertyKey.dataType(type); + if (cardinality == Cardinality.SINGLE) { + Object value = this.readProperty(type); + return value; + } + Collection values = this.newValue(cardinality); + assert cardinality == Cardinality.LIST || + cardinality == Cardinality.SET; + int size = this.readVInt(); + for (int i = 0; i < size; i++) { + values.add(this.readProperty(type)); + } + return values; + } + + public void writeProperty(DataType dataType, Object value) { + switch (dataType) { + case BOOLEAN: + this.writeVInt(((Boolean) value) ? 1 : 0); + break; + case BYTE: + this.writeVInt((Byte) value); + break; + case INT: + this.writeVInt((Integer) value); + break; + case FLOAT: + this.writeFloat((Float) value); + break; + case LONG: + this.writeVLong((Long) value); + break; + case DATE: + this.writeVLong(((Date) value).getTime()); + break; + case DOUBLE: + this.writeDouble((Double) value); + break; + case TEXT: + this.writeString((String) value); + break; + case BLOB: + byte[] bytes = value instanceof byte[] ? + (byte[]) value : ((Blob) value).bytes(); + this.writeBigBytes(bytes); + break; + case UUID: + UUID uuid = (UUID) value; + // Generally writeVLong(uuid) can't save space + this.writeLong(uuid.getMostSignificantBits()); + this.writeLong(uuid.getLeastSignificantBits()); + break; + default: + throw new IllegalArgumentException("Unsupported data type " + dataType); + } + } + + public Object readProperty(DataType dataType) { + switch (dataType) { + case BOOLEAN: + return this.readVInt() == 1; + case BYTE: + return (byte) this.readVInt(); + case INT: + return this.readVInt(); + case FLOAT: + return this.readFloat(); + case LONG: + return this.readVLong(); + case DATE: + return new Date(this.readVLong()); + case DOUBLE: + return this.readDouble(); + case TEXT: + return this.readString(); + case BLOB: + return Blob.wrap(this.readBigBytes()); + case UUID: + return new UUID(this.readLong(), this.readLong()); + default: + throw new IllegalArgumentException("Unsupported data type " + dataType); + } + } + + public BytesBuffer writeId(Id id) { + return this.writeId(id, false); + } + + public BytesBuffer writeId(Id id, boolean big) { + switch (id.type()) { + case LONG: + // Number Id + long value = id.asLong(); + this.writeNumber(value); + break; + case UUID: + // UUID Id + byte[] bytes = id.asBytes(); + assert bytes.length == Id.UUID_LENGTH; + this.writeUInt8(0x7f); // 0b01111111 means UUID + this.write(bytes); + break; + case EDGE: + // Edge Id + this.writeUInt8(0x7e); // 0b01111110 means EdgeId + this.writeEdgeId(id); + break; + default: + // String Id + bytes = id.asBytes(); + int len = bytes.length; + E.checkArgument(len > 0, "Can't write empty id"); + E.checkArgument(len <= 16384, + "Big id max length is %s, but got %s {%s}", + 16384, len, id); + len -= 1; + if (len <= 63) { + this.writeUInt8(len | 0x80); + } else { + int high = len >> 8; + int low = len & 0xff; + this.writeUInt8(high | 0xc0); + this.writeUInt8(low); + } + + this.write(bytes); + break; + } + return this; + } + + public Id readId() { + return this.readId(false); + } + + public Id readId(boolean big) { + byte b = this.read(); + boolean number = (b & 0x80) == 0; + if (number) { + if (b == 0x7f) { + // UUID Id + return IdGenerator.of(this.read(Id.UUID_LENGTH), IdType.UUID); + } else if (b == 0x7e) { + // Edge Id + return this.readEdgeId(); + } else { + // Number Id + return IdGenerator.of(this.readNumber(b)); + } + } else { + // String Id + int len = b & 0x3f; + if ((b & 0x40) != 0) { + int high = len << 8; + int low = this.readUInt8(); + len = high + low; + } + len += 1; + byte[] id = this.read(len); + return IdGenerator.of(id, IdType.STRING); + } + } + + public BytesBuffer writeEdgeId(Id id) { + 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; + } + + public Id readEdgeId() { + return new EdgeId(this.readId(), EdgeId.directionFromCode(this.read()), + this.readId(), this.readId(), + this.readStringWithEnding(), this.readId()); + } + + public Id readEdgeIdSkipSortValues() { + return new EdgeId(this.readId(), EdgeId.directionFromCode(this.read()), + this.readId(), this.readId(), + this.skipBytesWithEnding(), + this.readId()); + } + + + public BytesBuffer writeIndexId(Id id, HugeType type) { + return this.writeIndexId(id, type, true); + } + + public BytesBuffer writeIndexId(Id id, HugeType type, boolean withEnding) { + byte[] bytes = id.asBytes(); + int len = bytes.length; + E.checkArgument(len > 0, "Can't write empty id"); + + this.write(bytes); + if (type.isStringIndex()) { + if (Bytes.contains(bytes, STRING_ENDING_BYTE)) { + // Not allow STRING_ENDING_BYTE exist in string index id + E.checkArgument(false, + "The %s type index id can't contains " + + "byte '0x%s', but got: 0x%s", type, + Bytes.toHex(STRING_ENDING_BYTE), + Bytes.toHex(bytes)); + } + if (withEnding) { + this.writeStringWithEnding(""); + } + } + return this; + } + + public BinaryId readIndexId(HugeType type) { + byte[] id; + if (type.isRange4Index()) { + // HugeCodeType 1 bytes + IndexLabel 4 bytes + fieldValue 4 bytes + id = this.read(9); + } else if (type.isRange8Index()) { + // HugeCodeType 1 bytes + IndexLabel 4 bytes + fieldValue 8 bytes + id = this.read(13); + } else { + assert type.isStringIndex(); + id = this.readBytesWithEnding(); + } + return new BinaryId(id, IdGenerator.of(id, IdType.STRING)); + } + + public BinaryId asId() { + return new BinaryId(this.bytes(), null); + } + + public BinaryId parseId(HugeType type) { + if (type.isIndex()) { + return this.readIndexId(type); + } + // Parse id from bytes + int start = this.buffer.position(); + /* + * Since edge id in edges table doesn't prefix with leading 0x7e, + * so readId() will return the source vertex id instead of edge id, + * can't call: type.isEdge() ? this.readEdgeId() : this.readId(); + */ + Id id = this.readId(); + int end = this.buffer.position(); + int len = end - start; + byte[] bytes = new byte[len]; + System.arraycopy(this.array(), start, bytes, 0, len); + return new BinaryId(bytes, id); + } + + /** + * 解析 olap id + * @param type + * @param isOlap + * @return + */ + public BinaryId parseOlapId(HugeType type, boolean isOlap) { + if (type.isIndex()) { + return this.readIndexId(type); + } + // Parse id from bytes + int start = this.buffer.position(); + /** + * OLAP + * {PropertyKey}{VertexId} + */ + if (isOlap) { + // 先 read olap property id + Id pkId = this.readId(); + } + Id id = this.readId(); + int end = this.buffer.position(); + int len = end - start; + byte[] bytes = new byte[len]; + System.arraycopy(this.array(), start, bytes, 0, len); + return new BinaryId(bytes, id); + } + + private void writeNumber(long val) { + /* + * 8 kinds of number, 2 ~ 9 bytes number: + * 0b 0kkksxxx X... + * 0(1 bit) + kind(3 bits) + signed(1 bit) + number(n bits) + * + * 2 byte : 0b 0000 1xxx X(8 bits) [0, 2047] + * 0b 0000 0xxx X(8 bits) [-2048, -1] + * 3 bytes: 0b 0001 1xxx X X [0, 524287] + * 0b 0001 0xxx X X [-524288, -1] + * 4 bytes: 0b 0010 1xxx X X X [0, 134217727] + * 0b 0010 0xxx X X X [-134217728, -1] + * 5 bytes: 0b 0011 1xxx X X X X [0, 2^35 - 1] + * 0b 0011 0xxx X X X X [-2^35, -1] + * 6 bytes: 0b 0100 1xxx X X X X X [0, 2^43 - 1] + * 0b 0100 0xxx X X X X X [-2^43, -1] + * 7 bytes: 0b 0101 1xxx X X X X X X [0, 2^51 - 1] + * 0b 0101 0xxx X X X X X X [-2^51, -1] + * 8 bytes: 0b 0110 1xxx X X X X X X X [0, 2^59 - 1] + * 0b 0110 0xxx X X X X X X X [-2^59, -1] + * 9 bytes: 0b 0111 1000 X X X X X X X X [0, 2^64 - 1] + * 0b 0111 0000 X X X X X X X X [-2^64, -1] + * + * NOTE: 0b 0111 1111 is used by 128 bits UUID + * 0b 0111 1110 is used by EdgeId + */ + int positive = val >= 0 ? 0x08 : 0x00; + if (~0x7ffL <= val && val <= 0x7ffL) { + int high3bits = (int) (val >> 8) & 0x07; + this.writeUInt8(0x00 | positive | high3bits); + this.writeUInt8((byte) val); + } else if (~0x7ffffL <= val && val <= 0x7ffffL) { + int high3bits = (int) (val >> 16) & 0x07; + this.writeUInt8(0x10 | positive | high3bits); + this.writeShort((short) val); + } else if (~0x7ffffffL <= val && val <= 0x7ffffffL) { + int high3bits = (int) (val >> 24 & 0x07); + this.writeUInt8(0x20 | positive | high3bits); + this.write((byte) (val >> 16)); + this.writeShort((short) val); + } else if (~0x7ffffffffL <= val && val <= 0x7ffffffffL) { + int high3bits = (int) (val >> 32) & 0x07; + this.writeUInt8(0x30 | positive | high3bits); + this.writeInt((int) val); + } else if (~0x7ffffffffffL <= val && val <= 0x7ffffffffffL) { + int high3bits = (int) (val >> 40) & 0x07; + this.writeUInt8(0x40 | positive | high3bits); + this.write((byte) (val >> 32)); + this.writeInt((int) val); + } else if (~0x7ffffffffffffL <= val && val <= 0x7ffffffffffffL) { + int high3bits = (int) (val >> 48) & 0x07; + this.writeUInt8(0x50 | positive | high3bits); + this.writeShort((short) (val >> 32)); + this.writeInt((int) val); + } else if (~0x7ffffffffffffffL <= val && val <= 0x7ffffffffffffffL) { + int high3bits = (int) (val >> 56) & 0x07; + this.writeUInt8(0x60 | positive | high3bits); + this.write((byte) (val >> 48)); + this.writeShort((short) (val >> 32)); + this.writeInt((int) val); + } else { + // high3bits is always 0b000 for 9 bytes number + this.writeUInt8(0x70 | positive); + this.writeLong(val); + } + } + + private long readNumber(byte b) { + // Parse the kind from byte 0kkksxxx + int kind = b >>> 4; + boolean positive = (b & 0x08) > 0; + long high3bits = b & 0x07; + long value = high3bits << ((kind + 1) * 8); + switch (kind) { + case 0: + value |= this.readUInt8(); + break; + case 1: + value |= this.readUInt16(); + break; + case 2: + value |= this.readUInt8() << 16 | this.readUInt16(); + break; + case 3: + value |= this.readUInt32(); + break; + case 4: + value |= (long) this.readUInt8() << 32 | this.readUInt32(); + break; + case 5: + value |= (long) this.readUInt16() << 32 | this.readUInt32(); + break; + case 6: + value |= (long) this.readUInt8() << 48 | + (long) this.readUInt16() << 32 | + this.readUInt32(); + break; + case 7: + assert high3bits == 0L; + value |= this.readLong(); + break; + default: + throw new AssertionError("Invalid length of number: " + kind); + } + if (!positive && kind < 7) { + // Restore the bits of the original negative number + long mask = Long.MIN_VALUE >> (52 - kind * 8); + value |= mask; + } + return value; + } + + private byte[] readBytesWithEnding() { + int start = this.buffer.position(); + boolean foundEnding = false; + while (this.remaining() > 0) { + byte current = this.read(); + if (current == STRING_ENDING_BYTE) { + foundEnding = true; + break; + } + } + E.checkArgument(foundEnding, "Not found ending '0x%s'", + Bytes.toHex(STRING_ENDING_BYTE)); + int end = this.buffer.position() - 1; + int len = end - start; + byte[] bytes = new byte[len]; + System.arraycopy(this.array(), start, bytes, 0, len); + return bytes; + } + + public byte[] remainingBytes(){ + int length = this.remaining(); + int start = this.position(); + byte[] bytes = new byte[length]; + System.arraycopy(this.array(), start, bytes, 0, length); + return bytes; + } +} diff --git a/hugegraph-struct/src/main/java/org/apache/hugegraph/serializer/DirectBinarySerializer.java b/hugegraph-struct/src/main/java/org/apache/hugegraph/serializer/DirectBinarySerializer.java new file mode 100644 index 0000000000..1d857b5d55 --- /dev/null +++ b/hugegraph-struct/src/main/java/org/apache/hugegraph/serializer/DirectBinarySerializer.java @@ -0,0 +1,128 @@ +/* + * Copyright 2017 HugeGraph Authors + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.serializer; + +import java.util.Arrays; +import java.util.Base64; + +import org.apache.hugegraph.util.Bytes; +import org.apache.hugegraph.util.Log; +import org.slf4j.Logger; + +import org.apache.hugegraph.id.Id; +import org.apache.hugegraph.id.IdGenerator; +import org.apache.hugegraph.schema.PropertyKey; +import com.google.common.primitives.Longs; + +public class DirectBinarySerializer { + + protected static final Logger LOG = Log.logger(DirectBinarySerializer.class); + + public static class DirectHugeElement { + private Id id; + private long expiredTime; + + public DirectHugeElement(Id id, long expiredTime) { + this.id = id; + this.expiredTime = expiredTime; + } + + public Id id() { + return id; + } + + public long expiredTime() { + return expiredTime; + } + } + + public DirectHugeElement parseIndex(byte[] key, byte[] value) { + long expiredTime = 0L; + + if (value.length > 0) { + // 获取分隔符地址 + int delimiterIndex = + Bytes.indexOf(value, BytesBuffer.STRING_ENDING_BYTE); + + if (delimiterIndex >= 0) { + // 分隔符在数据中,则需要从数据中解析 + // 解析过期时间 + byte[] expiredTimeBytes = + Arrays.copyOfRange(value, delimiterIndex + 1, + value.length); + + if (expiredTimeBytes.length > 0) { + byte[] rawBytes = + Base64.getDecoder().decode(expiredTimeBytes); + if (rawBytes.length >= Longs.BYTES) { + expiredTime = Longs.fromByteArray(rawBytes); + } + } + } + } + + return new DirectHugeElement(IdGenerator.of(key), expiredTime); + } + + public DirectHugeElement parseVertex(byte[] key, byte[] value) { + long expiredTime = 0L; + + BytesBuffer buffer = BytesBuffer.wrap(value); + // read schema label id + buffer.readId(); + // Skip edge properties + this.skipProperties(buffer); + // Parse edge expired time if needed + if (buffer.remaining() > 0) { + expiredTime = buffer.readVLong(); + } + + return new DirectHugeElement(IdGenerator.of(key), expiredTime); + } + + public DirectHugeElement parseEdge(byte[] key, byte[] value) { + long expiredTime = 0L; + + BytesBuffer buffer = BytesBuffer.wrap(value); + // Skip edge properties + this.skipProperties(buffer); + // Parse edge expired time if needed + if (buffer.remaining() > 0) { + expiredTime = buffer.readVLong(); + } + + return new DirectHugeElement(IdGenerator.of(key), expiredTime); + } + + private void skipProperties(BytesBuffer buffer) { + int size = buffer.readVInt(); + assert size >= 0; + for (int i = 0; i < size; i++) { + Id pkeyId = IdGenerator.of(buffer.readVInt()); + this.skipProperty(pkeyId, buffer); + } + } + + protected void skipProperty(Id pkeyId, BytesBuffer buffer) { + // Parse value + PropertyKey pkey = new PropertyKey(null, pkeyId, ""); + buffer.readProperty(pkey); + } +} diff --git a/hugegraph-struct/src/main/java/org/apache/hugegraph/structure/BaseEdge.java b/hugegraph-struct/src/main/java/org/apache/hugegraph/structure/BaseEdge.java new file mode 100644 index 0000000000..21ccfc844b --- /dev/null +++ b/hugegraph-struct/src/main/java/org/apache/hugegraph/structure/BaseEdge.java @@ -0,0 +1,288 @@ +/* + * Copyright 2017 HugeGraph Authors + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.structure; + +import org.apache.hugegraph.HugeGraphSupplier; +import org.apache.hugegraph.exception.HugeException; +import org.apache.hugegraph.id.EdgeId; +import org.apache.hugegraph.id.Id; +import org.apache.hugegraph.id.SplicingIdGenerator; +import org.apache.hugegraph.schema.EdgeLabel; +import org.apache.hugegraph.schema.SchemaLabel; +import org.apache.hugegraph.schema.VertexLabel; +import org.apache.hugegraph.serializer.BytesBuffer; +import org.apache.hugegraph.type.HugeType; +import org.apache.hugegraph.type.define.Directions; +import org.apache.hugegraph.type.define.HugeKeys; +import com.google.common.collect.ImmutableList; + +import org.apache.hugegraph.util.E; + +import java.util.ArrayList; +import java.util.List; + +/* 只作为基础数据容器,id 生成逻辑靠上层封装*/ +public class BaseEdge extends BaseElement implements Cloneable { + + private BaseVertex sourceVertex; + private BaseVertex targetVertex; + boolean isOutEdge; + + private String name; + + public BaseEdge(Id id, EdgeLabel label) { + this.id(id); + this.schemaLabel(label); + } + + public BaseEdge(SchemaLabel label, boolean isOutEdge) { + this.schemaLabel(label); + this.isOutEdge = isOutEdge; + } + + + public boolean isOutEdge() { + return isOutEdge; + } + + public void isOutEdge(boolean isOutEdge) { + this.isOutEdge = isOutEdge; + } + + public EdgeId idWithDirection() { + return ((EdgeId) this.id()).directed(true); + } + + @Override + public String name() { + if (this.name == null) { + this.name = SplicingIdGenerator.concatValues(sortValues()); + } + return this.name; + } + + public void name(String name) { + this.name = name; + } + + @Override + public HugeType type() { + // NOTE: we optimize the edge type that let it include direction + return this.isOutEdge() ? HugeType.EDGE_OUT : HugeType.EDGE_IN; + } + + public List sortValues() { + List sortKeys = this.schemaLabel().sortKeys(); + if (sortKeys.isEmpty()) { + return ImmutableList.of(); + } + List propValues = new ArrayList<>(sortKeys.size()); + for (Id sk : sortKeys) { + BaseProperty property = this.getProperty(sk); + E.checkState(property != null, + "The value of sort key '%s' can't be null", sk); + propValues.add(property.propertyKey().serialValue(property.value(), true)); + } + return propValues; + } + + public Directions direction() { + return this.isOutEdge ? Directions.OUT : Directions.IN; + } + + public Id sourceVertexId() { + return this.sourceVertex.id(); + } + + public Id targetVertexId() { + return this.targetVertex.id(); + } + + public void sourceVertex(BaseVertex sourceVertex) { + this.sourceVertex = sourceVertex; + } + + public BaseVertex sourceVertex() { + return this.sourceVertex; + } + + public void targetVertex(BaseVertex targetVertex) { + this.targetVertex = targetVertex; + } + + public BaseVertex targetVertex() { + return this.targetVertex; + } + + public Id ownerVertexId() { + return this.isOutEdge() ? this.sourceVertexId() : this.targetVertexId(); + } + + public Id otherVertexId() { + return this.isOutEdge() ? this.targetVertexId() : this.sourceVertexId() ; + } + + public void vertices(boolean outEdge, BaseVertex owner, BaseVertex other) { + this.isOutEdge = outEdge ; + if (outEdge) { + this.sourceVertex(owner); + this.targetVertex(other); + } else { + this.sourceVertex(other); + this.targetVertex(owner); + } + } + + + + public EdgeLabel schemaLabel() { + return (EdgeLabel) super.schemaLabel(); + } + + public BaseVertex ownerVertex() { + return this.isOutEdge() ? this.sourceVertex() : this.targetVertex(); + } + + public BaseVertex otherVertex() { + return this.isOutEdge() ? this.targetVertex() : this.sourceVertex(); + } + + public void assignId() { + // Generate an id and assign + if (this.schemaLabel().hasFather()) { + this.id(new EdgeId(this.ownerVertex().id(), this.direction(), + this.schemaLabel().fatherId(), + this.schemaLabel().id(), + this.name(), + this.otherVertex().id())); + } else { + this.id(new EdgeId(this.ownerVertex().id(), this.direction(), + this.schemaLabel().id(), + this.schemaLabel().id(), + this.name(), this.otherVertex().id())); + } + + + if (this.fresh()) { + int len = this.id().length(); + E.checkArgument(len <= BytesBuffer.BIG_ID_LEN_MAX, + "The max length of edge id is %s, but got %s {%s}", + BytesBuffer.BIG_ID_LEN_MAX, len, this.id()); + } + } + @Override + public Object sysprop(HugeKeys key) { + switch (key) { + case ID: + return this.id(); + case OWNER_VERTEX: + return this.ownerVertexId(); + case LABEL: + if (this.schemaLabel().fatherId() != null) { + 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.otherVertexId(); + case SORT_VALUES: + return this.name(); + case PROPERTIES: + return this.getPropertiesMap(); + default: + E.checkArgument(false, + "Invalid system property '%s' of Edge", key); + return null; + } + + } + + @Override + public BaseEdge clone() { + try { + return (BaseEdge) super.clone(); + } catch (CloneNotSupportedException e) { + throw new HugeException("Failed to clone HugeEdge", e); + } + } + + public BaseEdge switchOwner() { + BaseEdge edge = this.clone(); + edge.isOutEdge(!edge.isOutEdge()); + if (edge.id() != null) { + edge.id(((EdgeId) edge.id()).switchDirection()); + } + return edge; + } + + public static BaseEdge constructEdge(HugeGraphSupplier graph, + BaseVertex ownerVertex, + boolean isOutEdge, + EdgeLabel edgeLabel, + String sortValues, + Id otherVertexId) { + Id ownerLabelId = edgeLabel.sourceLabel(); + Id otherLabelId = edgeLabel.targetLabel(); + VertexLabel srcLabel; + VertexLabel tgtLabel; + if (graph == null) { + srcLabel = new VertexLabel(null, ownerLabelId, "UNDEF"); + tgtLabel = new VertexLabel(null, otherLabelId, "UNDEF"); + } else { + if (edgeLabel.general()) { + srcLabel = VertexLabel.GENERAL; + tgtLabel = VertexLabel.GENERAL; + } else { + srcLabel = graph.vertexLabelOrNone(ownerLabelId); + tgtLabel = graph.vertexLabelOrNone(otherLabelId); + } + } + + VertexLabel otherVertexLabel; + if (isOutEdge) { + ownerVertex.correctVertexLabel(srcLabel); + otherVertexLabel = tgtLabel; + } else { + ownerVertex.correctVertexLabel(tgtLabel); + otherVertexLabel = srcLabel; + } + BaseVertex otherVertex = new BaseVertex(otherVertexId, otherVertexLabel); + + ownerVertex.propLoaded(false); + otherVertex.propLoaded(false); + + BaseEdge edge = new BaseEdge(edgeLabel, isOutEdge); + edge.name(sortValues); + edge.vertices(isOutEdge, ownerVertex, otherVertex); + edge.assignId(); + + ownerVertex.addEdge(edge); + otherVertex.addEdge(edge.switchOwner()); + + return edge; + } + +} diff --git a/hugegraph-struct/src/main/java/org/apache/hugegraph/structure/BaseElement.java b/hugegraph-struct/src/main/java/org/apache/hugegraph/structure/BaseElement.java new file mode 100644 index 0000000000..57fffe6029 --- /dev/null +++ b/hugegraph-struct/src/main/java/org/apache/hugegraph/structure/BaseElement.java @@ -0,0 +1,355 @@ +/* + * Copyright 2017 HugeGraph Authors + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.structure; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Date; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.function.Supplier; + +import org.apache.hugegraph.util.CollectionUtil; +import org.apache.hugegraph.util.E; +import org.apache.hugegraph.util.Log; +import org.eclipse.collections.api.map.primitive.MutableIntObjectMap; +import org.eclipse.collections.api.tuple.primitive.IntObjectPair; +import org.eclipse.collections.impl.map.mutable.primitive.IntObjectHashMap; +import org.slf4j.Logger; + +import org.apache.hugegraph.id.Id; +import org.apache.hugegraph.id.IdGenerator; +import org.apache.hugegraph.schema.PropertyKey; +import org.apache.hugegraph.schema.SchemaLabel; +import org.apache.hugegraph.serializer.BytesBuffer; +import org.apache.hugegraph.type.GraphType; +import org.apache.hugegraph.type.Idfiable; +import org.apache.hugegraph.type.define.Cardinality; +import org.apache.hugegraph.type.define.HugeKeys; +import org.apache.hugegraph.util.collection.CollectionFactory; + + +public abstract class BaseElement implements GraphType, Idfiable, Serializable { + + private static final Logger LOG = Log.logger(BaseElement.class); + + public static final MutableIntObjectMap> EMPTY_MAP = + new IntObjectHashMap<>(); + + private static final int MAX_PROPERTIES = BytesBuffer.UINT16_MAX; + + MutableIntObjectMap> properties; + + Id id; + private SchemaLabel schemaLabel; + long expiredTime; // TODO: move into properties to keep small object + + private boolean removed; + private boolean fresh; + private boolean propLoaded; + private boolean defaultValueUpdated; + + public BaseElement() { + this.properties = EMPTY_MAP; + this.removed = false; + this.fresh = false; + this.propLoaded = true; + this.defaultValueUpdated = false; + } + + public void setProperties(MutableIntObjectMap> properties) { + this.properties = properties; + } + + public Id id(){ + return id; + } + + public void id(Id id) { + this.id = id; + } + + public boolean removed() { + return removed; + } + + public void removed(boolean removed) { + this.removed = removed; + } + + public boolean fresh() { + return fresh; + } + + public void fresh(boolean fresh) { + this.fresh = fresh; + } + + public boolean propLoaded() { + return propLoaded; + } + + public void propLoaded(boolean propLoaded) { + this.propLoaded = propLoaded; + } + + public boolean defaultValueUpdated() { + return defaultValueUpdated; + } + + public void defaultValueUpdated(boolean defaultValueUpdated) { + this.defaultValueUpdated = defaultValueUpdated; + } + public SchemaLabel schemaLabel() { + return schemaLabel; + } + + public void schemaLabel(SchemaLabel label) { + this.schemaLabel = label; + } + public long expiredTime() { + return expiredTime; + } + + public void expiredTime(long expiredTime) { + this.expiredTime = expiredTime; + } + + public boolean hasTtl() { + return this.schemaLabel.ttl() > 0L; + } + public boolean expired(long now) { + boolean expired; + SchemaLabel label = this.schemaLabel(); + if (label.ttl() == 0L) { + // No ttl, not expired + return false; + } + if (this.expiredTime() > 0L) { + // Has ttl and set expiredTime properly + expired = this.expiredTime() < now; + LOG.debug("The element {} {} with expired time {} and now {}", + this, expired ? "expired" : "not expired", + this.expiredTime(), now); + return expired; + } + // Has ttl, but failed to set expiredTime when insert + LOG.error("The element {} should have positive expired time, " + + "but got {}! ttl is {} ttl start time is {}", + this, this.expiredTime(), label.ttl(), label.ttlStartTimeName()); + if (SchemaLabel.NONE_ID.equals(label.ttlStartTime())) { + // No ttlStartTime, can't decide whether timeout, treat not expired + return false; + } + Date date = this.getPropertyValue(label.ttlStartTime()); + if (date == null) { + // No ttlStartTime, can't decide whether timeout, treat not expired + return false; + } + // Has ttlStartTime, re-calc expiredTime to decide whether timeout, + long expiredTime = date.getTime() + label.ttl(); + expired = expiredTime < now; + LOG.debug("The element {} {} with expired time {} and now {}", + this, expired ? "expired" : "not expired", + expiredTime, now); + return expired; + } + + public long ttl(long now) { + if (this.expiredTime() == 0L || this.expiredTime() < now) { + return 0L; + } + return this.expiredTime() - now; + } + protected BaseProperty newProperty(PropertyKey pkey, V val) { + return new BaseProperty<>(pkey, val); + } + + public boolean hasProperty(Id key) { + return this.properties.containsKey(intFromId(key)); + } + + public boolean hasProperties() { + return this.properties.size() > 0; + } + + + public void setExpiredTimeIfNeeded(long now) { + SchemaLabel label = this.schemaLabel(); + if (label.ttl() == 0L) { + return; + } + + if (SchemaLabel.NONE_ID.equals(label.ttlStartTime())) { + this.expiredTime(now + label.ttl()); + return; + } + Date date = this.getPropertyValue(label.ttlStartTime()); + if (date == null) { + this.expiredTime(now + label.ttl()); + return; + } + long expired = date.getTime() + label.ttl(); + E.checkArgument(expired > now, + "The expired time '%s' of '%s' is prior to now: %s", + new Date(expired), this, now); + this.expiredTime(expired); + } + + public void resetProperties() { + this.properties = CollectionFactory.newIntObjectMap(); + this.propLoaded(true); + } + + public V getPropertyValue(Id key) { + BaseProperty prop = this.properties.get(intFromId(key)); + if (prop == null) { + return null; + } + return (V) prop.value(); + } + public MutableIntObjectMap> properties() { + return this.properties; + } + + public void properties(MutableIntObjectMap> properties) { + this.properties = properties; + } + public BaseProperty getProperty(Id key) { + return (BaseProperty) this.properties.get(intFromId(key)); + } + + private BaseProperty addProperty(PropertyKey pkey, V value, + Supplier> supplier) { + assert pkey.cardinality().multiple(); + BaseProperty> property; + if (this.hasProperty(pkey.id())) { + property = this.getProperty(pkey.id()); + } else { + property = this.newProperty(pkey, supplier.get()); + this.addProperty(property); + } + + Collection values; + if (pkey.cardinality() == Cardinality.SET) { + if (value instanceof Set) { + values = (Set) value; + } else { + values = CollectionUtil.toSet(value); + } + } else { + assert pkey.cardinality() == Cardinality.LIST; + if (value instanceof List) { + values = (List) value; + } else { + values = CollectionUtil.toList(value); + } + } + property.value().addAll(values); + + // Any better ways? + return (BaseProperty) property; + } + + public BaseProperty addProperty(PropertyKey pkey, V value) { + BaseProperty prop = null; + switch (pkey.cardinality()) { + case SINGLE: + prop = this.newProperty(pkey, value); + this.addProperty(prop); + break; + case SET: + prop = this.addProperty(pkey, value, HashSet::new); + break; + case LIST: + prop = this.addProperty(pkey, value, ArrayList::new); + break; + default: + assert false; + break; + } + return prop; + } + + public BaseProperty addProperty(BaseProperty prop) { + if (this.properties == EMPTY_MAP) { + this.properties = new IntObjectHashMap<>(); // change to CollectionFactory.newIntObjectMap(); + } + PropertyKey pkey = prop.propertyKey(); + + E.checkArgument(this.properties.containsKey(intFromId(pkey.id())) || + this.properties.size() < MAX_PROPERTIES, + "Exceeded the maximum number of properties"); + return this.properties.put(intFromId(pkey.id()), prop); + } + public Map> getProperties() { + Map> props = new HashMap<>(); + for (IntObjectPair> e : this.properties.keyValuesView()) { + props.put(IdGenerator.of(e.getOne()), e.getTwo()); + } + return props; + } + + public BaseProperty removeProperty(Id key) { + return this.properties.remove(intFromId(key)); + } + + /* a util may be should be moved to other place */ + public static int intFromId(Id id) { + E.checkArgument(id instanceof IdGenerator.LongId, + "Can't get number from %s(%s)", id, id.getClass()); + return ((IdGenerator.LongId) id).intValue(); + } + + public abstract Object sysprop(HugeKeys key); + + public Map getPropertiesMap() { + Map props = new HashMap<>(); + for (IntObjectPair> e : this.properties.keyValuesView()) { + props.put(IdGenerator.of(e.getOne()), e.getTwo().value()); + } + return props; + } + + public int sizeOfProperties() { + return this.properties.size(); + } + + public int sizeOfSubProperties() { + int size = 0; + for (BaseProperty p : this.properties.values()) { + size++; + if (p.propertyKey().cardinality() != Cardinality.SINGLE && + p.value() instanceof Collection) { + size += ((Collection) p.value()).size(); + } + } + return size; + } + + @Override + public BaseElement clone() throws CloneNotSupportedException{ + return (BaseElement) super.clone(); + } +} diff --git a/hugegraph-struct/src/main/java/org/apache/hugegraph/structure/BaseProperty.java b/hugegraph-struct/src/main/java/org/apache/hugegraph/structure/BaseProperty.java new file mode 100644 index 0000000000..6cc8279c9c --- /dev/null +++ b/hugegraph-struct/src/main/java/org/apache/hugegraph/structure/BaseProperty.java @@ -0,0 +1,68 @@ +/* + * Copyright 2017 HugeGraph Authors + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.structure; + +import org.apache.hugegraph.schema.PropertyKey; +import org.apache.hugegraph.type.define.Cardinality; +import org.apache.hugegraph.type.define.DataType; + +public class BaseProperty { + private PropertyKey propertyKey; + + protected V value; + + public BaseProperty(PropertyKey propertyKey, V value) { + this.propertyKey = propertyKey; + this.value = value; + } + + public DataType getDataType() { + return propertyKey.dataType(); + } + + public void setDataType(DataType dataType) { + this.propertyKey.dataType(dataType); + } + + public Cardinality getCardinality() { + return propertyKey.cardinality(); + } + + public void setCardinality(Cardinality cardinality) { + this.propertyKey.cardinality(cardinality); + } + + public V value() { + return value; + } + + public void value(V value) { + this.value = value; + } + + public PropertyKey propertyKey() { + return propertyKey; + } + + public Object serialValue(boolean encodeNumber) { + return this.propertyKey.serialValue(this.value, encodeNumber); + } + +} diff --git a/hugegraph-struct/src/main/java/org/apache/hugegraph/structure/BaseRawElement.java b/hugegraph-struct/src/main/java/org/apache/hugegraph/structure/BaseRawElement.java new file mode 100644 index 0000000000..c86887fd12 --- /dev/null +++ b/hugegraph-struct/src/main/java/org/apache/hugegraph/structure/BaseRawElement.java @@ -0,0 +1,57 @@ +/* + * Copyright 2017 HugeGraph Authors + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.structure; + +import org.apache.hugegraph.type.HugeType; +import org.apache.hugegraph.type.define.HugeKeys; + +public class BaseRawElement extends BaseElement implements Cloneable { + + private byte[] key; + private byte[] value; + + public BaseRawElement(byte[] key, byte[] value) { + this.key = key; + this.value = value; + } + + public byte[] key() { + return this.key; + } + + public byte[] value() { + return this.value; + } + + @Override + public Object sysprop(HugeKeys key) { + return null; + } + + @Override + public String name() { + return null; + } + + @Override + public HugeType type() { + return HugeType.KV_RAW; + } +} diff --git a/hugegraph-struct/src/main/java/org/apache/hugegraph/structure/BaseVertex.java b/hugegraph-struct/src/main/java/org/apache/hugegraph/structure/BaseVertex.java new file mode 100644 index 0000000000..21b2478352 --- /dev/null +++ b/hugegraph-struct/src/main/java/org/apache/hugegraph/structure/BaseVertex.java @@ -0,0 +1,168 @@ +/* + * Copyright 2017 HugeGraph Authors + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.structure; + + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import org.apache.hugegraph.perf.PerfUtil; +import org.apache.hugegraph.util.E; + +import org.apache.hugegraph.id.Id; +import org.apache.hugegraph.id.SplicingIdGenerator; +import org.apache.hugegraph.schema.SchemaLabel; +import org.apache.hugegraph.schema.VertexLabel; +import org.apache.hugegraph.type.HugeType; +import org.apache.hugegraph.type.define.CollectionType; +import org.apache.hugegraph.type.define.HugeKeys; +import org.apache.hugegraph.type.define.IdStrategy; +import org.apache.hugegraph.util.collection.CollectionFactory; +import com.google.common.collect.ImmutableList; + +public class BaseVertex extends BaseElement implements Cloneable { + private static final List EMPTY_LIST = ImmutableList.of(); + + + protected Collection edges; + + public BaseVertex(Id id) { + this.edges = EMPTY_LIST; + id(id); + } + + public BaseVertex(Id id, SchemaLabel label) { + // 注意: + // 如果 vertex 为 OLAP Vertex,id 为 olap 属性所属 vertex 的 id,不包含 olap 属性 id。 + this(id); + this.schemaLabel(label); + } + + @Override + public String name() { + E.checkState(this.schemaLabel().idStrategy() == IdStrategy.PRIMARY_KEY, + "Only primary key vertex has name, " + + "but got '%s' with id strategy '%s'", + this, this.schemaLabel().idStrategy()); + String name; + if (this.id() != null) { + String[] parts = SplicingIdGenerator.parse(this.id()); + E.checkState(parts.length == 2, + "Invalid primary key vertex id '%s'", this.id()); + name = parts[1]; + } else { + assert this.id() == null; + List propValues = this.primaryValues(); + E.checkState(!propValues.isEmpty(), + "Primary values must not be empty " + + "(has properties %s)", hasProperties()); + name = SplicingIdGenerator.concatValues(propValues); + E.checkArgument(!name.isEmpty(), + "The value of primary key can't be empty"); + } + return name; + } + + @PerfUtil.Watched(prefix = "vertex") + public List primaryValues() { + E.checkArgument(this.schemaLabel().idStrategy() == IdStrategy.PRIMARY_KEY, + "The id strategy '%s' don't have primary keys", + this.schemaLabel().idStrategy()); + List primaryKeys = this.schemaLabel().primaryKeys(); + E.checkArgument(!primaryKeys.isEmpty(), + "Primary key can't be empty for id strategy '%s'", + IdStrategy.PRIMARY_KEY); + + List propValues = new ArrayList<>(primaryKeys.size()); + for (Id pk : primaryKeys) { + BaseProperty property = this.getProperty(pk); + E.checkState(property != null, + "The value of primary key '%s' can't be null" + /*this.graph().propertyKey(pk).name() 补全日志*/); + propValues.add(property.serialValue(true)); + } + return propValues; + } + + public void addEdge(BaseEdge edge) { + if (this.edges == EMPTY_LIST) { + this.edges = CollectionFactory.newList(CollectionType.EC); + } + this.edges.add(edge); + } + + public void correctVertexLabel(VertexLabel correctLabel) { + E.checkArgumentNotNull(correctLabel, "Vertex label can't be null"); + if (this.schemaLabel() != null && !this.schemaLabel().undefined() && + !correctLabel.undefined() && !this.schemaLabel().generalVl() && !correctLabel.generalVl()) { + E.checkArgument(this.schemaLabel().equals(correctLabel), + "[%s]'s Vertex label can't be changed from '%s' " + + "to '%s'", this.id(), this.schemaLabel(), + correctLabel); + } + this.schemaLabel(correctLabel); + } + public Collection edges() { + return this.edges; + } + + public void edges(Collection edges) { + this.edges = edges; + } + + @Override + public Object sysprop(HugeKeys key) { + switch (key) { + case ID: + return this.id(); + case LABEL: + return this.schemaLabel().id(); + case PRIMARY_VALUES: + return this.name(); + case PROPERTIES: + return this.getPropertiesMap(); + default: + E.checkArgument(false, + "Invalid system property '%s' of Vertex", key); + return null; + } + } + + public VertexLabel schemaLabel() { + return (VertexLabel)super.schemaLabel(); + } + + public boolean olap() { + return VertexLabel.OLAP_VL.equals(this.schemaLabel()); + } + + public HugeType type() { + // 对 Vertex 类型,当 label 为 task 时,返回 TASK 类型,便于根据类型获取存储表信息 + /* Magic: ~task ~taskresult ~variables*/ + if (schemaLabel() != null && + (schemaLabel().name().equals("~task") || + schemaLabel().name().equals("~taskresult") || + schemaLabel().name().equals("~variables"))) { + return HugeType.TASK; + } + return HugeType.VERTEX; + } +} diff --git a/hugegraph-struct/src/main/java/org/apache/hugegraph/structure/Index.java b/hugegraph-struct/src/main/java/org/apache/hugegraph/structure/Index.java new file mode 100644 index 0000000000..deb94913be --- /dev/null +++ b/hugegraph-struct/src/main/java/org/apache/hugegraph/structure/Index.java @@ -0,0 +1,334 @@ +/* + * Copyright 2017 HugeGraph Authors + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.structure; + +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; + +import org.apache.hugegraph.util.E; +import org.apache.hugegraph.util.InsertionOrderUtil; +import org.apache.hugegraph.util.NumericUtil; + +import org.apache.hugegraph.HugeGraphSupplier; +import org.apache.hugegraph.exception.HugeException; +import org.apache.hugegraph.id.Id; +import org.apache.hugegraph.id.Id.IdType; +import org.apache.hugegraph.id.IdGenerator; +import org.apache.hugegraph.id.SplicingIdGenerator; +import org.apache.hugegraph.schema.IndexLabel; +import org.apache.hugegraph.schema.SchemaElement; +import org.apache.hugegraph.serializer.BytesBuffer; +import org.apache.hugegraph.type.GraphType; +import org.apache.hugegraph.type.HugeType; +import org.apache.hugegraph.type.define.DataType; +import com.google.common.collect.ImmutableSet; + +public class Index implements GraphType, Cloneable { + + private final HugeGraphSupplier graph; + private Object fieldValues; + private IndexLabel indexLabel; + /* + * Index read use elementIds, Index write always one element, use + * elementId + */ + private Set elementIds; + private IdWithExpiredTime elementId; + + public Index(HugeGraphSupplier graph, IndexLabel indexLabel) { + E.checkNotNull(graph, "graph"); + E.checkNotNull(indexLabel, "label"); + E.checkNotNull(indexLabel.id(), "label id"); + this.graph = graph; + this.indexLabel = indexLabel; + this.elementIds = new LinkedHashSet<>(); + this.fieldValues = null; + } + + public Index(HugeGraphSupplier graph, IndexLabel indexLabel, boolean write) { + E.checkNotNull(graph, "graph"); + E.checkNotNull(indexLabel, "label"); + E.checkNotNull(indexLabel.id(), "label id"); + this.graph = graph; + this.indexLabel = indexLabel; + if (!write) { + this.elementIds = new LinkedHashSet<>(); + } + this.elementId = null; + this.fieldValues = null; + } + + @Override + public String name() { + return this.indexLabel.name(); + } + + @Override + public HugeType type() { + if (this.indexLabel == IndexLabel.label(HugeType.VERTEX)) { + return HugeType.VERTEX_LABEL_INDEX; + } else if (this.indexLabel == IndexLabel.label(HugeType.EDGE)) { + return HugeType.EDGE_LABEL_INDEX; + } + return this.indexLabel.indexType().type(); + } + + public HugeGraphSupplier graph() { + return this.graph; + } + + public Id id() { + return formatIndexId(type(), this.indexLabelId(), this.fieldValues()); + } + + public Object fieldValues() { + return this.fieldValues; + } + + public void fieldValues(Object fieldValues) { + this.fieldValues = fieldValues; + } + + public Id indexLabelId() { + return this.indexLabel.id(); + } + + public IndexLabel indexLabel() { + return this.indexLabel; + } + + public IdWithExpiredTime elementIdWithExpiredTime() { + if (this.elementIds == null) { + return this.elementId; + } + E.checkState(this.elementIds.size() == 1, + "Expect one element id, actual %s", + this.elementIds.size()); + return this.elementIds.iterator().next(); + } + + public Id elementId() { + return this.elementIdWithExpiredTime().id(); + } + + public Set elementIds() { + if (this.elementIds == null) { + return ImmutableSet.of(); + } + Set ids = InsertionOrderUtil.newSet(this.elementIds.size()); + for (IdWithExpiredTime idWithExpiredTime : this.elementIds) { + ids.add(idWithExpiredTime.id()); + } + return Collections.unmodifiableSet(ids); + } + + public Set expiredElementIds() { + long now = this.graph.now(); + Set expired = InsertionOrderUtil.newSet(); + for (IdWithExpiredTime id : this.elementIds) { + if (0L < id.expiredTime && id.expiredTime < now) { + expired.add(id); + } + } + this.elementIds.removeAll(expired); + return expired; + } + + public void elementIds(Id elementId) { + this.elementIds(elementId, 0L); + } + + public void elementIds(Id elementId, long expiredTime) { + if (this.elementIds == null) { + this.elementId = new IdWithExpiredTime(elementId, expiredTime); + } else { + this.elementIds.add(new IdWithExpiredTime(elementId, expiredTime)); + } + } + + public void resetElementIds() { + this.elementIds = null; + } + + public long expiredTime() { + return this.elementIdWithExpiredTime().expiredTime(); + } + + public boolean hasTtl() { + if ((this.indexLabel() == IndexLabel.label(HugeType.VERTEX) || + this.indexLabel() == IndexLabel.label(HugeType.EDGE)) && + this.expiredTime() > 0) { + // LabelIndex 索引,如果元素存在过期时间,则索引也有 TTL + return true; + } + + if (this.indexLabel.system()) { + return false; + } + return this.indexLabel.baseElement().ttl() > 0L; + } + + public long ttl() { + return this.expiredTime() - this.graph.now(); + } + + @Override + public Index clone() { + try { + return (Index) super.clone(); + } catch (CloneNotSupportedException e) { + throw new HugeException("Failed to clone Index", e); + } + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof Index)) { + return false; + } + + Index other = (Index) obj; + return this.id().equals(other.id()); + } + + @Override + public int hashCode() { + return this.id().hashCode(); + } + + @Override + public String toString() { + return String.format("{label=%s<%s>, fieldValues=%s, elementIds=%s}", + this.indexLabel.name(), + this.indexLabel.indexType().string(), + this.fieldValues, this.elementIds); + } + + + public static Id formatIndexId(HugeType type, Id indexLabelId, + Object fieldValues) { + if (type.isStringIndex()) { + String value = ""; + if (fieldValues instanceof Id) { + value = IdGenerator.asStoredString((Id) fieldValues); + } else if (fieldValues != null) { + value = fieldValues.toString(); + } + /* + * Modify order between index label and field-values to put the + * index label in front(hugegraph-1317) + */ + String strIndexLabelId = IdGenerator.asStoredString(indexLabelId); + // 根据类型添加 id 前缀 + return SplicingIdGenerator.splicing(type.string(), strIndexLabelId, value); + } else { + assert type.isRangeIndex(); + int length = type.isRange4Index() ? 4 : 8; + // 1 为表类型 4 为 lableId length 为 value + BytesBuffer buffer = BytesBuffer.allocate(1 + 4 + length); + // 增加表类型 id + buffer.write(type.code()); + + buffer.writeInt(SchemaElement.schemaId(indexLabelId)); + if (fieldValues != null) { + E.checkState(fieldValues instanceof Number, + "Field value of range index must be number:" + + " %s", fieldValues.getClass().getSimpleName()); + byte[] bytes = number2bytes((Number) fieldValues); + buffer.write(bytes); + } + return buffer.asId(); + } + } + + public static Index parseIndexId(HugeGraphSupplier graph, HugeType type, + byte[] id) { + Object values; + IndexLabel indexLabel; + if (type.isStringIndex()) { + Id idObject = IdGenerator.of(id, IdType.STRING); + String[] parts = SplicingIdGenerator.parse(idObject); + E.checkState(parts.length == 3, "Invalid secondary index id"); + Id label = IdGenerator.ofStoredString(parts[1], IdType.LONG); + indexLabel = IndexLabel.label(graph, label); + values = parts[2]; + } else { + assert type.isRange4Index() || type.isRange8Index(); + final int labelLength = 4; + E.checkState(id.length > labelLength, "Invalid range index id"); + BytesBuffer buffer = BytesBuffer.wrap(id); + // 先读取首位 代表 所属表的类型 + final int hugeTypeCodeLength = 1; + byte[] read = buffer.read(hugeTypeCodeLength); + + Id label = IdGenerator.of(buffer.readInt()); + indexLabel = IndexLabel.label(graph, label); + List fields = indexLabel.indexFields(); + E.checkState(fields.size() == 1, "Invalid range index fields"); + DataType dataType = graph.propertyKey(fields.get(0)).dataType(); + E.checkState(dataType.isNumber() || dataType.isDate(), + "Invalid range index field type"); + Class clazz = dataType.isNumber() ? + dataType.clazz() : DataType.LONG.clazz(); + values = bytes2number(buffer.read(id.length - labelLength - hugeTypeCodeLength), clazz); + } + Index index = new Index(graph, indexLabel); + index.fieldValues(values); + return index; + } + + public static byte[] number2bytes(Number number) { + if (number instanceof Byte) { + // Handle byte as integer to store as 4 bytes in RANGE4_INDEX + number = number.intValue(); + } + return NumericUtil.numberToSortableBytes(number); + } + + public static Number bytes2number(byte[] bytes, Class clazz) { + return NumericUtil.sortableBytesToNumber(bytes, clazz); + } + + public static class IdWithExpiredTime { + + private Id id; + private long expiredTime; + + public IdWithExpiredTime(Id id, long expiredTime) { + this.id = id; + this.expiredTime = expiredTime; + } + + public Id id() { + return this.id; + } + + public long expiredTime() { + return this.expiredTime; + } + + @Override + public String toString() { + return String.format("%s(%s)", this.id, this.expiredTime); + } + } +} diff --git a/hugegraph-struct/src/main/java/org/apache/hugegraph/structure/KvElement.java b/hugegraph-struct/src/main/java/org/apache/hugegraph/structure/KvElement.java new file mode 100644 index 0000000000..ac8618d73d --- /dev/null +++ b/hugegraph-struct/src/main/java/org/apache/hugegraph/structure/KvElement.java @@ -0,0 +1,101 @@ +/* + * Copyright 2017 HugeGraph Authors + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.structure; + +import java.util.List; + +import org.apache.hugegraph.type.HugeType; +import org.apache.hugegraph.type.define.HugeKeys; + +/** + * for aggregation calculation + */ +public class KvElement extends BaseElement implements Comparable{ + + private List keys; + + private List values; + + private KvElement(List keys, List values) { + this.keys = keys; + this.values = values; + } + + public static KvElement of (List keys, List values) { + return new KvElement(keys, values); + } + + public List getKeys() { + return keys; + } + + public List getValues() { + return values; + } + + @Override + public Object sysprop(HugeKeys key) { + return null; + } + + @Override + public String name() { + return null; + } + + @Override + public HugeType type() { + return HugeType.KV_TYPE; + } + + /** + * compare by keys + * @param other the object to be compared. + * @return -1 = this > other, 0 = this == other, 1 = this < other. + */ + @Override + public int compareTo(KvElement other) { + if (this == other) { + return 0; + } + + if (other == null || other.keys == null) { + return keys == null ? 0 : 1; + } + + int len = Math.min(keys.size(), other.keys.size()); + for (int i = 0; i < len; i++) { + var o1 = keys.get(i); + var o2 = other.keys.get(i); + if (o1 != o2) { + if (o1 == null || o2 == null) { + return o1 == null ? -1 : 1; + } + + int v = o1.compareTo(o2); + if (v != 0) { + return v; + } + } + } + + return keys.size() - other.keys.size(); + } +} diff --git a/hugegraph-struct/src/main/java/org/apache/hugegraph/structure/builder/IndexBuilder.java b/hugegraph-struct/src/main/java/org/apache/hugegraph/structure/builder/IndexBuilder.java new file mode 100644 index 0000000000..5be53c1477 --- /dev/null +++ b/hugegraph-struct/src/main/java/org/apache/hugegraph/structure/builder/IndexBuilder.java @@ -0,0 +1,327 @@ +/* + * Copyright 2017 HugeGraph Authors + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.structure.builder; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Set; + +import org.apache.commons.lang3.StringUtils; +import org.apache.hugegraph.HugeGraphSupplier; +import org.apache.hugegraph.analyzer.Analyzer; +import org.apache.hugegraph.analyzer.AnalyzerFactory; +import org.apache.hugegraph.id.Id; +import org.apache.hugegraph.query.ConditionQuery; +import org.apache.hugegraph.schema.EdgeLabel; +import org.apache.hugegraph.schema.IndexLabel; +import org.apache.hugegraph.schema.SchemaLabel; +import org.apache.hugegraph.schema.VertexLabel; +import org.apache.hugegraph.structure.BaseEdge; +import org.apache.hugegraph.structure.BaseElement; +import org.apache.hugegraph.structure.BaseProperty; +import org.apache.hugegraph.structure.BaseVertex; +import org.apache.hugegraph.structure.Index; +import org.apache.hugegraph.util.E; +import org.apache.hugegraph.util.Log; +import org.apache.hugegraph.util.NumericUtil; +import org.slf4j.Logger; + +public class IndexBuilder { + private static final Logger LOG = Log.logger(IndexBuilder.class); + + private final HugeGraphSupplier graph; + private final Analyzer textAnalyzer; + + + public static final String INDEX_SYM_NULL = "\u0001"; + public static final String INDEX_SYM_EMPTY = "\u0002"; + public static final char INDEX_SYM_MAX = '\u0003'; + + private static final String TEXT_ANALYZER = "search.text_analyzer"; + private static final String TEXT_ANALYZER_MODE = + "search.text_analyzer_mode"; + + private static final String DEFAULT_TEXT_ANALYZER = "ikanalyzer"; + private static final String DEFAULT_TEXT_ANALYZER_MODE = "smart"; + + public IndexBuilder(HugeGraphSupplier graph) { + this.graph = graph; + + String name = graph.configuration().get(String.class, TEXT_ANALYZER); + String mode = graph.configuration().get(String.class, + TEXT_ANALYZER_MODE); + + name = name == null ? DEFAULT_TEXT_ANALYZER : name; + mode = mode == null ? DEFAULT_TEXT_ANALYZER_MODE : mode; + + LOG.debug("Loading text analyzer '{}' with mode '{}' for graph '{}'", + name, mode, graph.name()); + this.textAnalyzer = AnalyzerFactory.analyzer(name, mode); + } + + public List buildLabelIndex(BaseElement element) { + + List indexList = new ArrayList(); + // Don't Build label index if it's not enabled + SchemaLabel label = element.schemaLabel(); + + // Build label index if backend store not supports label-query + Index index = new Index(graph, + IndexLabel.label(element.type()), + true); + index.fieldValues(element.schemaLabel().id()); + index.elementIds(element.id(), element.expiredTime()); + + indexList.add(index); + + /**当添加一条子类型的边的时候,将其 edgeID 同时放入到父类型的 edgeLabelIndex 中 + * 从而支持:g.E().hasLabel("父类型") + * */ + if (element instanceof BaseEdge && ((EdgeLabel) label).hasFather()) { + Index fatherIndex = new Index(graph, + IndexLabel.label(element.type())); + fatherIndex.fieldValues(((EdgeLabel) label).fatherId()); + fatherIndex.elementIds(element.id(), element.expiredTime()); + + indexList.add(fatherIndex); + } + + return indexList; + } + + public List buildVertexOlapIndex(BaseVertex vertex) { + + List indexs = new ArrayList<>(); + + Id pkId = vertex.getProperties().keySet().iterator().next(); + Collection indexLabels = graph.indexLabels(); + for (IndexLabel il : indexLabels) { + if (il.indexFields().contains(pkId)) { + indexs.addAll(this.buildIndex(vertex, il)); + } + } + + return indexs; + } + + public List buildVertexIndex(BaseVertex vertex) { + List indexs = new ArrayList<>(); + + VertexLabel label = vertex.schemaLabel(); + + if (label.enableLabelIndex()) { + indexs.addAll(this.buildLabelIndex(vertex)); + } + + for (Id il : label.indexLabels()) { + indexs.addAll(this.buildIndex(vertex, graph.indexLabel(il))); + } + + return indexs; + } + + public List buildEdgeIndex(BaseEdge edge) { + List indexs = new ArrayList<>(); + + EdgeLabel label = edge.schemaLabel(); + + if (label.enableLabelIndex()) { + indexs.addAll(this.buildLabelIndex(edge)); + } + + + for (Id il : label.indexLabels()) { + indexs.addAll(this.buildIndex(edge, graph.indexLabel(il))); + } + + return indexs; + } + + /** + * Build index(user properties) of vertex or edge + * Notice: 本方法没有使用 unique 索引校验当前 element 是否已经存在 + * + * @param indexLabel the index label + * @param element the properties owner + */ + public List buildIndex(BaseElement element, IndexLabel indexLabel) { + E.checkArgument(indexLabel != null, + "Not exist index label with id '%s'", indexLabel.id()); + + List indexs = new ArrayList<>(); + + // Collect property values of index fields + List allPropValues = new ArrayList<>(); + int fieldsNum = indexLabel.indexFields().size(); + int firstNullField = fieldsNum; + for (Id fieldId : indexLabel.indexFields()) { + BaseProperty property = element.getProperty(fieldId); + if (property == null) { + E.checkState(hasNullableProp(element, fieldId), + "Non-null property '%s' is null for '%s'", + graph.propertyKey(fieldId), element); + if (firstNullField == fieldsNum) { + firstNullField = allPropValues.size(); + } + allPropValues.add(INDEX_SYM_NULL); + } else { + E.checkArgument(!INDEX_SYM_NULL.equals(property.value()), + "Illegal value of index property: '%s'", + INDEX_SYM_NULL); + allPropValues.add(property.value()); + } + } + + if (firstNullField == 0 && !indexLabel.indexType().isUnique()) { + // The property value of first index field is null + return indexs; + } + // Not build index for record with nullable field (except unique index) + List propValues = allPropValues.subList(0, firstNullField); + + // Expired time + long expiredTime = element.expiredTime(); + + // Build index for each index type + switch (indexLabel.indexType()) { + case RANGE_INT: + case RANGE_FLOAT: + case RANGE_LONG: + case RANGE_DOUBLE: + E.checkState(propValues.size() == 1, + "Expect only one property in range index"); + Object value = NumericUtil.convertToNumber(propValues.get(0)); + indexs.add(this.buildIndex(indexLabel, value, element.id(), + expiredTime)); + break; + case SEARCH: + E.checkState(propValues.size() == 1, + "Expect only one property in search index"); + value = propValues.get(0); + Set words = + this.segmentWords(propertyValueToString(value)); + for (String word : words) { + indexs.add(this.buildIndex(indexLabel, word, element.id(), + expiredTime)); + } + break; + case SECONDARY: + // Secondary index maybe include multi prefix index + if (isCollectionIndex(propValues)) { + /* + * Property value is a collection + * we should create index for each item + */ + for (Object propValue : + (Collection) propValues.get(0)) { + value = ConditionQuery.concatValuesLimitLength( + propValue); + value = escapeIndexValueIfNeeded((String) value); + indexs.add(this.buildIndex(indexLabel, value, + element.id(), + expiredTime)); + } + } else { + for (int i = 0, n = propValues.size(); i < n; i++) { + List prefixValues = + propValues.subList(0, i + 1); + value = ConditionQuery.concatValuesLimitLength( + prefixValues); + value = escapeIndexValueIfNeeded((String) value); + indexs.add(this.buildIndex(indexLabel, value, + element.id(), + expiredTime)); + } + } + break; + case SHARD: + value = ConditionQuery.concatValuesLimitLength(propValues); + value = escapeIndexValueIfNeeded((String) value); + indexs.add(this.buildIndex(indexLabel, value, element.id(), + expiredTime)); + break; + case UNIQUE: + value = ConditionQuery.concatValuesLimitLength(allPropValues); + assert !"".equals(value); + indexs.add(this.buildIndex(indexLabel, value, element.id(), + expiredTime)); + break; + default: + throw new AssertionError(String.format( + "Unknown index type '%s'", indexLabel.indexType())); + } + + return indexs; + } + + private Index buildIndex(IndexLabel indexLabel, Object propValue, + Id elementId, long expiredTime) { + Index index = new Index(graph, indexLabel, true); + index.fieldValues(propValue); + index.elementIds(elementId, expiredTime); + + return index; + } + + + private static String escapeIndexValueIfNeeded(String value) { + for (int i = 0; i < value.length(); i++) { + char ch = value.charAt(i); + if (ch <= INDEX_SYM_MAX) { + /* + * Escape symbols can't be used due to impossible to parse, + * and treat it as illegal value for the origin text property + */ + E.checkArgument(false, "Illegal char '\\u000%s' " + + "in index property: '%s'", (int) ch, + value); + } + } + if (value.isEmpty()) { + // Escape empty String to INDEX_SYM_EMPTY (char `\u0002`) + value = INDEX_SYM_EMPTY; + } + return value; + } + + private static boolean hasNullableProp(BaseElement element, Id key) { + return element.schemaLabel().nullableKeys().contains(key); + } + + private static boolean isCollectionIndex(List propValues) { + return propValues.size() == 1 && + propValues.get(0) instanceof Collection; + } + + private Set segmentWords(String text) { + return this.textAnalyzer.segment(text); + } + + private static String propertyValueToString(Object value) { + /* + * Join collection items with white space if the value is Collection, + * or else keep the origin value. + */ + return value instanceof Collection ? + StringUtils.join(((Collection) value).toArray(), " ") : + value.toString(); + } +} diff --git a/hugegraph-struct/src/main/java/org/apache/hugegraph/type/GraphType.java b/hugegraph-struct/src/main/java/org/apache/hugegraph/type/GraphType.java new file mode 100644 index 0000000000..8e6825a948 --- /dev/null +++ b/hugegraph-struct/src/main/java/org/apache/hugegraph/type/GraphType.java @@ -0,0 +1,23 @@ +/* + * Copyright 2017 HugeGraph Authors + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.type; + +public interface GraphType extends Namifiable, Typifiable { +} diff --git a/hugegraph-struct/src/main/java/org/apache/hugegraph/type/HugeType.java b/hugegraph-struct/src/main/java/org/apache/hugegraph/type/HugeType.java new file mode 100644 index 0000000000..5a9a11957d --- /dev/null +++ b/hugegraph-struct/src/main/java/org/apache/hugegraph/type/HugeType.java @@ -0,0 +1,213 @@ +/* + * Copyright 2017 HugeGraph Authors + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.type; + +import java.util.HashMap; +import java.util.Map; + +import org.apache.hugegraph.type.define.SerialEnum; + +public enum HugeType implements SerialEnum { + + UNKNOWN(0, "UNKNOWN"), + + /* Schema types */ + VERTEX_LABEL(1, "VL"), + EDGE_LABEL(2, "EL"), + PROPERTY_KEY(3, "PK"), + INDEX_LABEL(4, "IL"), + + COUNTER(50, "C"), + + /* Data types */ + VERTEX(101, "V"), + // System meta + SYS_PROPERTY(102, "S"), + // Property + PROPERTY(103, "U"), + // Vertex aggregate property + AGGR_PROPERTY_V(104, "VP"), + // Edge aggregate property + AGGR_PROPERTY_E(105, "EP"), + // Olap property + OLAP(106, "AP"), + // Edge + EDGE(120, "E"), + // Edge's direction is OUT for the specified vertex + EDGE_OUT(130, "O"), + // Edge's direction is IN for the specified vertex + EDGE_IN(140, "I"), + + SECONDARY_INDEX(150, "SI"), + VERTEX_LABEL_INDEX(151, "VI"), + EDGE_LABEL_INDEX(152, "EI"), + RANGE_INT_INDEX(160, "II"), + RANGE_FLOAT_INDEX(161, "FI"), + RANGE_LONG_INDEX(162, "LI"), + RANGE_DOUBLE_INDEX(163, "DI"), + SEARCH_INDEX(170, "AI"), + SHARD_INDEX(175, "HI"), + UNIQUE_INDEX(178, "UI"), + + TASK(180, "T"), + SERVER(181, "SERVER"), + + VARIABLE(185,"VA"), + + KV_TYPE(200, "KV"), + KV_RAW(201, "KVR"), + + // System schema + SYS_SCHEMA(250, "SS"), + + MAX_TYPE(255, "~"); + + private byte type = 0; + private String name; + + private static final Map ALL_NAME = new HashMap<>(); + + static { + SerialEnum.register(HugeType.class); + for (HugeType type : values()) { + ALL_NAME.put(type.name, type); + } + } + + HugeType(int type, String name) { + assert type < 256; + this.type = (byte) type; + this.name = name; + } + + @Override + public byte code() { + return this.type; + } + + public String string() { + return this.name; + } + + public String readableName() { + return this.name().replace('_', ' ').toLowerCase(); + } + + public boolean isSchema() { + return this == HugeType.VERTEX_LABEL || + this == HugeType.EDGE_LABEL || + this == HugeType.PROPERTY_KEY || + this == HugeType.INDEX_LABEL; + } + + public boolean isGraph() { + return this.isVertex() || this.isEdge() ; + } + + public boolean isVertex() { + // 认为 task vertex variable 是一样的,都是用来存储 HugeVertex 结构 + return this == HugeType.VERTEX || this == HugeType.TASK || + this == HugeType.VARIABLE; + } + + public boolean isEdge() { + return this == EDGE || this == EDGE_OUT || this == EDGE_IN; + } + + public boolean isEdgeLabel() { + return this == EDGE_LABEL; + } + + + public boolean isIndex() { + return this == VERTEX_LABEL_INDEX || this == EDGE_LABEL_INDEX || + this == SECONDARY_INDEX || this == SEARCH_INDEX || + this == RANGE_INT_INDEX || this == RANGE_FLOAT_INDEX || + this == RANGE_LONG_INDEX || this == RANGE_DOUBLE_INDEX || + this == SHARD_INDEX || this == UNIQUE_INDEX; + } + + public boolean isLabelIndex() { + return this == VERTEX_LABEL_INDEX || this == EDGE_LABEL_INDEX; + } + + public boolean isStringIndex() { + return this == VERTEX_LABEL_INDEX || this == EDGE_LABEL_INDEX || + this == SECONDARY_INDEX || this == SEARCH_INDEX || + this == SHARD_INDEX || this == UNIQUE_INDEX; + } + + public boolean isNumericIndex() { + return this == RANGE_INT_INDEX || this == RANGE_FLOAT_INDEX || + this == RANGE_LONG_INDEX || this == RANGE_DOUBLE_INDEX || + this == SHARD_INDEX; + } + + public boolean isSecondaryIndex() { + return this == VERTEX_LABEL_INDEX || this == EDGE_LABEL_INDEX || + this == SECONDARY_INDEX; + } + + public boolean isSearchIndex() { + return this == SEARCH_INDEX; + } + + public boolean isRangeIndex() { + return this == RANGE_INT_INDEX || this == RANGE_FLOAT_INDEX || + this == RANGE_LONG_INDEX || this == RANGE_DOUBLE_INDEX; + } + + public boolean isRange4Index() { + return this == RANGE_INT_INDEX || this == RANGE_FLOAT_INDEX; + } + + public boolean isRange8Index() { + return this == RANGE_LONG_INDEX || this == RANGE_DOUBLE_INDEX; + } + + public boolean isShardIndex() { + return this == SHARD_INDEX; + } + + public boolean isUniqueIndex() { + return this == UNIQUE_INDEX; + } + + public boolean isVertexAggregateProperty() { + return this == AGGR_PROPERTY_V; + } + + public boolean isEdgeAggregateProperty() { + return this == AGGR_PROPERTY_E; + } + + public boolean isAggregateProperty() { + return this.isVertexAggregateProperty() || + this.isEdgeAggregateProperty(); + } + + public static HugeType fromString(String type) { + return ALL_NAME.get(type); + } + + public static HugeType fromCode(byte code) { + return SerialEnum.fromCode(HugeType.class, code); + } +} diff --git a/hugegraph-struct/src/main/java/org/apache/hugegraph/type/Idfiable.java b/hugegraph-struct/src/main/java/org/apache/hugegraph/type/Idfiable.java new file mode 100644 index 0000000000..c5a58c0eb1 --- /dev/null +++ b/hugegraph-struct/src/main/java/org/apache/hugegraph/type/Idfiable.java @@ -0,0 +1,27 @@ +/* + * Copyright 2017 HugeGraph Authors + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.type; + +import org.apache.hugegraph.id.Id; + +public interface Idfiable { + + public Id id(); +} diff --git a/hugegraph-struct/src/main/java/org/apache/hugegraph/type/Indexfiable.java b/hugegraph-struct/src/main/java/org/apache/hugegraph/type/Indexfiable.java new file mode 100644 index 0000000000..a809a49a74 --- /dev/null +++ b/hugegraph-struct/src/main/java/org/apache/hugegraph/type/Indexfiable.java @@ -0,0 +1,29 @@ +/* + * Copyright 2017 HugeGraph Authors + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.type; + +import org.apache.hugegraph.id.Id; + +import java.util.Set; + +public interface Indexfiable { + + public Set indexLabels(); +} diff --git a/hugegraph-struct/src/main/java/org/apache/hugegraph/type/Namifiable.java b/hugegraph-struct/src/main/java/org/apache/hugegraph/type/Namifiable.java new file mode 100644 index 0000000000..b8e7574b33 --- /dev/null +++ b/hugegraph-struct/src/main/java/org/apache/hugegraph/type/Namifiable.java @@ -0,0 +1,30 @@ +// Copyright 2017 JanusGraph Authors +// +// Licensed 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.type; + +/** + * Represents an entity that can be uniquely identified by a String name. + * + * @author Matthias Broecheler (me@matthiasb.com) + */ +public interface Namifiable { + /** + * Returns the unique name of this entity. + * + * @return Name of this entity. + */ + public String name(); + +} diff --git a/hugegraph-struct/src/main/java/org/apache/hugegraph/type/Propfiable.java b/hugegraph-struct/src/main/java/org/apache/hugegraph/type/Propfiable.java new file mode 100644 index 0000000000..021d0c00f9 --- /dev/null +++ b/hugegraph-struct/src/main/java/org/apache/hugegraph/type/Propfiable.java @@ -0,0 +1,29 @@ +/* + * Copyright 2017 HugeGraph Authors + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.type; + +import java.util.Set; + +import org.apache.hugegraph.id.Id; + +public interface Propfiable { + + public Set properties(); +} diff --git a/hugegraph-struct/src/main/java/org/apache/hugegraph/type/Typifiable.java b/hugegraph-struct/src/main/java/org/apache/hugegraph/type/Typifiable.java new file mode 100644 index 0000000000..9a510722b8 --- /dev/null +++ b/hugegraph-struct/src/main/java/org/apache/hugegraph/type/Typifiable.java @@ -0,0 +1,26 @@ +/* + * Copyright 2017 HugeGraph Authors + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.type; + +public interface Typifiable { + + // Return schema/data type + public HugeType type(); +} diff --git a/hugegraph-struct/src/main/java/org/apache/hugegraph/type/define/Action.java b/hugegraph-struct/src/main/java/org/apache/hugegraph/type/define/Action.java new file mode 100644 index 0000000000..042594c224 --- /dev/null +++ b/hugegraph-struct/src/main/java/org/apache/hugegraph/type/define/Action.java @@ -0,0 +1,76 @@ +/* + * Copyright 2017 HugeGraph Authors + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.type.define; + +public enum Action implements SerialEnum { + + INSERT(1, "insert"), + + APPEND(2, "append"), + + ELIMINATE(3, "eliminate"), + + DELETE(4, "delete"), + + UPDATE_IF_PRESENT(5, "update_if_present"), + + UPDATE_IF_ABSENT(6, "update_if_absent"); + + private final byte code; + private final String name; + + static { + SerialEnum.register(Action.class); + } + + Action(int code, String name) { + assert code < 256; + this.code = (byte) code; + this.name = name; + } + + @Override + public byte code() { + return this.code; + } + + public String string() { + return this.name; + } + + public static Action fromCode(byte code) { + switch (code) { + case 1: + return INSERT; + case 2: + return APPEND; + case 3: + return ELIMINATE; + case 4: + return DELETE; + case 5: + return UPDATE_IF_PRESENT; + case 6: + return UPDATE_IF_ABSENT; + default: + throw new AssertionError("Unsupported action code: " + code); + } + } +} diff --git a/hugegraph-struct/src/main/java/org/apache/hugegraph/type/define/AggregateType.java b/hugegraph-struct/src/main/java/org/apache/hugegraph/type/define/AggregateType.java new file mode 100644 index 0000000000..e949d4af14 --- /dev/null +++ b/hugegraph-struct/src/main/java/org/apache/hugegraph/type/define/AggregateType.java @@ -0,0 +1,93 @@ +/* + * Copyright 2017 HugeGraph Authors + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.type.define; + +public enum AggregateType implements SerialEnum { + + NONE(0, "none"), + MAX(1, "max"), + MIN(2, "min"), + SUM(3, "sum"), + OLD(4, "old"), + SET(5, "set"), + LIST(6, "list"); + + private final byte code; + private final String name; + + static { + SerialEnum.register(AggregateType.class); + } + + AggregateType(int code, String name) { + assert code < 256; + this.code = (byte) code; + this.name = name; + } + + @Override + public byte code() { + return this.code; + } + + public String string() { + return this.name; + } + + public boolean isNone() { + return this == NONE; + } + + public boolean isMax() { + return this == MAX; + } + + public boolean isMin() { + return this == MIN; + } + + public boolean isSum() { + return this == SUM; + } + + public boolean isNumber() { + return this.isMax() || this.isMin() || this.isSum(); + } + + public boolean isOld() { + return this == OLD; + } + + public boolean isSet() { + return this == SET; + } + + public boolean isList() { + return this == LIST; + } + + public boolean isUnion() { + return this == SET || this == LIST; + } + + public boolean isIndexable() { + return this == NONE || this == MAX || this == MIN || this == OLD; + } +} diff --git a/hugegraph-struct/src/main/java/org/apache/hugegraph/type/define/Cardinality.java b/hugegraph-struct/src/main/java/org/apache/hugegraph/type/define/Cardinality.java new file mode 100644 index 0000000000..cc935ef435 --- /dev/null +++ b/hugegraph-struct/src/main/java/org/apache/hugegraph/type/define/Cardinality.java @@ -0,0 +1,69 @@ +// Copyright 2017 JanusGraph Authors +// +// Licensed 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.type.define; + +/** + * The cardinality of the values associated with given key for a particular element. + * + * @author Matthias Broecheler (me@matthiasb.com) + */ +public enum Cardinality implements SerialEnum { + + /** + * Only a single value may be associated with the given key. + */ + SINGLE(1, "single"), + + /** + * Multiple values and duplicate values may be associated with the given + * key. + */ + LIST(2, "list"), + + /** + * Multiple but distinct values may be associated with the given key. + */ + SET(3, "set"); + + private byte code = 0; + private String name = null; + + static { + SerialEnum.register(Cardinality.class); + } + + Cardinality(int code, String name) { + assert code < 256; + this.code = (byte) code; + this.name = name; + } + + @Override + public byte code() { + return this.code; + } + + public String string() { + return this.name; + } + + public boolean single() { + return this == SINGLE; + } + + public boolean multiple() { + return this == LIST || this == SET; + } +} diff --git a/hugegraph-struct/src/main/java/org/apache/hugegraph/type/define/CollectionType.java b/hugegraph-struct/src/main/java/org/apache/hugegraph/type/define/CollectionType.java new file mode 100644 index 0000000000..e8ff98ec95 --- /dev/null +++ b/hugegraph-struct/src/main/java/org/apache/hugegraph/type/define/CollectionType.java @@ -0,0 +1,68 @@ +/* + * Copyright 2017 HugeGraph Authors + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.type.define; + +public enum CollectionType implements SerialEnum { + + // Java Collection Framework + JCF(1, "jcf"), + + // Eclipse Collection + EC(2, "ec"), + + // FastUtil + FU(3, "fu"); + + private final byte code; + private final String name; + + static { + SerialEnum.register(CollectionType.class); + } + + CollectionType(int code, String name) { + assert code < 256; + this.code = (byte) code; + this.name = name; + } + + @Override + public byte code() { + return this.code; + } + + public String string() { + return this.name; + } + + public static CollectionType fromCode(byte code) { + switch (code) { + case 1: + return JCF; + case 2: + return EC; + case 3: + return FU; + default: + throw new AssertionError( + "Unsupported collection code: " + code); + } + } +} diff --git a/hugegraph-struct/src/main/java/org/apache/hugegraph/type/define/DataType.java b/hugegraph-struct/src/main/java/org/apache/hugegraph/type/define/DataType.java new file mode 100644 index 0000000000..6a04a83034 --- /dev/null +++ b/hugegraph-struct/src/main/java/org/apache/hugegraph/type/define/DataType.java @@ -0,0 +1,224 @@ +/* + * Copyright 2017 HugeGraph Authors + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.type.define; + +import java.nio.ByteBuffer; +import java.util.Date; +import java.util.List; +import java.util.UUID; + +import org.apache.hugegraph.util.Bytes; +import org.apache.hugegraph.util.DateUtil; + +import org.apache.hugegraph.exception.HugeException; +import org.apache.hugegraph.serializer.BytesBuffer; +import org.apache.hugegraph.util.Blob; +import org.apache.hugegraph.util.StringEncoding; +import com.google.common.collect.ImmutableSet; + +public enum DataType implements SerialEnum { + + UNKNOWN(0, "unknown", Object.class), + OBJECT(1, "object", Object.class), + BOOLEAN(2, "boolean", Boolean.class), + BYTE(3, "byte", Byte.class), + INT(4, "int", Integer.class), + LONG(5, "long", Long.class), + FLOAT(6, "float", Float.class), + DOUBLE(7, "double", Double.class), + TEXT(8, "text", String.class), + BLOB(9, "blob", Blob.class), + DATE(10, "date", Date.class), + UUID(11, "uuid", UUID.class); + + private final byte code; + private final String name; + private final Class clazz; + + private static final ImmutableSet SPECIAL_FLOATS = ImmutableSet.of("-Infinity", "Infinity", "NaN"); + + + static { + SerialEnum.register(DataType.class); + } + + DataType(int code, String name, Class clazz) { + assert code < 256; + this.code = (byte) code; + this.name = name; + this.clazz = clazz; + } + + @Override + public byte code() { + return this.code; + } + + public String string() { + return this.name; + } + + public Class clazz() { + return this.clazz; + } + + public boolean isText() { + return this == DataType.TEXT; + } + + public boolean isNumber() { + return this == BYTE || this == INT || this == LONG || + this == FLOAT || this == DOUBLE; + } + + public boolean isNumber4() { + // Store index value of Byte using 4 bytes + return this == BYTE || this == INT || this == FLOAT; + } + + public boolean isNumber8() { + return this == LONG || this == DOUBLE; + } + + public boolean isBlob() { + return this == DataType.BLOB; + } + + public boolean isDate() { + return this == DataType.DATE; + } + + public boolean isUUID() { + return this == DataType.UUID; + } + + public Number valueToNumber(V value) { + if (!(this.isNumber() && value instanceof Number) && + !(value instanceof String && SPECIAL_FLOATS.contains(value))) { + return null; + } + if (this.clazz.isInstance(value)) { + return (Number) value; + } + + Number number; + try { + switch (this) { + case BYTE: + number = Byte.valueOf(value.toString()); + break; + case INT: + number = Integer.valueOf(value.toString()); + break; + case LONG: + number = Long.valueOf(value.toString()); + break; + case FLOAT: + number = Float.valueOf(value.toString()); + break; + case DOUBLE: + number = Double.valueOf(value.toString()); + break; + default: + throw new AssertionError(String.format( + "Number type only contains Byte, Integer, " + + "Long, Float, Double, but got %s", this.clazz())); + } + } catch (NumberFormatException e) { + throw new IllegalArgumentException(String.format( + "Can't read '%s' as %s: %s", + value, this.name, e.getMessage())); + } + return number; + } + + public Date valueToDate(V value) { + if (!this.isDate()) { + return null; + } + if (value instanceof Date) { + return (Date) value; + } else if (value instanceof Integer) { + return new Date(((Number) value).intValue()); + } else if (value instanceof Long) { + return new Date(((Number) value).longValue()); + } else if (value instanceof String) { + return DateUtil.parse((String) value); + } + return null; + } + + public UUID valueToUUID(V value) { + if (!this.isUUID()) { + return null; + } + if (value instanceof UUID) { + return (UUID) value; + } else if (value instanceof String) { + return StringEncoding.uuid((String) value); + } + return null; + } + + public Blob valueToBlob(V value) { + if (!this.isBlob()) { + return null; + } + if (value instanceof Blob) { + return (Blob) value; + } else if (value instanceof byte[]) { + return Blob.wrap((byte[]) value); + } else if (value instanceof ByteBuffer) { + return Blob.wrap(((ByteBuffer) value).array()); + } else if (value instanceof BytesBuffer) { + return Blob.wrap(((BytesBuffer) value).bytes()); + } else if (value instanceof String) { + // Only base64 string or hex string accepted + String str = ((String) value); + if (str.startsWith("0x")) { + return Blob.wrap(Bytes.fromHex(str.substring(2))); + } + return Blob.wrap(StringEncoding.decodeBase64(str)); + } else if (value instanceof List) { + List values = (List) value; + byte[] bytes = new byte[values.size()]; + for (int i = 0; i < bytes.length; i++) { + Object v = values.get(i); + if (v instanceof Byte || v instanceof Integer) { + bytes[i] = ((Number) v).byteValue(); + } else { + throw new IllegalArgumentException(String.format( + "expect byte or int value, but got '%s'", v)); + } + } + return Blob.wrap(bytes); + } + return null; + } + + public static DataType fromClass(Class clazz) { + for (DataType type : DataType.values()) { + if (type.clazz() == clazz) { + return type; + } + } + throw new HugeException("Unknown clazz '%s' for DataType", clazz); + } +} diff --git a/hugegraph-struct/src/main/java/org/apache/hugegraph/type/define/Directions.java b/hugegraph-struct/src/main/java/org/apache/hugegraph/type/define/Directions.java new file mode 100644 index 0000000000..4c45990ab2 --- /dev/null +++ b/hugegraph-struct/src/main/java/org/apache/hugegraph/type/define/Directions.java @@ -0,0 +1,89 @@ +/* + * Copyright 2017 HugeGraph Authors + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.type.define; + +import org.apache.hugegraph.type.HugeType; + +public enum Directions implements SerialEnum { + + // TODO: add NONE enum for non-directional edges + + BOTH(0, "both"), + + OUT(1, "out"), + + IN(2, "in"); + + private byte code = 0; + private String name = null; + + static { + SerialEnum.register(Directions.class); + } + + Directions(int code, String name) { + assert code < 256; + this.code = (byte) code; + this.name = name; + } + + @Override + public byte code() { + return this.code; + } + + public String string() { + return this.name; + } + + public HugeType type() { + switch (this) { + case OUT: + return HugeType.EDGE_OUT; + case IN: + return HugeType.EDGE_IN; + default: + throw new IllegalArgumentException(String.format( + "Can't convert direction '%s' to HugeType", this)); + } + } + + public Directions opposite() { + if (this.equals(OUT)) { + return IN; + } else { + return this.equals(IN) ? OUT : BOTH; + } + } + + + + public static Directions convert(HugeType edgeType) { + switch (edgeType) { + case EDGE_OUT: + return OUT; + case EDGE_IN: + return IN; + default: + throw new IllegalArgumentException(String.format( + "Can't convert type '%s' to Direction", edgeType)); + } + } +} diff --git a/hugegraph-struct/src/main/java/org/apache/hugegraph/type/define/EdgeLabelType.java b/hugegraph-struct/src/main/java/org/apache/hugegraph/type/define/EdgeLabelType.java new file mode 100644 index 0000000000..7e90e7a241 --- /dev/null +++ b/hugegraph-struct/src/main/java/org/apache/hugegraph/type/define/EdgeLabelType.java @@ -0,0 +1,72 @@ +/* + * Copyright 2017 HugeGraph Authors + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.type.define; + +public enum EdgeLabelType implements SerialEnum { + + + NORMAL(1, "NORMAL"), + + PARENT(2, "PARENT"), + + SUB(3, "SUB"), + + GENERAL(4, "GENERAL"), + ; + + private final byte code; + private final String name; + + static { + SerialEnum.register(EdgeLabelType.class); + } + + EdgeLabelType(int code, String name) { + assert code < 256; + this.code = (byte) code; + this.name = name; + } + + @Override + public byte code() { + return this.code; + } + + public String string() { + return this.name; + } + + public boolean normal() { + return this == NORMAL; + } + + public boolean parent() { + return this == PARENT; + } + + public boolean sub() { + return this == SUB; + } + + public boolean general() { + return this == GENERAL; + } + +} diff --git a/hugegraph-struct/src/main/java/org/apache/hugegraph/type/define/Frequency.java b/hugegraph-struct/src/main/java/org/apache/hugegraph/type/define/Frequency.java new file mode 100644 index 0000000000..4ebe24867a --- /dev/null +++ b/hugegraph-struct/src/main/java/org/apache/hugegraph/type/define/Frequency.java @@ -0,0 +1,51 @@ +/* + * Copyright 2017 HugeGraph Authors + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.type.define; + +public enum Frequency implements SerialEnum { + + DEFAULT(0, "default"), + + SINGLE(1, "single"), + + MULTIPLE(2, "multiple"); + + private byte code = 0; + private String name = null; + + static { + SerialEnum.register(Frequency.class); + } + + Frequency(int code, String name) { + assert code < 256; + this.code = (byte) code; + this.name = name; + } + + @Override + public byte code() { + return this.code; + } + + public String string() { + return this.name; + } +} diff --git a/hugegraph-struct/src/main/java/org/apache/hugegraph/type/define/HugeKeys.java b/hugegraph-struct/src/main/java/org/apache/hugegraph/type/define/HugeKeys.java new file mode 100644 index 0000000000..dc00972cb7 --- /dev/null +++ b/hugegraph-struct/src/main/java/org/apache/hugegraph/type/define/HugeKeys.java @@ -0,0 +1,108 @@ +/* + * Copyright 2017 HugeGraph Authors + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.type.define; + +public enum HugeKeys { + + UNKNOWN(0, "undefined"), + + /* Column names of schema type (common) */ + ID(1, "id"), + NAME(2, "name"), + TIMESTAMP(3, "timestamp"), + SCHEMA_TYPE(4, "schema_type"), + + USER_DATA(10, "user_data"), + STATUS(11, "status"), + + /* Column names of schema type (VertexLabel) */ + ID_STRATEGY(50, "id_strategy"), + PROPERTIES(51, "properties"), + PRIMARY_KEYS(52, "primary_keys"), + INDEX_LABELS(53, "index_labels"), + NULLABLE_KEYS(54, "nullable_keys"), + ENABLE_LABEL_INDEX(55, "enable_label_index"), + + /* Column names of schema type (EdgeLabel) */ + LINKS(80, "links"), + FREQUENCY(81, "frequency"), + SOURCE_LABEL(82, "source_label"), + TARGET_LABEL(83, "target_label"), + 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"), + CARDINALITY(121, "cardinality"), + AGGREGATE_TYPE(122, "aggregate_type"), + WRITE_TYPE(123, "write_type"), + + /* Column names of schema type (IndexLabel) */ + BASE_TYPE(150, "base_type"), + BASE_VALUE(151, "base_value"), + INDEX_TYPE(152, "index_type"), + FIELDS(153, "fields"), + + /* Column names of index data */ + INDEX_NAME(180, "index_name"), + FIELD_VALUES(181, "field_values"), + INDEX_LABEL_ID(182, "index_label_id"), + ELEMENT_IDS(183, "element_ids"), + + /* Column names of data type (Vertex/Edge) */ + LABEL(200, "label"), + OWNER_VERTEX(201, "owner_vertex"), + OTHER_VERTEX(202, "other_vertex"), + PROPERTY_KEY(203, "property_key"), + PROPERTY_VALUE(204, "property_value"), + DIRECTION(205, "direction"), + 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"), + ; + + public static final long NORMAL_PROPERTY_ID = 0L; + + /* HugeKeys define */ + private byte code = 0; + private String name = null; + + HugeKeys(int code, String name) { + assert code < 256; + this.code = (byte) code; + this.name = name; + } + + public byte code() { + return this.code; + } + + public String string() { + return this.name; + } +} diff --git a/hugegraph-struct/src/main/java/org/apache/hugegraph/type/define/IdStrategy.java b/hugegraph-struct/src/main/java/org/apache/hugegraph/type/define/IdStrategy.java new file mode 100644 index 0000000000..4149c8db91 --- /dev/null +++ b/hugegraph-struct/src/main/java/org/apache/hugegraph/type/define/IdStrategy.java @@ -0,0 +1,71 @@ +/* + * Copyright 2017 HugeGraph Authors + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.type.define; + +public enum IdStrategy implements SerialEnum { + + DEFAULT(0, "default"), + + AUTOMATIC(1, "automatic"), + + PRIMARY_KEY(2, "primary_key"), + + CUSTOMIZE_STRING(3, "customize_string"), + + CUSTOMIZE_NUMBER(4, "customize_number"), + + CUSTOMIZE_UUID(5, "customize_uuid"); + + private byte code = 0; + private String name = null; + + static { + SerialEnum.register(IdStrategy.class); + } + + IdStrategy(int code, String name) { + assert code < 256; + this.code = (byte) code; + this.name = name; + } + + @Override + public byte code() { + return this.code; + } + + public String string() { + return this.name; + } + + public boolean isAutomatic() { + return this == AUTOMATIC; + } + + public boolean isPrimaryKey() { + return this == PRIMARY_KEY; + } + + public boolean isCustomized() { + return this == CUSTOMIZE_STRING || + this == CUSTOMIZE_NUMBER || + this == CUSTOMIZE_UUID; + } +} diff --git a/hugegraph-struct/src/main/java/org/apache/hugegraph/type/define/IndexType.java b/hugegraph-struct/src/main/java/org/apache/hugegraph/type/define/IndexType.java new file mode 100644 index 0000000000..77e59932e7 --- /dev/null +++ b/hugegraph-struct/src/main/java/org/apache/hugegraph/type/define/IndexType.java @@ -0,0 +1,122 @@ +/* + * Copyright 2017 HugeGraph Authors + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.type.define; + +import org.apache.hugegraph.type.HugeType; + +public enum IndexType implements SerialEnum { + + // For secondary query + SECONDARY(1, "secondary"), + + // For range query + RANGE(2, "range"), + RANGE_INT(21, "range_int"), + RANGE_FLOAT(22, "range_float"), + RANGE_LONG(23, "range_long"), + RANGE_DOUBLE(24, "range_double"), + + // For full-text query (not supported now) + SEARCH(3, "search"), + + // For prefix + range query + SHARD(4, "shard"), + + // For unique index + UNIQUE(5, "unique"); + + private byte code = 0; + private String name = null; + + static { + SerialEnum.register(IndexType.class); + } + + IndexType(int code, String name) { + assert code < 256; + this.code = (byte) code; + this.name = name; + } + + @Override + public byte code() { + return this.code; + } + + public String string() { + return this.name; + } + + public HugeType type() { + switch (this) { + case SECONDARY: + return HugeType.SECONDARY_INDEX; + case RANGE_INT: + return HugeType.RANGE_INT_INDEX; + case RANGE_FLOAT: + return HugeType.RANGE_FLOAT_INDEX; + case RANGE_LONG: + return HugeType.RANGE_LONG_INDEX; + case RANGE_DOUBLE: + return HugeType.RANGE_DOUBLE_INDEX; + case SEARCH: + return HugeType.SEARCH_INDEX; + case SHARD: + return HugeType.SHARD_INDEX; + case UNIQUE: + return HugeType.UNIQUE_INDEX; + default: + throw new AssertionError(String.format( + "Unknown index type '%s'", this)); + } + } + + public boolean isString() { + return this == SECONDARY || this == SEARCH || + this == SHARD || this == UNIQUE; + } + + public boolean isNumeric() { + return this == RANGE_INT || this == RANGE_FLOAT || + this == RANGE_LONG || this == RANGE_DOUBLE || + this == SHARD; + } + + public boolean isSecondary() { + return this == SECONDARY; + } + + public boolean isRange() { + return this == RANGE_INT || this == RANGE_FLOAT || + this == RANGE_LONG || this == RANGE_DOUBLE; + } + + public boolean isSearch() { + return this == SEARCH; + } + + public boolean isShard() { + return this == SHARD; + } + + public boolean isUnique() { + return this == UNIQUE; + } +} diff --git a/hugegraph-struct/src/main/java/org/apache/hugegraph/type/define/SchemaStatus.java b/hugegraph-struct/src/main/java/org/apache/hugegraph/type/define/SchemaStatus.java new file mode 100644 index 0000000000..9222aa8ecd --- /dev/null +++ b/hugegraph-struct/src/main/java/org/apache/hugegraph/type/define/SchemaStatus.java @@ -0,0 +1,67 @@ +/* + * Copyright 2017 HugeGraph Authors + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.type.define; + +public enum SchemaStatus implements SerialEnum { + + CREATED(1, "created"), + + CREATING(2, "creating"), + + REBUILDING(3, "rebuilding"), + + DELETING(4, "deleting"), + + UNDELETED(5, "undeleted"), + + INVALID(6, "invalid"), + + CLEARING(7, "clearing"); + + private byte code = 0; + private String name = null; + + static { + SerialEnum.register(SchemaStatus.class); + } + + SchemaStatus(int code, String name) { + assert code < 256; + this.code = (byte) code; + this.name = name; + } + + public boolean ok() { + return this == CREATED; + } + + public boolean deleting() { + return this == DELETING || this == UNDELETED; + } + + @Override + public byte code() { + return this.code; + } + + public String string() { + return this.name; + } +} diff --git a/hugegraph-struct/src/main/java/org/apache/hugegraph/type/define/SerialEnum.java b/hugegraph-struct/src/main/java/org/apache/hugegraph/type/define/SerialEnum.java new file mode 100644 index 0000000000..337c981a76 --- /dev/null +++ b/hugegraph-struct/src/main/java/org/apache/hugegraph/type/define/SerialEnum.java @@ -0,0 +1,83 @@ +/* + * Copyright 2017 HugeGraph Authors + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.type.define; + +import org.apache.hugegraph.exception.HugeException; +import org.apache.hugegraph.type.HugeType; + +import org.apache.hugegraph.util.CollectionUtil; +import org.apache.hugegraph.util.E; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +public interface SerialEnum { + + public byte code(); + +// static Table, Byte, SerialEnum> table = HashBasedTable.create(); + + static Map>table =new ConcurrentHashMap<>(); + + public static void register(Class clazz) { + Object enums; + try { + enums = clazz.getMethod("values").invoke(null); + } catch (Exception e) { + throw new HugeException("Exception in backend", e); + } + ConcurrentHashMap map=new ConcurrentHashMap(); + for (SerialEnum e : CollectionUtil.toList(enums)) { + map.put(e.code(), e); + } + table.put(clazz,map); + } + + + public static T fromCode(Class clazz, byte code) { + Map clazzMap=table.get(clazz); + if (clazzMap == null) { + SerialEnum.register(clazz); + clazzMap=table.get(clazz); + } + E.checkArgument(clazzMap != null, "Can't get class registery for %s", + clazz.getSimpleName()); + T value = (T) clazzMap.get(code); + if (value == null) { + E.checkArgument(false, "Can't construct %s from code %s", + clazz.getSimpleName(), code); + } + return value; + } + + public static void registerInternalEnums() { + SerialEnum.register(Action.class); + SerialEnum.register(AggregateType.class); + SerialEnum.register(Cardinality.class); + SerialEnum.register(DataType.class); + SerialEnum.register(Directions.class); + SerialEnum.register(Frequency.class); + SerialEnum.register(HugeType.class); + SerialEnum.register(IdStrategy.class); + SerialEnum.register(IndexType.class); + SerialEnum.register(SchemaStatus.class); +// SerialEnum.register(HugePermission.class); + } +} diff --git a/hugegraph-struct/src/main/java/org/apache/hugegraph/type/define/WriteType.java b/hugegraph-struct/src/main/java/org/apache/hugegraph/type/define/WriteType.java new file mode 100644 index 0000000000..538b5bc40b --- /dev/null +++ b/hugegraph-struct/src/main/java/org/apache/hugegraph/type/define/WriteType.java @@ -0,0 +1,67 @@ +/* + * Copyright 2017 HugeGraph Authors + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.type.define; + +public enum WriteType implements SerialEnum { + + // OLTP property key + OLTP(1, "oltp"), + + // OLAP property key without index + OLAP_COMMON(2, "olap_common"), + + // OLAP property key with secondary index + OLAP_SECONDARY(3, "olap_secondary"), + + // OLAP property key with range index + OLAP_RANGE(4, "olap_range"); + + private final byte code; + private final String name; + + static { + SerialEnum.register(WriteType.class); + } + + WriteType(int code, String name) { + assert code < 256; + this.code = (byte) code; + this.name = name; + } + + @Override + public byte code() { + return this.code; + } + + public String string() { + return this.name; + } + + public boolean oltp() { + return this == OLTP; + } + + public boolean olap() { + return this == OLAP_COMMON || + this == OLAP_RANGE || + this == OLAP_SECONDARY; + } +} diff --git a/hugegraph-struct/src/main/java/org/apache/hugegraph/util/Blob.java b/hugegraph-struct/src/main/java/org/apache/hugegraph/util/Blob.java new file mode 100644 index 0000000000..03d82e916e --- /dev/null +++ b/hugegraph-struct/src/main/java/org/apache/hugegraph/util/Blob.java @@ -0,0 +1,73 @@ +/* + * Copyright 2017 HugeGraph Authors + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.util; + +import java.util.Arrays; + +import org.apache.hugegraph.util.Bytes; +import org.apache.hugegraph.util.E; + +public class Blob implements Comparable { + + public static final Blob EMPTY = new Blob(new byte[0]); + + private final byte[] bytes; + + private Blob(byte[] bytes) { + E.checkNotNull(bytes, "bytes"); + this.bytes = bytes; + } + + public byte[] bytes() { + return this.bytes; + } + + public static Blob wrap(byte[] bytes) { + return new Blob(bytes); + } + + @Override + public int hashCode() { + return Arrays.hashCode(this.bytes); + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof Blob)) { + return false; + } + Blob other = (Blob) obj; + return Arrays.equals(this.bytes, other.bytes); + } + + @Override + public String toString() { + String hex = Bytes.toHex(this.bytes); + StringBuilder sb = new StringBuilder(6 + hex.length()); + sb.append("Blob{").append(hex).append("}"); + return sb.toString(); + } + + @Override + public int compareTo(Blob other) { + E.checkNotNull(other, "other blob"); + return Bytes.compare(this.bytes, other.bytes); + } +} diff --git a/hugegraph-struct/src/main/java/org/apache/hugegraph/util/GraphUtils.java b/hugegraph-struct/src/main/java/org/apache/hugegraph/util/GraphUtils.java new file mode 100644 index 0000000000..a634d11916 --- /dev/null +++ b/hugegraph-struct/src/main/java/org/apache/hugegraph/util/GraphUtils.java @@ -0,0 +1,34 @@ +/* + * Copyright 2017 HugeGraph Authors + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.util; + +public class GraphUtils { + + private static final String HIDDEN_PREFIX = "~"; + + /** + * 判断是否是系统变量 + * @param key + * @return + */ + public static boolean isHidden(final String key) { + return key.startsWith(HIDDEN_PREFIX); + } +} diff --git a/hugegraph-struct/src/main/java/org/apache/hugegraph/util/LZ4Util.java b/hugegraph-struct/src/main/java/org/apache/hugegraph/util/LZ4Util.java new file mode 100644 index 0000000000..98f23b9b29 --- /dev/null +++ b/hugegraph-struct/src/main/java/org/apache/hugegraph/util/LZ4Util.java @@ -0,0 +1,95 @@ +/* + * Copyright 2017 HugeGraph Authors + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.util; + +import java.io.ByteArrayInputStream; +import java.io.IOException; + +import org.apache.hugegraph.exception.BackendException; +import org.apache.hugegraph.serializer.BytesBuffer; + +import net.jpountz.lz4.LZ4BlockInputStream; +import net.jpountz.lz4.LZ4BlockOutputStream; +import net.jpountz.lz4.LZ4Compressor; +import net.jpountz.lz4.LZ4Factory; +import net.jpountz.lz4.LZ4FastDecompressor; + +public class LZ4Util { + + protected static final float DEFAULT_BUFFER_RATIO = 1.5f; + + public static BytesBuffer compress(byte[] bytes, int blockSize) { + return compress(bytes, blockSize, DEFAULT_BUFFER_RATIO); + } + + public static BytesBuffer compress(byte[] bytes, int blockSize, + float bufferRatio) { + float ratio = bufferRatio <= 0.0F ? DEFAULT_BUFFER_RATIO : bufferRatio; + LZ4Factory factory = LZ4Factory.fastestInstance(); + LZ4Compressor compressor = factory.fastCompressor(); + int initBufferSize = Math.round(bytes.length / ratio); + BytesBuffer buf = new BytesBuffer(initBufferSize); + LZ4BlockOutputStream lz4Output = new LZ4BlockOutputStream( + buf, blockSize, compressor); + try { + lz4Output.write(bytes); + lz4Output.close(); + } catch (IOException e) { + throw new BackendException("Failed to compress", e); + } + /* + * If need to perform reading outside the method, + * remember to call forReadWritten() + */ + return buf; + } + + public static BytesBuffer decompress(byte[] bytes, int blockSize) { + return decompress(bytes, blockSize, DEFAULT_BUFFER_RATIO); + } + + public static BytesBuffer decompress(byte[] bytes, int blockSize, + float bufferRatio) { + float ratio = bufferRatio <= 0.0F ? DEFAULT_BUFFER_RATIO : bufferRatio; + LZ4Factory factory = LZ4Factory.fastestInstance(); + LZ4FastDecompressor decompressor = factory.fastDecompressor(); + ByteArrayInputStream bais = new ByteArrayInputStream(bytes); + int initBufferSize = Math.min(Math.round(bytes.length * ratio), + BytesBuffer.MAX_BUFFER_CAPACITY); + BytesBuffer buf = new BytesBuffer(initBufferSize); + LZ4BlockInputStream lzInput = new LZ4BlockInputStream(bais, + decompressor); + int count; + byte[] buffer = new byte[blockSize]; + try { + while ((count = lzInput.read(buffer)) != -1) { + buf.write(buffer, 0, count); + } + lzInput.close(); + } catch (IOException e) { + throw new BackendException("Failed to decompress", e); + } + /* + * If need to perform reading outside the method, + * remember to call forReadWritten() + */ + return buf; + } +} diff --git a/hugegraph-struct/src/main/java/org/apache/hugegraph/util/StringEncoding.java b/hugegraph-struct/src/main/java/org/apache/hugegraph/util/StringEncoding.java new file mode 100644 index 0000000000..7e9ab6d8f3 --- /dev/null +++ b/hugegraph-struct/src/main/java/org/apache/hugegraph/util/StringEncoding.java @@ -0,0 +1,203 @@ +/* + * Copyright 2017 HugeGraph Authors + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +// Copyright 2017 JanusGraph Authors +// +// Licensed 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.util; + +import java.io.UnsupportedEncodingException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.Base64; +import java.util.UUID; + +import org.apache.hugegraph.util.Bytes; +import org.apache.hugegraph.util.E; +import org.mindrot.jbcrypt.BCrypt; + +import org.apache.hugegraph.exception.HugeException; +import org.apache.hugegraph.serializer.BytesBuffer; +import com.google.common.base.CharMatcher; + +/** + * @author Matthias Broecheler (me@matthiasb.com) + * @author HugeGraph Authors + */ +public final class StringEncoding { + + private static final MessageDigest DIGEST; + private static final byte[] BYTES_EMPTY = new byte[0]; + private static final int BLOCK_SIZE = 4096; + + static { + final String ALG = "SHA-256"; + try { + DIGEST = MessageDigest.getInstance(ALG); + } catch (NoSuchAlgorithmException e) { + throw new HugeException("Failed to load algorithm %s", e, ALG); + } + } + + private static final Base64.Encoder BASE64_ENCODER = Base64.getEncoder(); + private static final Base64.Decoder BASE64_DECODER = Base64.getDecoder(); + + // Similar to {@link StringSerializer} + public static int writeAsciiString(byte[] array, int offset, String value) { + E.checkArgument(CharMatcher.ascii().matchesAllOf(value), + "'%s' must be ASCII string", value); + int len = value.length(); + if (len == 0) { + array[offset++] = (byte) 0x80; + return offset; + } + + int i = 0; + do { + int c = value.charAt(i); + assert c <= 127; + byte b = (byte) c; + if (++i == len) { + b |= 0x80; // End marker + } + array[offset++] = b; + } while (i < len); + + return offset; + } + + public static String readAsciiString(byte[] array, int offset) { + StringBuilder sb = new StringBuilder(); + int c = 0; + do { + c = 0xFF & array[offset++]; + if (c != 0x80) { + sb.append((char) (c & 0x7F)); + } + } while ((c & 0x80) <= 0); + return sb.toString(); + } + + public static int getAsciiByteLength(String value) { + E.checkArgument(CharMatcher.ascii().matchesAllOf(value), + "'%s' must be ASCII string", value); + return value.isEmpty() ? 1 : value.length(); + } + + public static byte[] encode(String value) { + try { + return value.getBytes("UTF-8"); + } catch (UnsupportedEncodingException e) { + throw new HugeException("Failed to encode string", e); + } + } + + public static String decode(byte[] bytes) { + try { + return new String(bytes, "UTF-8"); + } catch (UnsupportedEncodingException e) { + throw new HugeException("Failed to decode string", e); + } + } + + public static String decode(byte[] bytes, int offset, int length) { + try { + return new String(bytes, offset, length, "UTF-8"); + } catch (UnsupportedEncodingException e) { + throw new HugeException("Failed to decode string", e); + } + } + + public static String encodeBase64(byte[] bytes) { + return BASE64_ENCODER.encodeToString(bytes); + } + + public static byte[] decodeBase64(String value) { + if (value.isEmpty()) { + return BYTES_EMPTY; + } + return BASE64_DECODER.decode(value); + } + + public static byte[] compress(String value) { + return compress(value, LZ4Util.DEFAULT_BUFFER_RATIO); + } + + public static byte[] compress(String value, float bufferRatio) { + BytesBuffer buf = LZ4Util.compress(encode(value), BLOCK_SIZE, + bufferRatio); + return buf.bytes(); + } + + public static String decompress(byte[] value) { + return decompress(value, LZ4Util.DEFAULT_BUFFER_RATIO); + } + + public static String decompress(byte[] value, float bufferRatio) { + BytesBuffer buf = LZ4Util.decompress(value, BLOCK_SIZE, bufferRatio); + return decode(buf.array(), 0, buf.position()); + } + + public static String hashPassword(String password) { + return BCrypt.hashpw(password, BCrypt.gensalt(4)); + } + + public static boolean checkPassword(String candidatePassword, + String dbPassword) { + return BCrypt.checkpw(candidatePassword, dbPassword); + } + + public static String sha256(String string) { + byte[] stringBytes = encode(string); + DIGEST.reset(); + return StringEncoding.encodeBase64(DIGEST.digest(stringBytes)); + } + + public static String format(byte[] bytes) { + return String.format("%s[0x%s]", decode(bytes), Bytes.toHex(bytes)); + } + + public static UUID uuid(String value) { + E.checkArgument(value != null, "The UUID can't be null"); + try { + if (value.contains("-") && value.length() == 36) { + return UUID.fromString(value); + } + // UUID represented by hex string + E.checkArgument(value.length() == 32, + "Invalid UUID string: %s", value); + String high = value.substring(0, 16); + String low = value.substring(16); + return new UUID(Long.parseUnsignedLong(high, 16), + Long.parseUnsignedLong(low, 16)); + } catch (NumberFormatException ignored) { + throw new IllegalArgumentException("Invalid UUID string: " + value); + } + } +} diff --git a/hugegraph-struct/src/main/java/org/apache/hugegraph/util/collection/CollectionFactory.java b/hugegraph-struct/src/main/java/org/apache/hugegraph/util/collection/CollectionFactory.java new file mode 100644 index 0000000000..fb42b8416e --- /dev/null +++ b/hugegraph-struct/src/main/java/org/apache/hugegraph/util/collection/CollectionFactory.java @@ -0,0 +1,264 @@ +/* + * Copyright 2017 HugeGraph Authors + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.util.collection; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.apache.hugegraph.util.E; +import org.eclipse.collections.api.map.primitive.IntObjectMap; +import org.eclipse.collections.api.map.primitive.MutableIntObjectMap; +import org.eclipse.collections.impl.list.mutable.FastList; +import org.eclipse.collections.impl.map.mutable.UnifiedMap; +import org.eclipse.collections.impl.map.mutable.primitive.IntObjectHashMap; +import org.eclipse.collections.impl.set.mutable.UnifiedSet; + +import org.apache.hugegraph.id.Id; +import org.apache.hugegraph.type.define.CollectionType; + +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; + +public class CollectionFactory { + + private final CollectionType type; + + public CollectionFactory() { + this.type = CollectionType.EC; + } + + public CollectionFactory(CollectionType type) { + this.type = type; + } + + public List newList() { + return newList(this.type); + } + + public List newList(int initialCapacity) { + return newList(this.type, initialCapacity); + } + + public List newList(Collection collection) { + return newList(this.type, collection); + } + + public static List newList(CollectionType type) { + switch (type) { + case EC: + return new FastList<>(); + case JCF: + return new ArrayList<>(); + case FU: + return new ObjectArrayList<>(); + default: + throw new AssertionError( + "Unsupported collection type: " + type); + } + } + + public static List newList(CollectionType type, + int initialCapacity) { + switch (type) { + case EC: + return new FastList<>(initialCapacity); + case JCF: + return new ArrayList<>(initialCapacity); + case FU: + return new ObjectArrayList<>(initialCapacity); + default: + throw new AssertionError( + "Unsupported collection type: " + type); + } + } + + public static List newList(CollectionType type, + Collection collection) { + switch (type) { + case EC: + return new FastList<>(collection); + case JCF: + return new ArrayList<>(collection); + case FU: + return new ObjectArrayList<>(collection); + default: + throw new AssertionError( + "Unsupported collection type: " + type); + } + } + + public Set newSet() { + return newSet(this.type); + } + + public Set newSet(int initialCapacity) { + return newSet(this.type, initialCapacity); + } + + public Set newSet(Collection collection) { + return newSet(this.type, collection); + } + + public static Set newSet(CollectionType type) { + switch (type) { + case EC: + return new UnifiedSet<>(); + case JCF: + return new HashSet<>(); + case FU: + return new ObjectOpenHashSet<>(); + default: + throw new AssertionError( + "Unsupported collection type: " + type); + } + } + + public static Set newSet(CollectionType type, + int initialCapacity) { + switch (type) { + case EC: + return new UnifiedSet<>(initialCapacity); + case JCF: + return new HashSet<>(initialCapacity); + case FU: + return new ObjectOpenHashSet<>(initialCapacity); + default: + throw new AssertionError( + "Unsupported collection type: " + type); + } + } + + public static Set newSet(CollectionType type, + Collection collection) { + switch (type) { + case EC: + return new UnifiedSet<>(collection); + case JCF: + return new HashSet<>(collection); + case FU: + return new ObjectOpenHashSet<>(collection); + default: + throw new AssertionError( + "Unsupported collection type: " + type); + } + } + + public Map newMap() { + return newMap(this.type); + } + + public Map newMap(int initialCapacity) { + return newMap(this.type, initialCapacity); + } + + public Map newMap(Map map) { + return newMap(this.type, map); + } + + public static Map newMap(CollectionType type) { + /* + * EC is faster 10%-20% than JCF, and it's more stable & less + * memory cost(size is bigger, EC is better). + */ + switch (type) { + case EC: + return new UnifiedMap<>(); + case JCF: + return new HashMap<>(); + case FU: + return new Object2ObjectOpenHashMap<>(); + default: + throw new AssertionError( + "Unsupported collection type: " + type); + } + } + + public static Map newMap(CollectionType type, + int initialCapacity) { + switch (type) { + case EC: + return new UnifiedMap<>(initialCapacity); + case JCF: + return new HashMap<>(initialCapacity); + case FU: + return new Object2ObjectOpenHashMap<>(initialCapacity); + default: + throw new AssertionError( + "Unsupported collection type: " + type); + } + } + + public static Map newMap(CollectionType type, + Map map) { + switch (type) { + case EC: + return new UnifiedMap<>(map); + case JCF: + return new HashMap<>(map); + case FU: + return new Object2ObjectOpenHashMap<>(map); + default: + throw new AssertionError( + "Unsupported collection type: " + type); + } + } + + public static MutableIntObjectMap newIntObjectMap() { + return new IntObjectHashMap<>(); + } + + public static MutableIntObjectMap newIntObjectMap(int initialCapacity) { + return new IntObjectHashMap<>(initialCapacity); + } + + public static MutableIntObjectMap newIntObjectMap( + IntObjectMap map) { + return new IntObjectHashMap<>(map); + } + + @SuppressWarnings("unchecked") + public static MutableIntObjectMap newIntObjectMap( + Object... objects) { + IntObjectHashMap map = IntObjectHashMap.newMap(); + E.checkArgument(objects.length % 2 == 0, + "Must provide even arguments for " + + "CollectionFactory.newIntObjectMap"); + for (int i = 0; i < objects.length; i += 2) { + int key = objects[i] instanceof Id ? + (int) ((Id) objects[i]).asLong() : (int) objects[i]; + map.put(key, (V) objects[i + 1]); + } + return map; + } + + public IdSet newIdSet() { + return newIdSet(this.type); + } + + public static IdSet newIdSet(CollectionType type) { + return new IdSet(type); + } +} diff --git a/hugegraph-struct/src/main/java/org/apache/hugegraph/util/collection/IdSet.java b/hugegraph-struct/src/main/java/org/apache/hugegraph/util/collection/IdSet.java new file mode 100644 index 0000000000..d77ddfb047 --- /dev/null +++ b/hugegraph-struct/src/main/java/org/apache/hugegraph/util/collection/IdSet.java @@ -0,0 +1,120 @@ +/* + * Copyright 2017 HugeGraph Authors + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.util.collection; + +import org.apache.hugegraph.id.Id; +import org.apache.hugegraph.id.IdGenerator; +import org.apache.hugegraph.type.define.CollectionType; + +import org.apache.hugegraph.iterator.ExtendableIterator; +import org.eclipse.collections.api.iterator.MutableLongIterator; +import org.eclipse.collections.impl.set.mutable.primitive.LongHashSet; + +import java.util.AbstractSet; +import java.util.Iterator; +import java.util.Set; + +public class IdSet extends AbstractSet { + + private final LongHashSet numberIds; + private final Set nonNumberIds; + + public IdSet(CollectionType type) { + this.numberIds = new LongHashSet(); + this.nonNumberIds = CollectionFactory.newSet(type); + } + + @Override + public int size() { + return this.numberIds.size() + this.nonNumberIds.size(); + } + + @Override + public boolean isEmpty() { + return this.numberIds.isEmpty() && this.nonNumberIds.isEmpty(); + } + + @Override + public boolean contains(Object object) { + if (!(object instanceof Id)) { + return false; + } + Id id = (Id) object; + if (id.type() == Id.IdType.LONG) { + return this.numberIds.contains(id.asLong()); + } else { + return this.nonNumberIds.contains(id); + } + } + + @Override + public Iterator iterator() { + return new ExtendableIterator<>( + this.nonNumberIds.iterator(), + new EcIdIterator(this.numberIds.longIterator())); + } + + @Override + public boolean add(Id id) { + if (id.type() == Id.IdType.LONG) { + return this.numberIds.add(id.asLong()); + } else { + return this.nonNumberIds.add(id); + } + } + + public boolean remove(Id id) { + if (id.type() == Id.IdType.LONG) { + return this.numberIds.remove(id.asLong()); + } else { + return this.nonNumberIds.remove(id); + } + } + + @Override + public void clear() { + this.numberIds.clear(); + this.nonNumberIds.clear(); + } + + private static class EcIdIterator implements Iterator { + + private final MutableLongIterator iterator; + + public EcIdIterator(MutableLongIterator iter) { + this.iterator = iter; + } + + @Override + public boolean hasNext() { + return this.iterator.hasNext(); + } + + @Override + public Id next() { + return IdGenerator.of(this.iterator.next()); + } + + @Override + public void remove() { + this.iterator.remove(); + } + } +} From 7552576f4b657e747ef92ce735d402afda0c83ae Mon Sep 17 00:00:00 2001 From: JisoLya <523420504@qq.com> Date: Sun, 20 Jul 2025 17:28:31 +0800 Subject: [PATCH 06/35] refactor hg-pd-grpc module --- .../proto/{pd_common.proto => common.proto} | 6 + .../hg-pd-grpc/src/main/proto/kv.proto | 1 - .../hg-pd-grpc/src/main/proto/meta.proto | 71 +++++++ .../hg-pd-grpc/src/main/proto/metaTask.proto | 4 +- .../hg-pd-grpc/src/main/proto/metapb.proto | 19 ++ .../hg-pd-grpc/src/main/proto/pd_pulse.proto | 172 ----------------- .../hg-pd-grpc/src/main/proto/pd_watch.proto | 102 ---------- .../hg-pd-grpc/src/main/proto/pdpb.proto | 42 +++++ .../hg-pd-grpc/src/main/proto/pulse.proto | 174 ++++++++++++++++++ 9 files changed, 315 insertions(+), 276 deletions(-) rename hugegraph-pd/hg-pd-grpc/src/main/proto/{pd_common.proto => common.proto} (95%) create mode 100644 hugegraph-pd/hg-pd-grpc/src/main/proto/meta.proto delete mode 100644 hugegraph-pd/hg-pd-grpc/src/main/proto/pd_pulse.proto delete mode 100644 hugegraph-pd/hg-pd-grpc/src/main/proto/pd_watch.proto create mode 100644 hugegraph-pd/hg-pd-grpc/src/main/proto/pulse.proto diff --git a/hugegraph-pd/hg-pd-grpc/src/main/proto/pd_common.proto b/hugegraph-pd/hg-pd-grpc/src/main/proto/common.proto similarity index 95% rename from hugegraph-pd/hg-pd-grpc/src/main/proto/pd_common.proto rename to hugegraph-pd/hg-pd-grpc/src/main/proto/common.proto index c2b55c2787..b9361065b8 100644 --- a/hugegraph-pd/hg-pd-grpc/src/main/proto/pd_common.proto +++ b/hugegraph-pd/hg-pd-grpc/src/main/proto/common.proto @@ -22,7 +22,9 @@ option java_package = "org.apache.hugegraph.pd.grpc.common"; option java_outer_classname = "HgPdCommonProto"; message RequestHeader { + // cluster ID uint64 cluster_id = 1; + // sender ID uint64 sender_id = 2; } @@ -49,3 +51,7 @@ message Error { ErrorType type = 1; string message = 2; } + +message NoArg{ + RequestHeader header = 1; +} diff --git a/hugegraph-pd/hg-pd-grpc/src/main/proto/kv.proto b/hugegraph-pd/hg-pd-grpc/src/main/proto/kv.proto index 22007cda31..80faebe6e6 100644 --- a/hugegraph-pd/hg-pd-grpc/src/main/proto/kv.proto +++ b/hugegraph-pd/hg-pd-grpc/src/main/proto/kv.proto @@ -18,7 +18,6 @@ syntax = "proto3"; package kv; import "pdpb.proto"; -import "metapb.proto"; option java_package = "org.apache.hugegraph.pd.grpc.kv"; option java_multiple_files = true; diff --git a/hugegraph-pd/hg-pd-grpc/src/main/proto/meta.proto b/hugegraph-pd/hg-pd-grpc/src/main/proto/meta.proto new file mode 100644 index 0000000000..12d91b56d9 --- /dev/null +++ b/hugegraph-pd/hg-pd-grpc/src/main/proto/meta.proto @@ -0,0 +1,71 @@ +/* + * 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. + */ + +syntax = "proto3"; +package meta; +option java_package = "org.apache.hugegraph.pd.grpc"; +import "google/protobuf/any.proto"; +import "metapb.proto"; +import "common.proto"; +import "pdpb.proto"; +option java_multiple_files = true; + +service MetaService{ + rpc getStores(NoArg) returns(Stores); + rpc getPartitions(NoArg) returns(Partitions); + rpc getShardGroups(NoArg) returns(ShardGroups); + rpc getGraphSpaces(NoArg) returns(GraphSpaces); + rpc getGraphs(NoArg) returns(Graphs); + rpc updateStore(metapb.Store) returns(VoidResponse); + rpc updatePartition(metapb.Partition) returns(VoidResponse); + rpc updateShardGroup(metapb.ShardGroup) returns(VoidResponse); + rpc updateGraphSpace(metapb.GraphSpace) returns(VoidResponse); + rpc updateGraph(metapb.Graph) returns(VoidResponse); +} +message Stores{ + pdpb.ResponseHeader header = 1; + repeated metapb.Store data = 2; +} +message Partitions{ + pdpb.ResponseHeader header = 1; + repeated metapb.Partition data = 2; +} +message ShardGroups{ + pdpb.ResponseHeader header = 1; + repeated metapb.ShardGroup data = 2; +} +message Shards{ + pdpb.ResponseHeader header = 1; + repeated metapb.Shard data = 2; +} +message GraphSpaces{ + pdpb.ResponseHeader header = 1; + repeated metapb.GraphSpace data = 2; +} +message Graphs{ + pdpb.ResponseHeader header = 1; + repeated metapb.Graph data = 2; +} + +message DefaultResponse{ + pdpb.ResponseHeader header = 1; + repeated google.protobuf.Any data = 2; +} + +message VoidResponse{ + pdpb.ResponseHeader header = 1; +} diff --git a/hugegraph-pd/hg-pd-grpc/src/main/proto/metaTask.proto b/hugegraph-pd/hg-pd-grpc/src/main/proto/metaTask.proto index 65ab26a688..aaf2a4e2d5 100644 --- a/hugegraph-pd/hg-pd-grpc/src/main/proto/metaTask.proto +++ b/hugegraph-pd/hg-pd-grpc/src/main/proto/metaTask.proto @@ -18,7 +18,7 @@ syntax = "proto3"; package metaTask; import "metapb.proto"; -import "pd_pulse.proto"; +import "pulse.proto"; option java_package = "org.apache.hugegraph.pd.grpc"; enum TaskType { @@ -28,6 +28,7 @@ enum TaskType { Move_Partition = 3; Clean_Partition = 4; Change_KeyRange = 5; + Build_Index = 6; } message Task { @@ -43,6 +44,7 @@ message Task { MovePartition movePartition = 11; CleanPartition cleanPartition = 12; PartitionKeyRange partitionKeyRange = 13; + metapb.BuildIndex buildIndex = 14; } enum TaskState{ diff --git a/hugegraph-pd/hg-pd-grpc/src/main/proto/metapb.proto b/hugegraph-pd/hg-pd-grpc/src/main/proto/metapb.proto index 2d361de662..d1840dfb43 100644 --- a/hugegraph-pd/hg-pd-grpc/src/main/proto/metapb.proto +++ b/hugegraph-pd/hg-pd-grpc/src/main/proto/metapb.proto @@ -390,3 +390,22 @@ enum GraphModeReason{ Initiative = 1; // Active status settings Quota = 2; // The limit condition is reached } + +message BuildIndex { + uint64 taskId = 1; + uint32 partition_id = 2; + BuildIndexParam param = 11; +} + +message BuildIndexParam { + string graph = 1; + bytes label_id = 2; + bool is_vertex_label = 3; + bytes prefix = 4; // query prefix + + oneof request_param_union { + bytes index_label = 11; // label id + bool all_index = 12; // rebuild all index + bool label_index = 13; // ?? + } +} diff --git a/hugegraph-pd/hg-pd-grpc/src/main/proto/pd_pulse.proto b/hugegraph-pd/hg-pd-grpc/src/main/proto/pd_pulse.proto deleted file mode 100644 index afb6d6287d..0000000000 --- a/hugegraph-pd/hg-pd-grpc/src/main/proto/pd_pulse.proto +++ /dev/null @@ -1,172 +0,0 @@ -/* - * 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. - */ - -syntax = "proto3"; - -import "metapb.proto"; -import "pd_common.proto"; - -option java_multiple_files = true; -option java_package = "org.apache.hugegraph.pd.grpc.pulse"; -option java_outer_classname = "HgPdPulseProto"; - -service HgPdPulse { - rpc Pulse(stream PulseRequest) returns (stream PulseResponse); -} - -/* requests */ -message PulseRequest { - PulseCreateRequest create_request = 1; - PulseCancelRequest cancel_request = 2; - PulseNoticeRequest notice_request = 3; - PulseAckRequest ack_request = 4; -} - -message PulseCreateRequest { - PulseType pulse_type = 1; -} - -message PulseCancelRequest { - int64 observer_id = 1; -} - -message PulseNoticeRequest { - int64 observer_id = 1; - oneof request_union { - PartitionHeartbeatRequest partition_heartbeat_request = 10; - } -} - -message PulseAckRequest { - int64 observer_id = 1; - int64 notice_id = 2; -} - -// When an event such as a partition heartbeat occurs such as the increase or decrease of peers in a partition or the change of leader, the leader sends a heartbeat. -// At the same time, the pd adds or decreases shards to the partition and sends the response to the leader -message PartitionHeartbeatRequest { - RequestHeader header = 1; - // Leader Peer sending the heartbeat - metapb.PartitionStats states = 4; -} - -/* responses */ -message PulseResponse { - PulseType pulse_type = 1; - int64 observer_id = 2; - int32 status = 3; //0=ok,1=fail - int64 notice_id = 4; - oneof response_union { - PartitionHeartbeatResponse partition_heartbeat_response = 10; - PdInstructionResponse instruction_response = 11; - } -} - -message PartitionHeartbeatResponse { - ResponseHeader header = 1; - uint64 id = 3; - metapb.Partition partition = 2; - ChangeShard change_shard = 4; - - TransferLeader transfer_leader = 5; - // Split into multiple partitions, with the first SplitPartition being the original partition and the second starting being the new partition - SplitPartition split_partition = 6; - // rocksdb compaction specifies the table, null is for all - DbCompaction db_compaction = 7; - // Migrate data from the partition to the target - MovePartition move_partition = 8; - // Clean up the data for the partition of the graph - CleanPartition clean_partition = 9; - // partition key range variation - PartitionKeyRange key_range = 10; -} - -/* Date model */ -message ChangeShard { - repeated metapb.Shard shard = 1; - ConfChangeType change_type = 2; -} - -message TransferLeader { - metapb.Shard shard = 1; -} - -message SplitPartition { - repeated metapb.Partition new_partition = 1; -} - -message DbCompaction { - string table_name = 3; -} - -message MovePartition { - // The new range after migration - metapb.Partition target_partition = 1; - // partition's key start and key end, - // will migrate to target partition - uint64 key_start = 2; - uint64 key_end = 3; -} - -message CleanPartition { - uint64 key_start = 1; - uint64 key_end = 2; - CleanType clean_type = 3; - bool delete_partition = 4; // Whether to delete the partition -} - -message PartitionKeyRange{ - uint32 partition_id = 1; - uint64 key_start = 2; - uint64 key_end = 3; -} - -message PdInstructionResponse { - PdInstructionType instruction_type = 1; - string leader_ip = 2; -} - -/* enums */ -enum PulseType { - PULSE_TYPE_UNKNOWN = 0; - PULSE_TYPE_PARTITION_HEARTBEAT = 1; - PULSE_TYPE_PD_INSTRUCTION = 2; -} - -enum PulseChangeType { - PULSE_CHANGE_TYPE_UNKNOWN = 0; - PULSE_CHANGE_TYPE_ADD = 1; - PULSE_CHANGE_TYPE_ALTER = 2; - PULSE_CHANGE_TYPE_DEL = 3; -} - -enum ConfChangeType { - CONF_CHANGE_TYPE_UNKNOWN = 0; - CONF_CHANGE_TYPE_ADD_NODE = 1; - CONF_CHANGE_TYPE_REMOVE_NODE = 2; - CONF_CHANGE_TYPE_ADD_LEARNER_NODE = 3; - CONF_CHANGE_TYPE_ADJUST = 4; // Adjust the shard, and the leader dynamically increases or decreases according to the new configuration. -} - -enum CleanType { - CLEAN_TYPE_KEEP_RANGE = 0; // Only this range remains - CLEAN_TYPE_EXCLUDE_RANGE = 1; // Delete this range -} - -enum PdInstructionType { - CHANGE_TO_FOLLOWER = 0; -} diff --git a/hugegraph-pd/hg-pd-grpc/src/main/proto/pd_watch.proto b/hugegraph-pd/hg-pd-grpc/src/main/proto/pd_watch.proto deleted file mode 100644 index 6d0c016c2c..0000000000 --- a/hugegraph-pd/hg-pd-grpc/src/main/proto/pd_watch.proto +++ /dev/null @@ -1,102 +0,0 @@ -/* - * 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. - */ - -syntax = "proto3"; - -import "metapb.proto"; - -option java_multiple_files = true; -option java_package = "org.apache.hugegraph.pd.grpc.watch"; -option java_outer_classname = "HgPdWatchProto"; - -service HgPdWatch { - rpc Watch(stream WatchRequest) returns (stream WatchResponse); -} - -message WatchRequest { - WatchCreateRequest create_request = 1; - WatchCancelRequest cancel_request = 2; -} - -message WatchCreateRequest { - WatchType watch_type = 1; -} - -message WatchCancelRequest { - int64 watcher_id = 1; -} - -message WatchResponse { - WatchType watch_type = 1; - int64 watcher_id = 2; - int32 status = 3; //0=ok,1=fail - int64 notice_id = 4; - string msg = 5; - oneof response_union { - WatchPartitionResponse partition_response = 10; - WatchNodeResponse node_response = 11; - WatchGraphResponse graph_response = 12; - WatchShardGroupResponse shard_group_response = 13; - } -} - -message WatchPartitionResponse { - string graph = 1; - int32 partition_id = 2; - WatchChangeType change_type = 3; -} - -message WatchNodeResponse { - string graph = 1; - uint64 node_id = 2; - NodeEventType node_event_type = 3; -} - -message WatchGraphResponse { - metapb.Graph graph = 1; - WatchType type = 2; -} - -message WatchShardGroupResponse { - metapb.ShardGroup shard_group = 1; - WatchChangeType type = 2; - int32 shard_group_id = 3; -} - -enum WatchType { - WATCH_TYPE_UNKNOWN = 0; - WATCH_TYPE_PARTITION_CHANGE = 1; - WATCH_TYPE_STORE_NODE_CHANGE = 2; - WATCH_TYPE_GRAPH_CHANGE = 3; - WATCH_TYPE_SHARD_GROUP_CHANGE = 4; -} - -enum WatchChangeType { - WATCH_CHANGE_TYPE_UNKNOWN = 0; - WATCH_CHANGE_TYPE_ADD = 1; - WATCH_CHANGE_TYPE_ALTER = 2; - WATCH_CHANGE_TYPE_DEL = 3; - WATCH_CHANGE_TYPE_SPECIAL1 = 4; -} - -enum NodeEventType { - NODE_EVENT_TYPE_UNKNOWN = 0; - NODE_EVENT_TYPE_NODE_ONLINE = 1; - NODE_EVENT_TYPE_NODE_OFFLINE = 2; - NODE_EVENT_TYPE_NODE_RAFT_CHANGE = 3; - NODE_EVENT_TYPE_PD_LEADER_CHANGE = 4; -} diff --git a/hugegraph-pd/hg-pd-grpc/src/main/proto/pdpb.proto b/hugegraph-pd/hg-pd-grpc/src/main/proto/pdpb.proto index f7754824ec..d03fd08355 100644 --- a/hugegraph-pd/hg-pd-grpc/src/main/proto/pdpb.proto +++ b/hugegraph-pd/hg-pd-grpc/src/main/proto/pdpb.proto @@ -105,6 +105,15 @@ service PD { rpc getCache(GetGraphRequest) returns (CacheResponse) {} rpc getPartitions(GetGraphRequest) returns (CachePartitionResponse) {} + + // submit rebuild index task + rpc submitTask(IndexTaskCreateRequest) returns (IndexTaskCreateResponse) {} + // query task state + rpc queryTaskState(IndexTaskQueryRequest) returns (IndexTaskQueryResponse) {} + // retry index task + rpc retryIndexTask(IndexTaskQueryRequest) returns (IndexTaskQueryResponse){} + rpc getGraphStats(GetGraphRequest) returns (GraphStatsResponse) {} + rpc GetMembersAndClusterState(GetMembersRequest) returns (MembersAndClusterState) {} } message RequestHeader { @@ -372,6 +381,13 @@ message GetMembersResponse{ metapb.Member leader = 3; } +message MembersAndClusterState{ + ResponseHeader header = 1; + repeated metapb.Member members = 2; + metapb.Member leader = 3; + metapb.ClusterState state = 4; +} + message GetPDConfigRequest{ RequestHeader header = 1; uint64 version = 2 ; @@ -602,3 +618,29 @@ message CachePartitionResponse { ResponseHeader header = 1; repeated metapb.Partition partitions = 2; } + +message IndexTaskCreateRequest { + RequestHeader header = 1; + metapb.BuildIndexParam param = 2; +} + +message IndexTaskCreateResponse { + ResponseHeader header = 1; + uint64 task_id = 2; +} + +message IndexTaskQueryRequest { + RequestHeader header = 1; + uint64 task_id = 2; +} + +message IndexTaskQueryResponse{ + ResponseHeader header = 1; + metaTask.TaskState state = 2; + string message = 3; +} + +message GraphStatsResponse { + ResponseHeader header = 1; + metapb.GraphStats stats = 2; +} diff --git a/hugegraph-pd/hg-pd-grpc/src/main/proto/pulse.proto b/hugegraph-pd/hg-pd-grpc/src/main/proto/pulse.proto new file mode 100644 index 0000000000..05a50445ed --- /dev/null +++ b/hugegraph-pd/hg-pd-grpc/src/main/proto/pulse.proto @@ -0,0 +1,174 @@ +/* + * 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. + */ + +syntax = "proto3"; + +import "metapb.proto"; +import "common.proto"; + +option java_multiple_files = true; +option java_package = "org.apache.hugegraph.pd.grpc.pulse"; +option java_outer_classname = "HgPdPulseProto"; + +service HgPdPulse { + rpc Pulse(stream PulseRequest) returns (stream PulseResponse); +} + +/* requests */ +message PulseRequest { + PulseCreateRequest create_request = 1; + PulseCancelRequest cancel_request = 2; + PulseNoticeRequest notice_request = 3; + PulseAckRequest ack_request = 4; +} + +message PulseCreateRequest { + PulseType pulse_type = 1; +} + +message PulseCancelRequest { + int64 observer_id = 1; +} + +message PulseNoticeRequest { + int64 observer_id = 1; + oneof request_union { + PartitionHeartbeatRequest partition_heartbeat_request = 10; + } +} + +message PulseAckRequest { + int64 observer_id = 1; + int64 notice_id = 2; +} + +// 分区心跳,分区的peer增减、leader改变等事件发生时,由leader发送心跳。 +// 同时pd对分区进行shard增减通过Response发送给leader +message PartitionHeartbeatRequest { + RequestHeader header = 1; + // Leader Peer sending the heartbeat + metapb.PartitionStats states = 4; +} + +/* responses */ +message PulseResponse { + PulseType pulse_type = 1; + int64 observer_id = 2; + int32 status = 3; //0=ok,1=fail + int64 notice_id=4; + oneof response_union { + PartitionHeartbeatResponse partition_heartbeat_response = 10; + PdInstructionResponse instruction_response = 11; + } +} + +message PartitionHeartbeatResponse { + ResponseHeader header = 1; + uint64 id = 3; + metapb.Partition partition = 2; + ChangeShard change_shard = 4; + + TransferLeader transfer_leader = 5; + // 拆分成多个分区,第一个SplitPartition是原分区,从第二开始是新分区 + SplitPartition split_partition = 6; + // rocksdb compaction 指定的表,null是针对所有 + DbCompaction db_compaction = 7; + // 将partition的数据,迁移到 target + MovePartition move_partition = 8; + // 清理partition的graph的数据 + CleanPartition clean_partition = 9; + // partition key range 变化 + PartitionKeyRange key_range = 10; + // 创建索引的任务 + metapb.BuildIndex build_index = 11; +} + +/* Date model */ +message ChangeShard { + repeated metapb.Shard shard = 1; + ConfChangeType change_type = 2; +} + +message TransferLeader { + metapb.Shard shard = 1; +} + +message SplitPartition { + repeated metapb.Partition new_partition = 1; +} + +message DbCompaction { + string table_name = 3; +} + +message MovePartition{ + // target partition的key range为,迁移后的新range + metapb.Partition target_partition = 1; + // partition 的 key start 和 key end的所有数据, + // 会迁移到 target partition 上 + uint64 key_start = 2; + uint64 key_end = 3; +} + +message CleanPartition { + uint64 key_start = 1; + uint64 key_end = 2; + CleanType clean_type = 3; + bool delete_partition = 4; //是否删除分区 +} + +message PartitionKeyRange{ + uint32 partition_id = 1; + uint64 key_start = 2; + uint64 key_end = 3; +} + +message PdInstructionResponse { + PdInstructionType instruction_type = 1; + string leader_ip = 2; +} + +/* enums */ +enum PulseType { + PULSE_TYPE_UNKNOWN = 0; + PULSE_TYPE_PARTITION_HEARTBEAT = 1; + PULSE_TYPE_PD_INSTRUCTION = 2; +} + +enum PulseChangeType { + PULSE_CHANGE_TYPE_UNKNOWN = 0; + PULSE_CHANGE_TYPE_ADD = 1; + PULSE_CHANGE_TYPE_ALTER = 2; + PULSE_CHANGE_TYPE_DEL = 3; +} + +enum ConfChangeType { + CONF_CHANGE_TYPE_UNKNOWN = 0; + CONF_CHANGE_TYPE_ADD_NODE = 1; + CONF_CHANGE_TYPE_REMOVE_NODE = 2; + CONF_CHANGE_TYPE_ADD_LEARNER_NODE = 3; + CONF_CHANGE_TYPE_ADJUST = 4; // 调整shard,leader根据新的配置动态增减。 +} + +enum CleanType { + CLEAN_TYPE_KEEP_RANGE = 0; // 仅保留这个range + CLEAN_TYPE_EXCLUDE_RANGE = 1; // 删除这个range +} + +enum PdInstructionType { + CHANGE_TO_FOLLOWER = 0; +} From 0c9e10de683752db46a8d800f2f4f18591612a46 Mon Sep 17 00:00:00 2001 From: JisoLya <523420504@qq.com> Date: Sat, 26 Jul 2025 16:15:43 +0800 Subject: [PATCH 07/35] fix: add missing method --- .../rocksdb/access/RocksDBSession.java | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/hugegraph-store/hg-store-rocksdb/src/main/java/org/apache/hugegraph/rocksdb/access/RocksDBSession.java b/hugegraph-store/hg-store-rocksdb/src/main/java/org/apache/hugegraph/rocksdb/access/RocksDBSession.java index 9c3005da66..a8827abfc0 100644 --- a/hugegraph-store/hg-store-rocksdb/src/main/java/org/apache/hugegraph/rocksdb/access/RocksDBSession.java +++ b/hugegraph-store/hg-store-rocksdb/src/main/java/org/apache/hugegraph/rocksdb/access/RocksDBSession.java @@ -866,6 +866,42 @@ public long getApproximateDataSize(byte[] start, byte[] end) { } } + /** + * 根据表名获取 size + * @param table table + * @param start key start + * @param end key end + * @return size + */ + public long getApproximateDataSize(String table, byte[] start, byte[] end) { + cfHandleLock.readLock().lock(); + try { + if (this.tables.containsKey(table)) { + return 0; + } + + long kbSize = 0; + long bytesSize = 0; + Range r1 = new Range(new Slice(start), new Slice(end)); + + var h = this.tables.get(table); + long[] sizes = + this.rocksDB.getApproximateSizes( + h, Arrays.asList(r1), SizeApproximationFlag.INCLUDE_FILES, SizeApproximationFlag.INCLUDE_MEMTABLES); + + bytesSize += sizes[0]; + kbSize += bytesSize / 1024; + bytesSize = bytesSize % 1024; + + if (bytesSize != 0) { + kbSize += 1; + } + return kbSize; + } finally { + cfHandleLock.readLock().unlock(); + } + } + public Map getApproximateCFDataSize(byte[] start, byte[] end) { Map map = new ConcurrentHashMap<>(this.tables.size()); cfHandleLock.readLock().lock(); From 2eb063d2a54969c6080dbe1f2c40474d041b1023 Mon Sep 17 00:00:00 2001 From: JisoLya <523420504@qq.com> Date: Mon, 28 Jul 2025 23:15:19 +0800 Subject: [PATCH 08/35] fix: add missing proto file --- .../hg-pd-grpc/src/main/proto/watch.proto | 103 ++++++++++++++++++ 1 file changed, 103 insertions(+) create mode 100644 hugegraph-pd/hg-pd-grpc/src/main/proto/watch.proto diff --git a/hugegraph-pd/hg-pd-grpc/src/main/proto/watch.proto b/hugegraph-pd/hg-pd-grpc/src/main/proto/watch.proto new file mode 100644 index 0000000000..c9063f30d9 --- /dev/null +++ b/hugegraph-pd/hg-pd-grpc/src/main/proto/watch.proto @@ -0,0 +1,103 @@ +/* + * 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. + */ + +syntax = "proto3"; + +import "metapb.proto"; + +option java_multiple_files = true; +option java_package = "org.apache.hugegraph.pd.grpc.watch"; +option java_outer_classname = "HgPdWatchProto"; + +service HgPdWatch { + rpc Watch(stream WatchRequest) returns (stream WatchResponse); +} + +message WatchRequest { + WatchCreateRequest create_request = 1; + WatchCancelRequest cancel_request = 2; +} + +message WatchCreateRequest { + WatchType watch_type = 1; +} + +message WatchCancelRequest { + int64 watcher_id = 1; +} + +message WatchResponse { + WatchType watch_type = 1; + int64 watcher_id = 2; + int32 status = 3; //0=ok,1=fail + int64 notice_id = 4; + string msg = 5; + oneof response_union { + WatchPartitionResponse partition_response = 10; + WatchNodeResponse node_response = 11; + WatchGraphResponse graph_response = 12; + WatchShardGroupResponse shard_group_response = 13; + } +} + +message WatchPartitionResponse { + string graph = 1; + int32 partition_id = 2; + WatchChangeType change_type = 3; +} + +message WatchNodeResponse { + string graph = 1; + uint64 node_id = 2; + NodeEventType node_event_type = 3; +} + +message WatchGraphResponse { + metapb.Graph graph = 1; + WatchType type = 2; +} + +message WatchShardGroupResponse { + metapb.ShardGroup shard_group = 1; + WatchChangeType type = 2; + int32 shard_group_id = 3; +} + +enum WatchType { + WATCH_TYPE_UNKNOWN = 0; + WATCH_TYPE_PARTITION_CHANGE = 1; + WATCH_TYPE_STORE_NODE_CHANGE = 2; + WATCH_TYPE_GRAPH_CHANGE = 3; + WATCH_TYPE_SHARD_GROUP_CHANGE = 4; +} + +enum WatchChangeType { + WATCH_CHANGE_TYPE_UNKNOWN = 0; + WATCH_CHANGE_TYPE_ADD = 1; + WATCH_CHANGE_TYPE_ALTER = 2; + WATCH_CHANGE_TYPE_DEL = 3; + WATCH_CHANGE_TYPE_SPECIAL1 = 4; +} + +enum NodeEventType { + NODE_EVENT_TYPE_UNKNOWN = 0; + NODE_EVENT_TYPE_NODE_ONLINE = 1; + NODE_EVENT_TYPE_NODE_OFFLINE = 2; + NODE_EVENT_TYPE_NODE_RAFT_CHANGE = 3; + // pd leader 变更 + NODE_EVENT_TYPE_PD_LEADER_CHANGE = 4; +} From cdbc74cfa6d7aea5fbc977ae7743e155d4b54a8f Mon Sep 17 00:00:00 2001 From: JisoLya <523420504@qq.com> Date: Wed, 30 Jul 2025 14:41:17 +0800 Subject: [PATCH 09/35] fix: add missing proto file --- hugegraph-pd/hg-pd-grpc/src/main/proto/metapb.proto | 1 + 1 file changed, 1 insertion(+) diff --git a/hugegraph-pd/hg-pd-grpc/src/main/proto/metapb.proto b/hugegraph-pd/hg-pd-grpc/src/main/proto/metapb.proto index d1840dfb43..ae4d335a7a 100644 --- a/hugegraph-pd/hg-pd-grpc/src/main/proto/metapb.proto +++ b/hugegraph-pd/hg-pd-grpc/src/main/proto/metapb.proto @@ -306,6 +306,7 @@ message StoreStats { int32 cores = 24; // system metrics repeated RecordPair system_metrics = 25; + bool executing_task = 26; } // Partition query criteria From 13b04ee8cee3b325c88a3865e1cd22f2b2fede63 Mon Sep 17 00:00:00 2001 From: JisoLya <523420504@qq.com> Date: Wed, 30 Jul 2025 14:41:41 +0800 Subject: [PATCH 10/35] fix: update maven dependency --- pom.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/pom.xml b/pom.xml index 1fa07660ee..dc10c63092 100644 --- a/pom.xml +++ b/pom.xml @@ -104,6 +104,7 @@ hugegraph-commons install-dist hugegraph-cluster-test + hugegraph-struct From 751ccd90365454eb84cd599a7788755a9103258f Mon Sep 17 00:00:00 2001 From: JisoLya <523420504@qq.com> Date: Wed, 30 Jul 2025 14:43:01 +0800 Subject: [PATCH 11/35] fix: update store module maven dependency --- hugegraph-store/hg-store-core/pom.xml | 4 ++++ hugegraph-store/pom.xml | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/hugegraph-store/hg-store-core/pom.xml b/hugegraph-store/hg-store-core/pom.xml index 17a53380f9..01aff3f932 100644 --- a/hugegraph-store/hg-store-core/pom.xml +++ b/hugegraph-store/hg-store-core/pom.xml @@ -168,6 +168,10 @@ hg-store-client test + + org.apache.hugegraph + hugegraph-struct + diff --git a/hugegraph-store/pom.xml b/hugegraph-store/pom.xml index d25db72216..29c34a8afb 100644 --- a/hugegraph-store/pom.xml +++ b/hugegraph-store/pom.xml @@ -76,7 +76,7 @@ ${project.version} - org.apache + org.apache.hugegraph hugegraph-struct ${project.version} From 4763b298d469f4e196067c35087bc4bc5455c2e2 Mon Sep 17 00:00:00 2001 From: JisoLya <523420504@qq.com> Date: Thu, 31 Jul 2025 13:58:26 +0800 Subject: [PATCH 12/35] fix: add dependency --- hugegraph-store/hg-store-common/pom.xml | 4 ++++ hugegraph-store/hg-store-core/pom.xml | 5 +++++ 2 files changed, 9 insertions(+) diff --git a/hugegraph-store/hg-store-common/pom.xml b/hugegraph-store/hg-store-common/pom.xml index 30435b8fc3..0dc5834c22 100644 --- a/hugegraph-store/hg-store-common/pom.xml +++ b/hugegraph-store/hg-store-common/pom.xml @@ -41,5 +41,9 @@ guava 32.0.1-android + + org.apache.hugegraph + hugegraph-struct + diff --git a/hugegraph-store/hg-store-core/pom.xml b/hugegraph-store/hg-store-core/pom.xml index 01aff3f932..6062c1930a 100644 --- a/hugegraph-store/hg-store-core/pom.xml +++ b/hugegraph-store/hg-store-core/pom.xml @@ -114,6 +114,11 @@ hg-store-common ${revision} + + org.roaringbitmap + RoaringBitmap + 0.9.38 + From 1cc50e049fb118ef3a5409fbfc139a0a99363b57 Mon Sep 17 00:00:00 2001 From: JisoLya <523420504@qq.com> Date: Thu, 31 Jul 2025 14:11:34 +0800 Subject: [PATCH 13/35] update: some method need to be fixed --- .../hugegraph/store/HeartbeatService.java | 153 ++-- .../apache/hugegraph/store/HgStoreEngine.java | 295 ++++--- .../hugegraph/store/HgStoreStateListener.java | 1 + .../hugegraph/store/PartitionEngine.java | 510 ++++++----- .../store/PartitionInstructionProcessor.java | 9 +- .../store/PartitionStateListener.java | 2 +- .../business/AbstractSelectIterator.java | 43 +- .../store/business/BusinessHandler.java | 78 +- .../store/business/BusinessHandlerImpl.java | 818 +++++++++++++++++- .../hugegraph/store/business/DataManager.java | 81 ++ .../store/business/DataManagerImpl.java | 430 +++++++++ .../hugegraph/store/business/DataMover.java | 1 + .../store/business/DefaultDataMover.java | 1 + .../store/business/FilterIterator.java | 40 +- .../store/business/GraphStoreIterator.java | 77 +- .../store/business/InnerKeyCreator.java | 46 +- .../store/business/InnerKeyFilter.java | 8 + .../business/MultiPartitionIterator.java | 13 + .../store/business/SelectIterator.java | 4 +- .../business/itrv2/BatchGetIterator.java | 88 ++ .../business/itrv2/FileObjectIterator.java | 71 ++ .../itrv2/InAccurateIntersectionIterator.java | 103 +++ .../itrv2/InAccurateUnionFilterIterator.java | 100 +++ .../itrv2/IntersectionFilterIterator.java | 243 ++++++ .../business/itrv2/IntersectionWrapper.java | 115 +++ .../store/business/itrv2/MapJoinIterator.java | 122 +++ .../business/itrv2/MapLimitIterator.java | 133 +++ .../business/itrv2/MapUnionIterator.java | 82 ++ .../itrv2/MapValueFilterIterator.java | 57 ++ .../business/itrv2/MultiListIterator.java | 158 ++++ .../business/itrv2/TypeTransIterator.java | 176 ++++ .../business/itrv2/UnionFilterIterator.java | 228 +++++ .../itrv2/io/SortShuffleSerializer.java | 275 ++++++ .../hugegraph/store/cmd/BatchPutRequest.java | 1 + .../hugegraph/store/cmd/BatchPutResponse.java | 1 + .../store/cmd/CleanDataResponse.java | 2 +- .../store/cmd/CreateRaftRequest.java | 1 + .../store/cmd/CreateRaftResponse.java | 1 + .../store/cmd/DbCompactionResponse.java | 1 + .../store/cmd/DestroyRaftResponse.java | 1 + .../store/cmd/FutureClosureAdapter.java | 1 + .../store/cmd/GetStoreInfoResponse.java | 1 + .../apache/hugegraph/store/cmd/HgCmdBase.java | 4 + .../hugegraph/store/cmd/HgCmdClient.java | 17 + .../hugegraph/store/cmd/HgCmdProcessor.java | 72 +- .../store/cmd/UpdatePartitionRequest.java | 1 + .../store/cmd/UpdatePartitionResponse.java | 1 + .../store/cmd/request/BatchPutRequest.java | 54 ++ .../store/cmd/request/BlankTaskRequest.java | 32 + .../cmd/{ => request}/CleanDataRequest.java | 3 +- .../store/cmd/request/CreateRaftRequest.java | 73 ++ .../{ => request}/DbCompactionRequest.java | 5 +- .../cmd/{ => request}/DestroyRaftRequest.java | 6 +- .../{ => request}/GetStoreInfoRequest.java | 4 +- .../cmd/request/RedirectRaftTaskRequest.java | 42 + .../cmd/request/UpdatePartitionRequest.java | 37 + .../store/cmd/response/BatchPutResponse.java | 24 + .../store/cmd/response/CleanDataResponse.java | 24 + .../cmd/response/CreateRaftResponse.java | 24 + .../cmd/response/DbCompactionResponse.java | 24 + .../store/cmd/response/DefaultResponse.java | 28 + .../cmd/response/DestroyRaftResponse.java | 24 + .../cmd/response/GetStoreInfoResponse.java | 45 + .../response/RedirectRaftTaskResponse.java | 24 + .../cmd/response/UpdatePartitionResponse.java | 24 + .../hugegraph/store/consts/PoolNames.java | 35 + .../listener/PartitionChangedListener.java | 34 + .../listener/PartitionStateListener.java | 34 + .../store/listener/StoreStateListener.java | 26 + .../hugegraph/store/meta/GraphIdManager.java | 83 +- .../store/meta/PartitionManager.java | 111 ++- .../hugegraph/store/meta/ShardGroup.java | 10 +- .../hugegraph/store/meta/StoreMetadata.java | 112 ++- .../meta/asynctask/AbstractAsyncTask.java | 1 + .../store/meta/asynctask/CleanTask.java | 2 +- .../store/metric/SystemMetricService.java | 348 ++++---- .../store/options/HgStoreEngineOptions.java | 35 +- .../hugegraph/store/options/JobOptions.java | 33 + .../hugegraph/store/pd/DefaultPdProvider.java | 129 +-- .../store/pd/FakePdServiceProvider.java | 137 +-- .../pd/PartitionInstructionListener.java | 1 + .../apache/hugegraph/store/pd/PdProvider.java | 16 +- .../store/processor/BuildIndexProcessor.java | 74 ++ .../store/processor/ChangeShardProcessor.java | 77 ++ .../processor/CleanPartitionProcessor.java | 79 ++ .../store/processor/CommandProcessor.java | 238 +++++ .../processor/DbCompactionProcessor.java | 74 ++ .../processor/MovePartitionProcessor.java | 85 ++ .../PartitionRangeChangeProcessor.java | 104 +++ .../hugegraph/store/processor/Processors.java | 79 ++ .../processor/SplitPartitionProcessor.java | 86 ++ .../processor/TransferLeaderProcessor.java | 67 ++ .../store/raft/DefaultRaftClosure.java | 52 ++ .../store/raft/PartitionStateMachine.java | 240 +++++ .../hugegraph/store/raft/RaftOperation.java | 3 + .../store/snapshot/HgSnapshotHandler.java | 1 + .../store/snapshot/SnapshotHandler.java | 230 +++++ .../store/util/CopyOnWriteCache.java | 40 +- .../store/util/HgStoreException.java | 37 +- .../apache/hugegraph/store/util/MultiKv.java | 71 ++ .../store/util/PartitionMetaStoreWrapper.java | 4 + .../hugegraph/store/util/SortShuffle.java | 218 +++++ .../apache/hugegraph/store/util/ZipUtils.java | 93 ++ .../rocksdb/access/RocksDBSession.java | 14 +- 104 files changed, 7310 insertions(+), 966 deletions(-) create mode 100644 hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/business/DataManager.java create mode 100644 hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/business/DataManagerImpl.java create mode 100644 hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/business/itrv2/BatchGetIterator.java create mode 100644 hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/business/itrv2/FileObjectIterator.java create mode 100644 hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/business/itrv2/InAccurateIntersectionIterator.java create mode 100644 hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/business/itrv2/InAccurateUnionFilterIterator.java create mode 100644 hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/business/itrv2/IntersectionFilterIterator.java create mode 100644 hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/business/itrv2/IntersectionWrapper.java create mode 100644 hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/business/itrv2/MapJoinIterator.java create mode 100644 hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/business/itrv2/MapLimitIterator.java create mode 100644 hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/business/itrv2/MapUnionIterator.java create mode 100644 hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/business/itrv2/MapValueFilterIterator.java create mode 100644 hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/business/itrv2/MultiListIterator.java create mode 100644 hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/business/itrv2/TypeTransIterator.java create mode 100644 hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/business/itrv2/UnionFilterIterator.java create mode 100644 hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/business/itrv2/io/SortShuffleSerializer.java create mode 100644 hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/request/BatchPutRequest.java create mode 100644 hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/request/BlankTaskRequest.java rename hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/{ => request}/CleanDataRequest.java (96%) create mode 100644 hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/request/CreateRaftRequest.java rename hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/{ => request}/DbCompactionRequest.java (91%) rename hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/{ => request}/DestroyRaftRequest.java (87%) rename hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/{ => request}/GetStoreInfoRequest.java (90%) create mode 100644 hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/request/RedirectRaftTaskRequest.java create mode 100644 hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/request/UpdatePartitionRequest.java create mode 100644 hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/response/BatchPutResponse.java create mode 100644 hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/response/CleanDataResponse.java create mode 100644 hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/response/CreateRaftResponse.java create mode 100644 hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/response/DbCompactionResponse.java create mode 100644 hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/response/DefaultResponse.java create mode 100644 hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/response/DestroyRaftResponse.java create mode 100644 hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/response/GetStoreInfoResponse.java create mode 100644 hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/response/RedirectRaftTaskResponse.java create mode 100644 hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/response/UpdatePartitionResponse.java create mode 100644 hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/consts/PoolNames.java create mode 100644 hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/listener/PartitionChangedListener.java create mode 100644 hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/listener/PartitionStateListener.java create mode 100644 hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/listener/StoreStateListener.java create mode 100644 hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/options/JobOptions.java create mode 100644 hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/processor/BuildIndexProcessor.java create mode 100644 hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/processor/ChangeShardProcessor.java create mode 100644 hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/processor/CleanPartitionProcessor.java create mode 100644 hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/processor/CommandProcessor.java create mode 100644 hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/processor/DbCompactionProcessor.java create mode 100644 hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/processor/MovePartitionProcessor.java create mode 100644 hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/processor/PartitionRangeChangeProcessor.java create mode 100644 hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/processor/Processors.java create mode 100644 hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/processor/SplitPartitionProcessor.java create mode 100644 hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/processor/TransferLeaderProcessor.java create mode 100644 hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/raft/DefaultRaftClosure.java create mode 100644 hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/raft/PartitionStateMachine.java create mode 100644 hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/snapshot/SnapshotHandler.java create mode 100644 hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/util/MultiKv.java create mode 100644 hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/util/SortShuffle.java create mode 100644 hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/util/ZipUtils.java diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/HeartbeatService.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/HeartbeatService.java index b8fe84ba91..1eb7d0b096 100644 --- a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/HeartbeatService.java +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/HeartbeatService.java @@ -17,24 +17,28 @@ package org.apache.hugegraph.store; +import static org.apache.hugegraph.pd.grpc.Pdpb.ErrorType.PD_UNREACHABLE_VALUE; + import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; import org.apache.hugegraph.pd.common.PDException; +import org.apache.hugegraph.pd.common.PDRuntimeException; import org.apache.hugegraph.pd.grpc.Metapb; -import org.apache.hugegraph.pd.grpc.Pdpb; +import org.apache.hugegraph.pd.grpc.Pdpb.ErrorType; +import org.apache.hugegraph.store.consts.PoolNames; +import org.apache.hugegraph.store.listener.PartitionStateListener; +import org.apache.hugegraph.store.listener.StoreStateListener; import org.apache.hugegraph.store.meta.Partition; import org.apache.hugegraph.store.meta.PartitionRole; import org.apache.hugegraph.store.meta.Store; import org.apache.hugegraph.store.meta.StoreMetadata; import org.apache.hugegraph.store.options.HgStoreEngineOptions; -import org.apache.hugegraph.store.options.RaftRocksdbOptions; import org.apache.hugegraph.store.pd.PdProvider; import org.apache.hugegraph.store.util.IpUtil; import org.apache.hugegraph.store.util.Lifecycle; -import org.rocksdb.MemoryUsageType; import com.alipay.sofa.jraft.entity.PeerId; import com.alipay.sofa.jraft.util.Utils; @@ -47,23 +51,23 @@ @Slf4j public class HeartbeatService implements Lifecycle, PartitionStateListener { - private static final int MAX_HEARTBEAT_RETRY_COUNT = 5; // Heartbeat retry count - private static final int REGISTER_RETRY_INTERVAL = 1; // Registration retry interval, in seconds + private static final int MAX_HEARTBEAT_RETRY_COUNT = 5; + private static final int REGISTER_RETRY_INTERVAL = 1; + private static int processors = Runtime.getRuntime().availableProcessors(); private final HgStoreEngine storeEngine; - private final List stateListeners; - private final Object partitionThreadLock = new Object(); - private final Object storeThreadLock = new Object(); private HgStoreEngineOptions options; private PdProvider pdProvider; private Store storeInfo; private Metapb.ClusterStats clusterStats; private StoreMetadata storeMetadata; - // Heartbeat failure count + private List stateListeners; + private Object partitionThreadLock = new Object(); + private Object storeThreadLock = new Object(); private int heartbeatFailCount = 0; private int reportErrCount = 0; // Thread sleep time private volatile int timerNextDelay = 1000; - private boolean terminated = false; + private volatile boolean terminated = false; public HeartbeatService(HgStoreEngine storeEngine) { this.storeEngine = storeEngine; @@ -82,28 +86,16 @@ public boolean init(HgStoreEngineOptions opts) { storeInfo.setRaftAddress(options.getRaftAddress()); storeInfo.setState(Metapb.StoreState.Unknown); storeInfo.setLabels(options.getLabels()); - storeInfo.setCores(Runtime.getRuntime().availableProcessors()); + storeInfo.setCores(processors); storeInfo.setDeployPath(HeartbeatService.class.getResource("/").getPath()); storeInfo.setDataPath(options.getDataPath()); this.pdProvider = options.getPdProvider(); - - new Thread(new Runnable() { - @Override - public void run() { - doStoreHeartbeat(); - } - }, "heartbeat").start(); - - new Thread(new Runnable() { - @Override - public void run() { - doPartitionHeartbeat(); - } - }, " partition-hb").start(); + new Thread(() -> doStoreHeartbeat(), PoolNames.HEARTBEAT).start(); + new Thread(() -> doPartitionHeartbeat(), PoolNames.P_HEARTBEAT).start(); return true; } - public HeartbeatService addStateListener(HgStoreStateListener stateListener) { + public HeartbeatService addStateListener(StoreStateListener stateListener) { stateListeners.add(stateListener); return this; } @@ -118,7 +110,10 @@ public void setStoreMetadata(StoreMetadata storeMetadata) { // Whether the cluster is ready public boolean isClusterReady() { - return clusterStats.getState() == Metapb.ClusterState.Cluster_OK; + if (clusterStats == null) { + clusterStats = pdProvider.getClusterStats(); + } + return clusterStats != null && clusterStats.getState() == Metapb.ClusterState.Cluster_OK; } /** @@ -145,7 +140,22 @@ protected void doStoreHeartbeat() { storeThreadLock.wait(timerNextDelay); } } catch (Throwable e) { - log.error("heartbeat error: ", e); + if (e instanceof PDRuntimeException && + ((PDRuntimeException) e).getErrorCode() == PD_UNREACHABLE_VALUE) { + log.error("store heartbeat error: PD UNREACHABLE"); + synchronized (storeThreadLock) { + try { + if (timerNextDelay < 10000) { + storeThreadLock.wait(timerNextDelay); + } else { + storeThreadLock.wait(timerNextDelay / 2); + } + } catch (Exception ie) { + } + } + } else { + log.error("heartbeat error: ", e); + } } } } @@ -170,7 +180,8 @@ protected void doPartitionHeartbeat() { protected void registerStore() { try { - // Register store, initial registration of PD generates id, automatically assigns value to storeinfo + // Register store, initial registration of PD generates id, automatically assigns + // value to storeinfo this.storeInfo.setStoreAddress(IpUtil.getNearestAddress(options.getGrpcAddress())); this.storeInfo.setRaftAddress(IpUtil.getNearestAddress(options.getRaftAddress())); @@ -200,22 +211,17 @@ protected void registerStore() { } } catch (PDException e) { int exceptCode = e.getErrorCode(); - if (exceptCode == Pdpb.ErrorType.STORE_ID_NOT_EXIST_VALUE) { - log.error( - "The store ID {} does not match the PD. Check that the correct PD is " + - "connected, " + - "and then delete the store ID!!!", - storeInfo.getId()); + if (exceptCode == ErrorType.STORE_ID_NOT_EXIST_VALUE) { + log.error("The store ID {} does not match the PD. Check that the correct PD is " + + "connected, " + "and then delete the store ID!!!", storeInfo.getId()); System.exit(-1); - } else if (exceptCode == Pdpb.ErrorType.STORE_HAS_BEEN_REMOVED_VALUE) { + } else if (exceptCode == ErrorType.STORE_HAS_BEEN_REMOVED_VALUE) { log.error("The store ID {} has been removed, please delete all data and restart!", storeInfo.getId()); System.exit(-1); - } else if (exceptCode == Pdpb.ErrorType.STORE_PROHIBIT_DUPLICATE_VALUE) { - log.error( - "The store ID {} maybe duplicated, please check out store raft address " + - "and restart later!", - storeInfo.getId()); + } else if (exceptCode == ErrorType.STORE_PROHIBIT_DUPLICATE_VALUE) { + log.error("The store ID {} maybe duplicated, please check out store raft address " + + "and restart later!", storeInfo.getId()); System.exit(-1); } } @@ -230,16 +236,19 @@ protected void storeHeartbeat() { clusterStats = pdProvider.storeHeartbeat(this.storeInfo); } catch (PDException e) { int exceptCode = e.getErrorCode(); - if (exceptCode == Pdpb.ErrorType.STORE_ID_NOT_EXIST_VALUE) { + if (exceptCode == ErrorType.STORE_ID_NOT_EXIST_VALUE) { log.error("The store ID {} does not match the PD. Check that the correct PD is " + "connected, and then delete the store ID!!!", storeInfo.getId()); System.exit(-1); - } else if (exceptCode == Pdpb.ErrorType.STORE_HAS_BEEN_REMOVED_VALUE) { + } else if (exceptCode == ErrorType.STORE_HAS_BEEN_REMOVED_VALUE) { log.error("The store ID {} has been removed, please delete all data and restart!", storeInfo.getId()); System.exit(-1); } } + if (clusterStats == null || clusterStats.getState() == null) { + throw new PDRuntimeException(PD_UNREACHABLE_VALUE); + } if (clusterStats.getState().getNumber() >= Metapb.ClusterState.Cluster_Fault.getNumber()) { if (reportErrCount == 0) { log.info("The cluster is abnormal, {}", clusterStats); @@ -286,9 +295,9 @@ protected void partitionHeartbeat() { final List statsList = new ArrayList<>(partitions.size()); Metapb.Shard localLeader = Metapb.Shard.newBuilder() - .setStoreId( - storeEngine.getPartitionManager().getStore() - .getId()) + .setStoreId(storeEngine + .getPartitionManager() + .getStore().getId()) .setRole(Metapb.ShardRole.Leader) .build(); // Get information for each shard. @@ -300,6 +309,16 @@ protected void partitionHeartbeat() { stats.setConfVer(partition.getShardGroup().getConfVersion()); stats.setLeader(localLeader); + Metapb.PartitionState partitionState = Metapb.PartitionState.PState_Normal; + for (var entry : storeEngine.getPartitionManager().getPartitions(partition.getGroupId()) + .entrySet()) { + if (entry.getValue().getWorkState() == Metapb.PartitionState.PState_Offline) { + partitionState = Metapb.PartitionState.PState_Offline; + } + } + // pd 不会处理 (3.7.2+) + stats.setState(partitionState); + stats.addAllShard(partition.getShardGroup().getMetaPbShard()); // shard status @@ -331,20 +350,20 @@ protected void partitionHeartbeat() { public void monitorMemory() { - try { - Map mems = - storeEngine.getBusinessHandler().getApproximateMemoryUsageByType(null); - - if (mems.get(MemoryUsageType.kCacheTotal) > - RaftRocksdbOptions.getWriteCacheCapacity() * 0.9 && - mems.get(MemoryUsageType.kMemTableUnFlushed) > - RaftRocksdbOptions.getWriteCacheCapacity() * 0.1) { - // storeEngine.getBusinessHandler().flushAll(); - log.warn("Less memory, start flush dbs, {}", mems); - } - } catch (Exception e) { - log.error("MonitorMemory exception {}", e); - } + // try { + // Map mems = + // storeEngine.getBusinessHandler().getApproximateMemoryUsageByType(null); + // + // if (mems.get(MemoryUsageType.kCacheTotal) > RaftRocksdbOptions + // .getWriteCacheCapacity() * 0.9 && + // mems.get(MemoryUsageType.kMemTableUnFlushed) > RaftRocksdbOptions + // .getWriteCacheCapacity() * 0.1) { + // // storeEngine.getBusinessHandler().flushAll(); + // // log.warn("Less memory, start flush dbs, {}", mems); + // } + // } catch (Exception e) { + // log.error("MonitorMemory exception {}", e); + // } } @Override @@ -381,4 +400,18 @@ private void wakeupHeartbeatThread() { storeThreadLock.notifyAll(); } } + + /** + * reconnect pulse + */ + public void connectNewPulse() { + pdProvider.getPDClient().forceReconnect(); +// pdProvider.startHeartbeatStream(error->{ +// onStateChanged(Metapb.StoreState.Offline); +// timerNextDelay = REGISTER_RETRY_INTERVAL * 1000; +// wakeupHeartbeatThread(); +// log.error("Connection closed. The store state changes to {}", Metapb.StoreState +// .Offline); +// }); + } } diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/HgStoreEngine.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/HgStoreEngine.java index b76e7a45c9..440d706ae4 100644 --- a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/HgStoreEngine.java +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/HgStoreEngine.java @@ -24,33 +24,43 @@ import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.stream.Collectors; import org.apache.hugegraph.pd.common.PDException; import org.apache.hugegraph.pd.grpc.Metapb; import org.apache.hugegraph.rocksdb.access.RocksDBFactory; import org.apache.hugegraph.store.business.BusinessHandler; import org.apache.hugegraph.store.business.BusinessHandlerImpl; -import org.apache.hugegraph.store.business.DataMover; +import org.apache.hugegraph.store.business.DataManager; import org.apache.hugegraph.store.cmd.HgCmdClient; import org.apache.hugegraph.store.cmd.HgCmdProcessor; -import org.apache.hugegraph.store.cmd.UpdatePartitionRequest; -import org.apache.hugegraph.store.cmd.UpdatePartitionResponse; +import org.apache.hugegraph.store.cmd.request.UpdatePartitionRequest; +import org.apache.hugegraph.store.cmd.response.UpdatePartitionResponse; +import org.apache.hugegraph.store.consts.PoolNames; +import org.apache.hugegraph.store.listener.PartitionChangedListener; +import org.apache.hugegraph.store.listener.StoreStateListener; import org.apache.hugegraph.store.meta.Partition; import org.apache.hugegraph.store.meta.PartitionManager; import org.apache.hugegraph.store.meta.ShardGroup; import org.apache.hugegraph.store.meta.Store; import org.apache.hugegraph.store.metric.HgMetricService; import org.apache.hugegraph.store.options.HgStoreEngineOptions; +import org.apache.hugegraph.store.options.JobOptions; import org.apache.hugegraph.store.options.PartitionEngineOptions; import org.apache.hugegraph.store.pd.DefaultPdProvider; import org.apache.hugegraph.store.pd.FakePdServiceProvider; import org.apache.hugegraph.store.pd.PdProvider; +import org.apache.hugegraph.store.processor.Processors; import org.apache.hugegraph.store.raft.RaftClosure; import org.apache.hugegraph.store.raft.RaftOperation; +import org.apache.hugegraph.store.util.ExecutorUtil; import org.apache.hugegraph.store.util.HgRaftError; import org.apache.hugegraph.store.util.Lifecycle; import com.alipay.sofa.jraft.JRaftUtils; +import com.alipay.sofa.jraft.Node; import com.alipay.sofa.jraft.Status; import com.alipay.sofa.jraft.conf.Configuration; import com.alipay.sofa.jraft.core.NodeMetrics; @@ -66,11 +76,13 @@ * The core class of the storage engine, initializing PD client and raft client */ @Slf4j -public class HgStoreEngine implements Lifecycle, HgStoreStateListener { +public class HgStoreEngine implements Lifecycle, StoreStateListener, + PartitionChangedListener { - private final static HgStoreEngine instance = new HgStoreEngine(); - private static ConcurrentHashMap engineLocks = new ConcurrentHashMap<>(); - // Partition raft engine, key is GraphName_PartitionID + private static final HgStoreEngine INSTANCE = new HgStoreEngine(); + private static final ConcurrentHashMap engineLocks = new ConcurrentHashMap<>(); + private static ThreadPoolExecutor uninterruptibleJobs; + // 分区raft引擎,key为GraphName_PartitionID private final Map partitionEngines = new ConcurrentHashMap<>(); private RpcServer rpcServer; private HgStoreEngineOptions options; @@ -80,14 +92,24 @@ public class HgStoreEngine implements Lifecycle, HgStoreSt private HeartbeatService heartbeatService; private BusinessHandler businessHandler; private HgMetricService metricService; - private DataMover dataMover; + private DataManager dataManager; + private final AtomicBoolean closing = new AtomicBoolean(false); + + private HgStoreEngine() { + + } public static HgStoreEngine getInstance() { - return instance; + return INSTANCE; + } + + public static ThreadPoolExecutor getUninterruptibleJobs() { + return uninterruptibleJobs; } /** - * 1. Read StoreId, register with PD, the StoreId is generated by PD for the first registration and stored locally. + * 1. Read StoreId, register with PD, the StoreId is generated by PD for the first + * registration and stored locally. * 2. Registration successful, start the raft service. * 3. Timely send Store heartbeats and Partition heartbeats to maintain contact with PD. * @@ -102,6 +124,15 @@ public synchronized boolean init(final HgStoreEngineOptions opts) { } this.options = opts; + // 放到最前面 + if (uninterruptibleJobs == null) { + JobOptions jobConfig = options.getJobConfig(); + uninterruptibleJobs = ExecutorUtil.createExecutor(PoolNames.U_JOB, + jobConfig.getUninterruptibleCore(), + jobConfig.getUninterruptibleMax(), + jobConfig.getUninterruptibleQueueSize(), + false); + } BusinessHandlerImpl.initRocksdb(opts.getRocksdbConfig(), getRocksdbListener()); @@ -109,16 +140,17 @@ public synchronized boolean init(final HgStoreEngineOptions opts) { pdProvider = new FakePdServiceProvider(opts.getFakePdOptions()); } else { pdProvider = new DefaultPdProvider(opts.getPdAddress()); - pdProvider.addPartitionInstructionListener(new PartitionInstructionProcessor(this)); + pdProvider.setCommandProcessors(new Processors(this)); } options.setPdProvider(pdProvider); partitionManager = new PartitionManager(pdProvider, opts); - partitionManager.addPartitionChangedListener(new PartitionChangedListener()); - + partitionManager.addPartitionChangedListener(this); businessHandler = new BusinessHandlerImpl(partitionManager); - // Need businessHandler initialization afterwards + BusinessHandlerImpl.setIndexDataSize( + this.options.getQueryPushDownOption().getIndexSizeLimitCount()); + // 需要businessHandler 初始化后 partitionManager.load(); rpcServer = createRaftRpcServer(opts.getRaftAddress()); @@ -128,7 +160,7 @@ public synchronized boolean init(final HgStoreEngineOptions opts) { // When splitting, it has not been reported to pd in time. if (getPartitionEngine(ptId) != null) { return getPartitionEngine(ptId).waitForLeader( - options.getWaitLeaderTimeout() * 1000); + options.getWaitLeaderTimeout() * 1000L); } else { // May occur cross-partition migration Metapb.Shard shard = pdProvider.getPartitionLeader(graphName, ptId); @@ -143,11 +175,12 @@ public synchronized boolean init(final HgStoreEngineOptions opts) { metricService = HgMetricService.getInstance(); metricService.setHgStoreEngine(this).init(null); - - dataMover = opts.getDataTransfer(); - if (dataMover != null) { - this.dataMover.setBusinessHandler(this.businessHandler); - this.dataMover.setCmdClient(hgCmdClient); + partitionManager.setCmdClient(hgCmdClient); + dataManager = opts.getDataTransfer(); + if (dataManager != null) { + dataManager.setBusinessHandler(this.businessHandler); + dataManager.setMetaManager(partitionManager); + dataManager.setCmdClient(hgCmdClient); } return true; } @@ -157,6 +190,7 @@ public synchronized boolean init(final HgStoreEngineOptions opts) { */ private RpcServer createRaftRpcServer(String raftAddr) { Endpoint endpoint = JRaftUtils.getEndPoint(raftAddr); + //todo soya is this right? RpcServer rpcServer = RaftRpcServerFactory.createRaftRpcServer(endpoint, JRaftUtils.createExecutor( "RAFT-RPC-", @@ -172,16 +206,30 @@ public void shutdown() { if (rpcServer == null) { return; } - partitionEngines.forEach((k, v) -> { - v.shutdown(); - }); + closing.set(true); + heartbeatService.shutdown(); + metricService.shutdown(); + partitionEngines.values().stream().parallel().map(pe -> { + try { + Node raftNode = pe.getRaftNode(); + if (raftNode.isLeader(false)) { + Status status = raftNode.transferLeadershipTo(PeerId.ANY_PEER); + if (!status.isOk()) { + log.warn("transfer leader error: {}", status); + } + } + } catch (Exception e) { + log.error("transfer leader error: ", e); + } + pe.shutdown(); + businessHandler.closeDB(pe.getGroupId()); + return true; + }).collect(Collectors.toList()); partitionEngines.clear(); rpcServer.shutdown(); // HgStoreEngine.init function check rpcServer whether is null, skipped if the instance // exists even shut down. rpcServer = null; - heartbeatService.shutdown(); - metricService.shutdown(); // close all db session RocksDBFactory.getInstance().releaseAllGraphDB(); } @@ -246,17 +294,7 @@ public void rebuildRaftGroup(long storeId) { if (partitions.size() > 0) { var shards = pdProvider.getShardGroup(partId).getShardsList(); if (shards.stream().anyMatch(s -> s.getStoreId() == storeId)) { - var peers = partitionManager.shards2Peers(shards); - Configuration initConf = engine.getOptions().getConf(); - if (initConf == null) { - engine.getOptions().setPeerList(peers); - } else { - peers.stream() - .forEach(peer -> initConf.addPeer(JRaftUtils.getPeerId(peer))); - } - - // engine.getOptions().getConf().setPeers(); - engine.restartRaftNode(); + restartPartitionEngine(engine, shards); } } } catch (PDException e) { @@ -265,6 +303,41 @@ public void rebuildRaftGroup(long storeId) { }); } + public void handleShardGroupOp(int groupId, List shards) { + log.info("handleShardGroupOp, groupId: {}, shards: {}", groupId, shards); + + var engine = getPartitionEngine(groupId); + + if (engine != null) { + if (shards.stream() + .anyMatch(s -> s.getStoreId() == partitionManager.getStore().getId())) { + restartPartitionEngine(engine, shards); + } else { + destroyPartitionEngine(groupId, List.copyOf(engine.getPartitions().keySet())); + engine.getPartitions().forEach((g, p) -> engine.removePartition(g)); + engine.shutdown(); + } + } + } + + /** + * 使用新的configure 启动 partition engine,一般用来处理 raft addr变更,或者要手动调整某些分区 + * + * @param engine partition engine + * @param shards shard list + */ + private void restartPartitionEngine(PartitionEngine engine, List shards) { + var peers = partitionManager.shards2Peers(shards); + Configuration initConf = engine.getOptions().getConf(); + if (initConf == null) { + engine.getOptions().setPeerList(peers); + } else { + peers.stream().forEach(peer -> initConf.addPeer(JRaftUtils.getPeerId(peer))); + } + // engine.getOptions().getConf().setPeers(); + engine.restartRaftNode(); + } + /** * Create raft Node * @@ -282,22 +355,23 @@ public PartitionEngine createPartitionEngine(Partition partition, Configuration return createPartitionEngine(partition.getId(), shardGroup, conf); } - private PartitionEngine createPartitionEngine(int groupId, ShardGroup shardGroup, - Configuration conf) { + public PartitionEngine createPartitionEngine(int groupId, ShardGroup shardGroup, + Configuration conf) { PartitionEngine engine; if ((engine = partitionEngines.get(groupId)) == null) { engineLocks.computeIfAbsent(groupId, k -> new Object()); synchronized (engineLocks.get(groupId)) { - // Special cases during partition splitting (different number of graph partitions in the cluster) can cause the splitting partition not to be on this machine. + // Special cases during partition splitting (different number of graph partitions + // in the cluster) can cause the splitting partition not to be on this machine. if (conf != null) { var list = conf.listPeers(); list.addAll(conf.listLearners()); - if (!list.stream().anyMatch( - p -> p.getEndpoint().toString().equals(options.getRaftAddress()))) { - log.info( - "raft {}, conf {} does not contains raft address:{}, skipped " + - "create partition engine", - groupId, conf, options.getRaftAddress()); + if (!list.stream() + .anyMatch(p -> p.getEndpoint().toString() + .equals(options.getRaftAddress()))) { + log.info("raft {}, conf {} does not contains raft address:{}, skipped " + + "create partition engine", groupId, conf, + options.getRaftAddress()); return null; } } else { @@ -341,7 +415,8 @@ private PartitionEngine createPartitionEngine(int groupId, ShardGroup shardGroup } /** - * Create raft group, in addition to creating the local raft node, also need to notify other peers to create raft nodes. + * Create raft group, in addition to creating the local raft node, also need to notify other + * peers to create raft nodes. * 1. Traverse partition.shards * 2. Retrieve Store information based on storeId * 3. Establish Raft RPC to other stores, send StartRaft messages. @@ -365,14 +440,14 @@ public PartitionEngine createPartitionGroups(Partition partition) { if (store == null || partitionManager.isLocalStore(store)) { return; } - // Send messages to other peers, create raft groups. This is an asynchronous send. + // Send messages to other peers, create raft groups. This is an asynchronous + // send. hgCmdClient.createRaftNode(store.getRaftAddress(), List.of(partition), status -> { - log.info( - "send to {} createRaftNode rpc call " + - "result {} partitionId {}", - store.getRaftAddress(), status, - partition.getId()); + log.info("send to {} createRaftNode rpc call " + + "result {} partitionId {}", + store.getRaftAddress(), status, + partition.getId()); }); }); } @@ -393,14 +468,10 @@ public void destroyPartitionGroups(Partition partition) { } // Send messages to other peers, create raft groups. This is an asynchronous send. hgCmdClient.destroyRaftNode(store.getRaftAddress(), - Arrays.asList(new Partition[]{partition}), - status -> { - log.info( - "send to {} - {} DestroyRaftNode rpc call" + - " result {}", - store.getRaftAddress(), partition.getId(), - status); - }); + Arrays.asList(new Partition[]{partition}), status -> { + log.info("send to {} - {} DestroyRaftNode rpc call" + " result {}", + store.getRaftAddress(), partition.getId(), status); + }); }); } } @@ -425,6 +496,8 @@ public synchronized void destroyPartitionEngine(Integer groupId, List gr partitionEngines.remove(groupId); // Delete the corresponding db folder businessHandler.destroyGraphDB(graphNames.get(0), groupId); + // 删除 partition db location信息 + getPartitionManager().getStoreMetadata().removePartitionStore(groupId); } else { graphNames.forEach(graphName -> { businessHandler.dbCompaction(graphName, groupId); @@ -517,8 +590,8 @@ public void setPartitionManager(PartitionManager ptm) { this.partitionManager = ptm; } - public DataMover getDataMover() { - return dataMover; + public DataManager getDataManager() { + return dataManager; } public PdProvider getPdProvider() { @@ -569,9 +642,10 @@ public void addRaftTask(String graphName, Integer partId, RaftOperation operatio Partition partition = partitionManager.findPartition(graphName, partId); if (partition != null) { engine = this.createPartitionGroups(partition); - // May migrate, should not create, put in synchronize block, avoid subsequent ones. + // May migrate, should not create, put in synchronize block, avoid + // subsequent ones. if (engine != null) { - engine.waitForLeader(options.getWaitLeaderTimeout() * 1000); + engine.waitForLeader(options.getWaitLeaderTimeout() * 1000L); } } } @@ -580,7 +654,7 @@ public void addRaftTask(String graphName, Integer partId, RaftOperation operatio if (engine != null) { // Waiting for Leader - Endpoint leader = engine.waitForLeader(options.getWaitLeaderTimeout() * 1000); + Endpoint leader = engine.waitForLeader(options.getWaitLeaderTimeout() * 1000L); if (engine.isLeader()) { engine.addRaftTask(operation, closure); } else if (leader != null) { @@ -588,7 +662,8 @@ public void addRaftTask(String graphName, Integer partId, RaftOperation operatio Store store = partitionManager.getStoreByRaftEndpoint(engine.getShardGroup(), leader.toString()); if (store.getId() == 0) { - // Local store information for the Leader was not found, possibly the Partition has not been synchronized yet, reacquire from the Leader. + // Local store information for the Leader was not found, possibly the + // Partition has not been synchronized yet, reacquire from the Leader. Store leaderStore = hgCmdClient.getStoreInfo(leader.toString()); store = leaderStore != null ? leaderStore : store; log.error("getStoreByRaftEndpoint error store:{}, shard: {}, leader is {}", @@ -670,57 +745,59 @@ public void onCompacted(String dbName) { }; } - class PartitionChangedListener implements PartitionManager.PartitionChangedListener { + public HgStoreEngineOptions getOption() { + return this.options; + } - /** - * Partition object changes, leader notifies other followers. - */ - @Override - public void onChanged(Partition partition) { - PartitionEngine engine = getPartitionEngine(partition.getId()); + /** + * Partition object changes, leader notifies other followers. + */ + @Override + public void onChanged(Partition partition) { + PartitionEngine engine = getPartitionEngine(partition.getId()); - if (engine != null && engine.isLeader()) { - try { - engine.addRaftTask(RaftOperation.create(RaftOperation.SYNC_PARTITION, - partition.getProtoObj()), - new RaftClosure() { - @Override - public void run(Status status) { - log.info( - "Partition {}-{}-{} sync partition status " + - "is {}", - partition.getGraphName(), partition.getId(), - partition.getWorkState(), - status); - } - }); - } catch (IOException e) { - log.error("Partition {}-{} sync partition exception {}", - partition.getGraphName(), partition.getId(), e); - } + if (engine != null && engine.isLeader()) { + try { + engine.addRaftTask( + RaftOperation.create(RaftOperation.SYNC_PARTITION, partition.getProtoObj()), + new RaftClosure() { + @Override + public void run(Status status) { + log.info("Partition {}-{}-{} sync partition status is {}", + partition.getGraphName(), partition.getId(), + partition.getWorkState(), status); + } + }); + } catch (IOException e) { + log.error("Partition {}-{} sync partition exception {}", partition.getGraphName(), + partition.getId(), e); } } + } - /** - * Partition object key range, status changes, notify other followers by actively finding the leader. - */ - @Override - public UpdatePartitionResponse rangeOrStateChanged(UpdatePartitionRequest request) { - UpdatePartitionResponse response = null; - try { - response = hgCmdClient.raftUpdatePartition(request); - - log.info("not leader request threadId:{} pId:{} range:{}-{} state:{} response:{}", - Thread.currentThread().getId(), request.getPartitionId(), - request.getStartKey(), - request.getEndKey(), request.getWorkState(), response.getStatus()); + /** + * Partition object key range, status changes, notify other followers by actively finding the + * leader. + */ + @Override + public UpdatePartitionResponse rangeOrStateChanged(UpdatePartitionRequest request) { + UpdatePartitionResponse response = null; + try { + response = hgCmdClient.raftUpdatePartition(request); - } catch (Exception e) { - e.printStackTrace(); - } + log.info("not leader request threadId:{} pId:{} range:{}-{} state:{} response:{}", + Thread.currentThread().getId(), request.getPartitionId(), + request.getStartKey(), request.getEndKey(), request.getWorkState(), + response.getStatus()); - return response; + } catch (Exception e) { + e.printStackTrace(); } + return response; + } + + public AtomicBoolean isClosing() { + return closing; } } diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/HgStoreStateListener.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/HgStoreStateListener.java index cf8ce3904e..9b31dff712 100644 --- a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/HgStoreStateListener.java +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/HgStoreStateListener.java @@ -20,6 +20,7 @@ import org.apache.hugegraph.pd.grpc.Metapb; import org.apache.hugegraph.store.meta.Store; +@Deprecated public interface HgStoreStateListener { void stateChanged(Store store, Metapb.StoreState oldState, Metapb.StoreState newState); diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/PartitionEngine.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/PartitionEngine.java index ee65162f7c..808149fb22 100644 --- a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/PartitionEngine.java +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/PartitionEngine.java @@ -29,34 +29,43 @@ import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.concurrent.CountDownLatch; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; import org.apache.commons.collections.ListUtils; +import org.apache.commons.collections.SetUtils; import org.apache.commons.io.FileUtils; import org.apache.commons.lang.StringUtils; import org.apache.hugegraph.pd.common.PDException; import org.apache.hugegraph.pd.grpc.MetaTask; import org.apache.hugegraph.pd.grpc.Metapb; -import org.apache.hugegraph.store.cmd.BatchPutRequest; -import org.apache.hugegraph.store.cmd.CleanDataRequest; -import org.apache.hugegraph.store.cmd.DbCompactionRequest; +import org.apache.hugegraph.store.business.BusinessHandler; +import org.apache.hugegraph.store.business.BusinessHandlerImpl; import org.apache.hugegraph.store.cmd.HgCmdClient; -import org.apache.hugegraph.store.cmd.UpdatePartitionRequest; +import org.apache.hugegraph.store.cmd.request.BatchPutRequest; +import org.apache.hugegraph.store.cmd.request.CleanDataRequest; +import org.apache.hugegraph.store.cmd.request.DbCompactionRequest; +import org.apache.hugegraph.store.cmd.request.UpdatePartitionRequest; +import org.apache.hugegraph.store.listener.PartitionStateListener; import org.apache.hugegraph.store.meta.Partition; import org.apache.hugegraph.store.meta.PartitionManager; import org.apache.hugegraph.store.meta.Shard; import org.apache.hugegraph.store.meta.ShardGroup; import org.apache.hugegraph.store.meta.Store; import org.apache.hugegraph.store.meta.TaskManager; +import org.apache.hugegraph.store.options.HgStoreEngineOptions; import org.apache.hugegraph.store.options.PartitionEngineOptions; -import org.apache.hugegraph.store.raft.HgStoreStateMachine; +import org.apache.hugegraph.store.raft.DefaultRaftClosure; +import org.apache.hugegraph.store.raft.PartitionStateMachine; import org.apache.hugegraph.store.raft.RaftClosure; import org.apache.hugegraph.store.raft.RaftOperation; import org.apache.hugegraph.store.raft.RaftStateListener; import org.apache.hugegraph.store.raft.RaftTaskHandler; import org.apache.hugegraph.store.raft.util.RaftUtils; -import org.apache.hugegraph.store.snapshot.HgSnapshotHandler; +import org.apache.hugegraph.store.snapshot.SnapshotHandler; import org.apache.hugegraph.store.util.FutureClosure; import org.apache.hugegraph.store.util.HgRaftError; import org.apache.hugegraph.store.util.HgStoreException; @@ -81,12 +90,12 @@ import com.alipay.sofa.jraft.storage.impl.RocksDBLogStorage; import com.alipay.sofa.jraft.storage.log.RocksDBSegmentLogStorage; import com.alipay.sofa.jraft.util.Endpoint; -import com.alipay.sofa.jraft.util.SystemPropertyUtil; import com.alipay.sofa.jraft.util.ThreadId; import com.alipay.sofa.jraft.util.Utils; import com.alipay.sofa.jraft.util.internal.ThrowUtil; import com.google.protobuf.CodedInputStream; +import lombok.Getter; import lombok.extern.slf4j.Slf4j; /** @@ -105,25 +114,17 @@ public class PartitionEngine implements Lifecycle, RaftS private final AtomicBoolean changingPeer; private final AtomicBoolean snapshotFlag; private final Object leaderChangedEvent = "leaderChangedEvent"; - /** - * Default value size threshold to decide whether it will be stored in segments or rocksdb, - * default is 4K. - * When the value size is less than 4K, it will be stored in rocksdb directly. - */ - private final int DEFAULT_VALUE_SIZE_THRESHOLD = SystemPropertyUtil.getInt( - "jraft.log_storage.segment.value.threshold.bytes", 4 * 1024); - /** - * Default checkpoint interval in milliseconds. - */ - private final int DEFAULT_CHECKPOINT_INTERVAL_MS = SystemPropertyUtil.getInt( - "jraft.log_storage.segment.checkpoint.interval.ms", 5000); private PartitionEngineOptions options; - private HgStoreStateMachine stateMachine; + private PartitionStateMachine stateMachine; + @Getter private RaftGroupService raftGroupService; + @Getter private TaskManager taskManager; + private SnapshotHandler snapshotHandler; + @Getter private Node raftNode; - private boolean started; + private volatile boolean started; public PartitionEngine(HgStoreEngine storeEngine, ShardGroup shardGroup) { this.storeEngine = storeEngine; @@ -182,8 +183,8 @@ public synchronized boolean init(PartitionEngineOptions opts) { log.info("PartitionEngine starting: {}", this); this.taskManager = new TaskManager(storeEngine.getBusinessHandler(), opts.getGroupId()); - HgSnapshotHandler snapshotHandler = new HgSnapshotHandler(this); - this.stateMachine = new HgStoreStateMachine(opts.getGroupId(), snapshotHandler); + this.snapshotHandler = new SnapshotHandler(this); + this.stateMachine = new PartitionStateMachine(opts.getGroupId(), snapshotHandler); // probably null in test case if (opts.getTaskHandler() != null) { this.stateMachine.addTaskHandler(opts.getTaskHandler()); @@ -219,6 +220,7 @@ public synchronized boolean init(PartitionEngineOptions opts) { nodeOptions.setSharedVoteTimer(true); nodeOptions.setFilterBeforeCopyRemote(true); + HgStoreEngineOptions.RaftOptions raft = options.getRaftOptions(); nodeOptions.setServiceFactory(new DefaultJRaftServiceFactory() { @Override public LogStorage createLogStorage(final String uri, final RaftOptions raftOptions) { @@ -231,27 +233,25 @@ public LogStorage createLogStorage(final String uri, final RaftOptions raftOptio }); // Initial cluster nodeOptions.setInitialConf(initConf); - // Snapshot interval - nodeOptions.setSnapshotIntervalSecs(options.getRaftOptions().getSnapshotIntervalSecs()); + // 快照时间间隔 + nodeOptions.setSnapshotIntervalSecs(raft.getSnapshotIntervalSecs()); + //todo soya fix + //nodeOptions.setSnapShotDownloadingThreads(raft.getSnapshotDownloadingThreads()); //nodeOptions.setSnapshotLogIndexMargin(options.getRaftOptions() // .getSnapshotLogIndexMargin()); - nodeOptions.setRpcConnectTimeoutMs(options.getRaftOptions().getRpcConnectTimeoutMs()); - nodeOptions.setRpcDefaultTimeout(options.getRaftOptions().getRpcDefaultTimeout()); - nodeOptions.setRpcInstallSnapshotTimeout( - options.getRaftOptions().getRpcInstallSnapshotTimeout()); - nodeOptions.setElectionTimeoutMs(options.getRaftOptions().getElectionTimeoutMs()); + nodeOptions.setRpcConnectTimeoutMs(raft.getRpcConnectTimeoutMs()); + nodeOptions.setRpcDefaultTimeout(raft.getRpcDefaultTimeout()); + nodeOptions.setRpcInstallSnapshotTimeout(raft.getRpcInstallSnapshotTimeout()); + nodeOptions.setElectionTimeoutMs(raft.getElectionTimeoutMs()); // Set raft configuration RaftOptions raftOptions = nodeOptions.getRaftOptions(); - raftOptions.setDisruptorBufferSize(options.getRaftOptions().getDisruptorBufferSize()); - raftOptions.setMaxEntriesSize(options.getRaftOptions().getMaxEntriesSize()); - raftOptions.setMaxReplicatorInflightMsgs( - options.getRaftOptions().getMaxReplicatorInflightMsgs()); + raftOptions.setDisruptorBufferSize(raft.getDisruptorBufferSize()); + raftOptions.setMaxEntriesSize(raft.getMaxEntriesSize()); + raftOptions.setMaxReplicatorInflightMsgs(raft.getMaxReplicatorInflightMsgs()); raftOptions.setMaxByteCountPerRpc(1024 * 1024); - raftOptions.setMaxBodySize(options.getRaftOptions().getMaxBodySize()); nodeOptions.setEnableMetrics(true); - final PeerId serverId = JRaftUtils.getPeerId(options.getRaftAddress()); // Build raft group and start raft @@ -310,125 +310,133 @@ public Status changePeers(List peers, final Closure done) { // Check the peer that needs to be added. List addPeers = ListUtils.removeAll(peers, oldPeers); // learner to be deleted. Possible peer change. - List removedPeers = ListUtils.removeAll(RaftUtils.getLearnerEndpoints(raftNode), - peers); + List removedPeers = ListUtils.removeAll(oldPeers, peers); HgCmdClient rpcClient = storeEngine.getHgCmdClient(); // Generate a new Configuration object + Configuration oldConf = getCurrentConf(); Configuration conf = oldConf.copy(); - if (!addPeers.isEmpty()) { - addPeers.forEach(peer -> { - conf.addLearner(JRaftUtils.getPeerId(peer)); - }); - doSnapshot((RaftClosure) status -> { - log.info("Raft {} snapshot before add learner, result:{}", getGroupId(), status); - }); + FutureClosure closure; - FutureClosure closure = new FutureClosure(addPeers.size()); - addPeers.forEach(peer -> Utils.runInThread(() -> { - // 1. Create a new peer's raft object + if (!addPeers.isEmpty()) { + addPeers.forEach(peer -> conf.addLearner(JRaftUtils.getPeerId(peer))); + doSnapshot(status -> log.info("Raft {} snapshot before add learner, result:{}", + getGroupId(), status)); + // 2.1 learner 加入 raft group + for (var peer : addPeers) { + closure = new FutureClosure(); rpcClient.createRaftNode(peer, partitionManager.getPartitionList(getGroupId()), - conf, status -> { - closure.run(status); - if (!status.isOk()) { - log.error("Raft {} add node {} error {}", - options.getGroupId(), peer, status); - } - }); - })); - closure.get(); - } else { - // 3. Check if learner has completed snapshot synchronization - boolean snapshotOk = true; - for (PeerId peerId : raftNode.listLearners()) { - Replicator.State state = getReplicatorState(peerId); - if (state == null || state != Replicator.State.Replicate) { - snapshotOk = false; - break; + conf, closure); + var status = closure.get(); + if (!status.isOk()) { + log.info("Raft {} createRaftNode, peer:{}, reason:{}", getGroupId(), peer, + status.getErrorMsg()); + return status; } - log.info("Raft {} {} getReplicatorState {}", getGroupId(), peerId, state); } - if (snapshotOk && !conf.listLearners().isEmpty()) { - // 4. Delete learner, rejoin as peer - FutureClosure closure = new FutureClosure(); - raftNode.removeLearners(conf.listLearners(), closure); - if (closure.get().isOk()) { - conf.listLearners().forEach(peerId -> { - conf.addPeer(peerId); - conf.removeLearner(peerId); - }); - result = Status.OK(); - } else { - // Failed, retrying - result = HgRaftError.TASK_ERROR.toStatus(); - } - } else if (snapshotOk) { - result = Status.OK(); // No learner, indicating only delete operations are performed. + + closure = new FutureClosure(); + raftNode.changePeers(conf, closure); + var status = closure.get(); + if (!status.isOk()) { + log.info("Raft {} changePeers failed, reason:{}", getGroupId(), + status.getErrorMsg()); + return status; } - } - if (result.isOk()) { - // Sync completed, delete old peer - removedPeers.addAll(ListUtils.removeAll(oldPeers, peers)); - // Check if leader is deleted, if so, perform leader migration first. - if (removedPeers.contains( - this.getRaftNode().getNodeId().getPeerId().getEndpoint().toString())) { - - log.info("Raft {} leader is removed, needs to transfer leader {}, conf: {}", - getGroupId(), peers, conf); - // only one (that's leader self), should add peer first - if (raftNode.listPeers().size() == 1) { - FutureClosure closure = new FutureClosure(); - raftNode.changePeers(conf, closure); - log.info("Raft {} change peer result:{}", getGroupId(), closure.get()); + + // 2.2 等待 learner 完成快照同步 (check added learner) + boolean allLearnerSnapshotOk = false; + long current = System.currentTimeMillis(); + while (!allLearnerSnapshotOk) { + boolean snapshotOk = true; + for (var peerId : addPeers) { + var state = getReplicatorState(JRaftUtils.getPeerId(peerId)); + log.info("Raft {}, peer:{}, replicate state:{}", getGroupId(), peerId, state); + if (state != Replicator.State.Replicate) { + snapshotOk = false; + } + } + allLearnerSnapshotOk = snapshotOk; + + if (!allLearnerSnapshotOk) { + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + log.warn("Raft {} sleep when check learner snapshot", getGroupId()); + } } + if (System.currentTimeMillis() - current > 600 * 1000) { + return HgRaftError.TASK_CONTINUE.toStatus(); + } + } + + log.info("Raft {} replicate status is OK", getGroupId()); - var status = this.raftNode.transferLeadershipTo(PeerId.ANY_PEER); - log.info("Raft {} transfer leader status : {}", getGroupId(), status); - // Need to resend the command to the new leader + closure = new FutureClosure(); + // 2.3 change learner to follower (first remove, then add follower) + raftNode.removeLearners(conf.listLearners(), closure); + if (!closure.get().isOk()) { + log.error("Raft {} remove learner error, result:{}", getGroupId(), status); + return HgRaftError.TASK_ERROR.toStatus(); + } + + addPeers.forEach(peer -> { + conf.removeLearner(JRaftUtils.getPeerId(peer)); + conf.addPeer(JRaftUtils.getPeerId(peer)); + }); + + // add follower + closure = new FutureClosure(); + raftNode.changePeers(conf, closure); + if (!closure.get().isOk()) { + log.error("Raft {} changePeers error, result:{}", getGroupId(), status); return HgRaftError.TASK_ERROR.toStatus(); } } + boolean removeSelf = false; + // case 3: if (!removedPeers.isEmpty()) { - removedPeers.forEach(peer -> { + var self = this.getRaftNode().getNodeId().getPeerId().getEndpoint().toString(); + removeSelf = removedPeers.contains(self); + // 3.1 remove peers + List toDestroy = new ArrayList<>(); + for (var peer : removedPeers) { + if (Objects.equals(peer, self)) { + continue; + } conf.removeLearner(JRaftUtils.getPeerId(peer)); conf.removePeer(JRaftUtils.getPeerId(peer)); - }); - } + toDestroy.add(peer); + } - if (!RaftUtils.configurationEquals(oldConf, conf)) { - // 2. The new peer joins as a learner. - // 5. peer switching, add new peer, delete old peer - FutureClosure closure = new FutureClosure(); + closure = new FutureClosure(); raftNode.changePeers(conf, closure); - if (closure.get().isOk()) { - if (!removedPeers.isEmpty()) { - removedPeers.forEach(peer -> Utils.runInThread(() -> { - // 6. Stop the deleted peer - rpcClient.destroyRaftNode(peer, - partitionManager.getPartitionList(getGroupId()), - status -> { - if (!status.isOk()) { - // TODO: What if it fails? - log.error("Raft {} destroy node {}" + - " error {}", - options.getGroupId(), peer, - status); - } - }); - })); - } + var status = closure.get(); + + if (!status.isOk()) { + log.error("Raft {} changePeers error after destroy, result:{}", getGroupId(), + status); + return HgRaftError.TASK_ERROR.toStatus(); } else { - // Failed, retrying - result = HgRaftError.TASK_ERROR.toStatus(); + for (var peer : toDestroy) { + closure = new FutureClosure(); + rpcClient.destroyRaftNode(peer, partitionManager.getPartitionList(getGroupId()), + closure); + log.info("Raft {} destroy raft node {}, result:{}", peer, getGroupId(), + closure.get()); + } + } + + // transfer leadership to any peer + if (removeSelf) { + raftNode.transferLeadershipTo(PeerId.ANY_PEER); } - log.info("Raft {} changePeers result {}, conf is {}", - getRaftNode().getGroupId(), closure.get(), conf); } - log.info("Raft {} changePeers end. {}, result is {}", getGroupId(), peers, result); - return result; + + return removeSelf ? HgRaftError.TASK_CONTINUE.toStatus() : HgRaftError.OK.toStatus(); } public void addRaftTask(RaftOperation operation, RaftClosure closure) { @@ -438,7 +446,7 @@ public void addRaftTask(RaftOperation operation, RaftClosure closure) { } final Task task = new Task(); task.setData(ByteBuffer.wrap(operation.getValues())); - task.setDone(new HgStoreStateMachine.RaftClosureAdapter(operation, closure)); + task.setDone(new DefaultRaftClosure(operation, closure)); this.raftNode.apply(task); } @@ -447,9 +455,6 @@ public void shutdown() { if (!this.started) { return; } - - partitionManager.updateShardGroup(shardGroup); - if (this.raftGroupService != null) { this.raftGroupService.shutdown(); try { @@ -521,8 +526,8 @@ public void addStateListener(PartitionStateListener listener) { public Map getAlivePeers() { Map peers = new HashMap<>(); raftNode.listAlivePeers().forEach(peerId -> { - Shard shard = partitionManager.getShardByRaftEndpoint(shardGroup, - peerId.getEndpoint().toString()); + Shard shard = partitionManager.getShardByEndpoint(shardGroup, + peerId.getEndpoint().toString()); if (shard != null) { peers.put(shard.getStoreId(), peerId); } @@ -629,7 +634,9 @@ public void onStartFollowing(final PeerId newLeaderId, final long newTerm) { */ @Override public void onConfigurationCommitted(Configuration conf) { - + if (storeEngine.isClosing().get()) { + return; + } try { // Update shardlist log.info("Raft {} onConfigurationCommitted, conf is {}", getGroupId(), conf.toString()); @@ -661,10 +668,19 @@ public void onConfigurationCommitted(Configuration conf) { // partitionManager.changeShards(partition, shardGroup.getMetaPbShard()); // }); try { - var pdGroup = storeEngine.getPdProvider().getShardGroup(getGroupId()); + var pdGroup = storeEngine.getPdProvider().getShardGroupDirect(getGroupId()); List peers = partitionManager.shards2Peers(pdGroup.getShardsList()); - if (!ListUtils.isEqualList(peers, RaftUtils.getPeerEndpoints(raftNode))) { + Long leaderStoreId = null; + for (var shard : pdGroup.getShardsList()) { + if (shard.getRole() == Metapb.ShardRole.Leader) { + leaderStoreId = shard.getStoreId(); + } + } + // leader 不同,peers 不同,learner 不同,都要更新 pd 信息 + if (!SetUtils.isEqualSet(peers, RaftUtils.getPeerEndpoints(raftNode)) || + !SetUtils.isEqualSet(learners, RaftUtils.getLearnerEndpoints(raftNode)) || + !Objects.equals(leaderStoreId, partitionManager.getStore().getId())) { partitionManager.getPdProvider().updateShardGroup(shardGroup.getProtoObj()); } @@ -738,99 +754,46 @@ public Status transferLeader(String graphName, Metapb.Shard shard) { * 4. After the snapshot synchronization is completed, call changePeers, change the learner to follower, and delete the old peer. */ public void doChangeShard(final MetaTask.Task task, Closure done) { - if (!isLeader()) { - return; - } + try { + if (!isLeader() || !changingPeer.compareAndSet(false, true)) { + return; + } - log.info("Raft {} doChangeShard task is {}", getGroupId(), task); - // If the same partition has the same task executing, ignore task execution. - if (taskManager.partitionTaskRepeat(task.getPartition().getId(), - task.getPartition().getGraphName(), - task.getType().name())) { - log.error("Raft {} doChangeShard task repeat, type:{}", getGroupId(), task.getType()); - return; - } - // Task not completed, repeat execution. - if (task.getState().getNumber() < MetaTask.TaskState.Task_Stop_VALUE && isLeader()) { + log.info("Raft {} doChangeShard task is {}", getGroupId(), task); Utils.runInThread(() -> { + List peers = + partitionManager.shards2Peers(task.getChangeShard().getShardList()); + HashSet hashSet = new HashSet<>(peers); + try { - // cannot changePeers in the state machine - List peers = - partitionManager.shards2Peers(task.getChangeShard().getShardList()); - HashSet hashSet = new HashSet<>(peers); - // Task has the same peers, indicating there is an error in the task itself, task ignored + // 任务中有相同的 peers,说明任务本身有错误,任务忽略 if (peers.size() != hashSet.size()) { - log.info("Raft {} doChangeShard peer is repeat, peers: {}", getGroupId(), + log.info("Raft {} doChangeShard peer is repeat, peers:{}", getGroupId(), peers); + return; } - Status result; - if (changingPeer.compareAndSet(false, true)) { - result = this.changePeers(peers, done); - } else { - result = HgRaftError.TASK_ERROR.toStatus(); - } - - if (result.getCode() != HgRaftError.TASK_CONTINUE.getNumber()) { - log.info("Raft {} doChangeShard is finished, status is {}", getGroupId(), - result); - // Task completed, synchronize task status - MetaTask.Task newTask; - if (result.isOk()) { - newTask = task.toBuilder().setState(MetaTask.TaskState.Task_Success) - .build(); - } else { - log.warn( - "Raft {} doChangeShard is failure, need to retry, status is {}", - getGroupId(), result); - try { - // Reduce send times - Thread.sleep(1000); - } catch (Exception e) { - log.error("wait 1s to resend retry task. got error:{}", - e.getMessage()); - } - newTask = task.toBuilder().setState(MetaTask.TaskState.Task_Ready) - .build(); - } - try { - // During the waiting process, it may have already shut down. - if (isLeader()) { - storeEngine.addRaftTask(newTask.getPartition().getGraphName(), - newTask.getPartition().getId(), - RaftOperation.create( - RaftOperation.SYNC_PARTITION_TASK, - newTask), - status -> { - if (!status.isOk()) { - log.error( - "Raft {} addRaftTask " + - "error, status is {}", - newTask.getPartition() - .getId(), status); - } - } - ); - } - } catch (Exception e) { - log.error("Partition {}-{} update task state exception {}", - task.getPartition().getGraphName(), - task.getPartition().getId(), e); - } - // db might have been destroyed, do not update anymore - if (this.started) { - taskManager.updateTask(newTask); - } - } else { - log.info("Raft {} doChangeShard not finished", getGroupId()); + Status result = changePeers(peers, null); + + if (result.getCode() == HgRaftError.TASK_CONTINUE.getNumber()) { + // 需要重新发送一个 request + storeEngine.addRaftTask(task.getPartition().getGraphName(), + task.getPartition().getId(), RaftOperation.create( + RaftOperation.SYNC_PARTITION_TASK, task), status -> { + if (!status.isOk()) { + log.error( + "Raft {} addRaftTask error, " + "status " + "is {}", + task.getPartition().getId(), status); + } + }); } + log.info("Raft {} doChangeShard result is {}", getGroupId(), result); } catch (Exception e) { log.error("Raft {} doChangeShard exception {}", getGroupId(), e); } finally { changingPeer.set(false); } }); - } else { - // Whether the message has been processed + } finally { if (done != null) { done.run(Status.OK()); } @@ -917,7 +880,7 @@ private Status handleSplitTask(MetaTask.Task task) { storeEngine.createPartitionGroups(new Partition(newPartitions.get(i))); } // Copy data from the source machine to the target machine - status = storeEngine.getDataMover().moveData(task.getPartition(), newPartitions); + status = storeEngine.getDataManager().move(task.getPartition(), newPartitions); if (status.isOk()) { var source = Metapb.Partition.newBuilder(targets.get(0)) @@ -925,7 +888,7 @@ private Status handleSplitTask(MetaTask.Task task) { .build(); // Update local key range, and synchronize follower partitionManager.updatePartition(source, true); - storeEngine.getDataMover().updatePartitionRange(source, + partitionManager.updateRange(source, (int) source.getStartKey(), (int) source.getEndKey()); } @@ -955,7 +918,7 @@ private Status handleMoveTask(MetaTask.Task task) { task.getPartition().getGraphName(), task.getPartition().getId(), task.getMovePartition().getTargetPartition().getId()); - status = storeEngine.getDataMover().moveData(task.getPartition(), + status = storeEngine.getDataManager().move(task.getPartition(), task.getMovePartition() .getTargetPartition()); } catch (Exception e) { @@ -1051,7 +1014,7 @@ private void handleCleanOp(CleanDataRequest request) { partitionManager.getPartition(request.getGraphName(), request.getPartitionId()); if (partition != null) { - storeEngine.getDataMover().doCleanData(request); + storeEngine.getDataManager().clean(request); storeEngine.getBusinessHandler() .dbCompaction(partition.getGraphName(), partition.getId()); @@ -1087,6 +1050,97 @@ private void handleCleanOp(CleanDataRequest request) { } } + public void buildIndex(MetaTask.Task task) { + + var state = MetaTask.TaskState.Task_Failure; + String message = "SUCCESS"; + try { + var status = storeEngine.getDataManager().doBuildIndex(task.getBuildIndex().getParam(), + task.getPartition()); + if (status.isOk()) { + state = MetaTask.TaskState.Task_Success; + } else { + message = status.getErrorMsg(); + } + + } catch (Exception e) { + message = e.getMessage() == null ? "UNKNOWN" : e.getMessage(); + log.error("build index error:", e); + } + + try { + partitionManager.reportTask( + task.toBuilder().setState(state).setMessage(message).build()); + } catch (Exception e) { + log.error("report task failed: error :", e); + } + + } + + public void doSnapshotSync(Closure done) { + long lastIndex = raftNode.getLastAppliedLogIndex(); + BusinessHandler handler = storeEngine.getBusinessHandler(); + Integer groupId = getGroupId(); + String lockPath = handler.getLockPath(groupId); + AtomicInteger state = handler.getState(groupId); + if (state != null && state.get() == BusinessHandler.compactionDone) { + log.info("Partition {},path:{} prepare to doSnapshotSync", this.getGroupId(), lockPath); + BusinessHandlerImpl.getCompactionPool().execute(() -> { + try { + long start = System.currentTimeMillis(); + while ((System.currentTimeMillis() - start) < 5000 && + raftNode.getLastAppliedLogIndex() == lastIndex) { + synchronized (state) { + state.wait(200); + } + } + log.info("Partition {},path:{} begin to doSnapshotSync", this.getGroupId(), + lockPath); + raftNode.getRaftOptions().setTruncateLog(true); + CountDownLatch latch = new CountDownLatch(1); + AtomicReference result = new AtomicReference<>(); + raftNode.snapshot(status -> { + result.set(status); + try { + raftNode.getRaftOptions().setTruncateLog(false); + latch.countDown(); + log.info("Partition {},path: {} doSnapshotSync result : {}. ", groupId, + lockPath, status); + } catch (Exception e) { + log.error("wait doSnapshotSync with error:", e); + } finally { + handler.setAndNotifyState(groupId, BusinessHandler.compactionCanStart); + handler.unlock(lockPath); + log.info("Partition {},path: {} release dbCompaction lock", groupId, + lockPath); + } + }); + latch.await(); + } catch (Exception e) { + log.error("doSnapshotSync with error:", e); + handler.setAndNotifyState(groupId, BusinessHandler.compactionCanStart); + handler.unlock(lockPath); + } + }); + } + if (done != null) { + done.run(Status.OK()); + } + } + + public void doBlankTaskSync(Closure done) { + try { + doSnapshotSync(done); + } catch (Exception e) { + Integer groupId = getGroupId(); + // String msg = String.format("Partition %s blank task done with error:", groupId); + //log.error(msg, e); + if (done != null) { + done.run(new Status(-1, e.getMessage())); + } + } + } + public Configuration getCurrentConf() { return new Configuration(this.raftNode.listPeers(), this.raftNode.listLearners()); } @@ -1192,7 +1246,9 @@ public boolean invoke(final int groupId, byte[] request, invoke(groupId, methodId, Metapb.Partition.parseFrom(input), response); break; case RaftOperation.DO_SNAPSHOT: + case RaftOperation.DO_SYNC_SNAPSHOT: case RaftOperation.BLANK_TASK: + case RaftOperation.SYNC_BLANK_TASK: invoke(groupId, methodId, null, response); break; case RaftOperation.IN_WRITE_OP: @@ -1236,7 +1292,7 @@ public boolean invoke(final int groupId, byte methodId, Object req, doSnapshot(response); break; case RaftOperation.IN_WRITE_OP: - storeEngine.getDataMover().doWriteData((BatchPutRequest) (req)); + storeEngine.getDataManager().write((BatchPutRequest) (req)); break; case RaftOperation.IN_CLEAN_OP: handleCleanOp((CleanDataRequest) req); @@ -1253,6 +1309,12 @@ public boolean invoke(final int groupId, byte methodId, Object req, dbCompactionRequest.getPartitionId(), dbCompactionRequest.getTableName()); break; + case RaftOperation.DO_SYNC_SNAPSHOT: + doSnapshotSync(response); + break; + case RaftOperation.SYNC_BLANK_TASK: + doBlankTaskSync(response); + break; default: return false; } diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/PartitionInstructionProcessor.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/PartitionInstructionProcessor.java index 65830b7ba8..a57fadea84 100644 --- a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/PartitionInstructionProcessor.java +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/PartitionInstructionProcessor.java @@ -17,7 +17,6 @@ package org.apache.hugegraph.store; -import java.io.IOException; import java.util.List; import java.util.concurrent.ExecutorService; import java.util.concurrent.LinkedBlockingQueue; @@ -36,8 +35,8 @@ import org.apache.hugegraph.pd.grpc.pulse.PartitionKeyRange; import org.apache.hugegraph.pd.grpc.pulse.SplitPartition; import org.apache.hugegraph.pd.grpc.pulse.TransferLeader; -import org.apache.hugegraph.store.cmd.CleanDataRequest; -import org.apache.hugegraph.store.cmd.DbCompactionRequest; +import org.apache.hugegraph.store.cmd.request.CleanDataRequest; +import org.apache.hugegraph.store.cmd.request.DbCompactionRequest; import org.apache.hugegraph.store.meta.MetadataKeyHelper; import org.apache.hugegraph.store.meta.Partition; import org.apache.hugegraph.store.pd.PartitionInstructionListener; @@ -53,6 +52,7 @@ /** * PD sends partition instruction processor to Store */ +@Deprecated public class PartitionInstructionProcessor implements PartitionInstructionListener { private static final Logger LOG = Log.logger(PartitionInstructionProcessor.class); @@ -309,9 +309,6 @@ public void onPartitionKeyRangeChanged(long taskId, Partition partition, }); LOG.info("onPartitionKeyRangeChanged: {}, update to pd", newPartition); partitionManager.updatePartitionToPD(List.of(newPartition)); - } catch (IOException e) { - LOG.error("Partition {}-{} onPartitionKeyRangeChanged exception {}", - newPartition.getGraphName(), newPartition.getId(), e); } catch (PDException e) { throw new RuntimeException(e); } diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/PartitionStateListener.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/PartitionStateListener.java index ad73f95e8a..349ddc3812 100644 --- a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/PartitionStateListener.java +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/PartitionStateListener.java @@ -22,7 +22,7 @@ import org.apache.hugegraph.pd.grpc.Metapb; import org.apache.hugegraph.store.meta.Partition; import org.apache.hugegraph.store.meta.PartitionRole; - +@Deprecated public interface PartitionStateListener { // Partition role changed diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/business/AbstractSelectIterator.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/business/AbstractSelectIterator.java index 88c71dc9a9..353464f8c6 100644 --- a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/business/AbstractSelectIterator.java +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/business/AbstractSelectIterator.java @@ -17,14 +17,10 @@ package org.apache.hugegraph.store.business; -import org.apache.hugegraph.backend.serializer.AbstractSerializer; -import org.apache.hugegraph.backend.serializer.BinarySerializer; -import org.apache.hugegraph.backend.store.BackendEntry; -import org.apache.hugegraph.iterator.CIter; +import org.apache.hugegraph.backend.BackendColumn; import org.apache.hugegraph.rocksdb.access.ScanIterator; -import org.apache.hugegraph.structure.HugeElement; -import org.apache.hugegraph.util.Bytes; -import org.apache.tinkerpop.gremlin.structure.Edge; +import org.apache.hugegraph.serializer.BinaryElementSerializer; +import org.apache.hugegraph.structure.BaseElement; import lombok.extern.slf4j.Slf4j; @@ -32,36 +28,17 @@ public abstract class AbstractSelectIterator implements ScanIterator { protected ScanIterator iterator; - protected AbstractSerializer serializer; + protected BinaryElementSerializer serializer; public AbstractSelectIterator() { - this.serializer = new BinarySerializer(); + this.serializer = new BinaryElementSerializer(); } - public boolean belongToMe(BackendEntry entry, - BackendEntry.BackendColumn column) { - return Bytes.prefixWith(column.name, entry.id().asBytes()); - } - - public HugeElement parseEntry(BackendEntry entry, boolean isVertex) { - try { - if (isVertex) { - return this.serializer.readVertex(null, entry); - } else { - CIter itr = - this.serializer.readEdges(null, entry); - - // Iterator itr = this.serializer.readEdges( - // null, entry, true, false).iterator(); - HugeElement el = null; - if (itr.hasNext()) { - el = (HugeElement) itr.next(); - } - return el; - } - } catch (Exception e) { - log.error("Failed to parse entry: {}", entry, e); - throw e; + public BaseElement parseEntry(BackendColumn column, boolean isVertex) { + if (isVertex) { + return serializer.parseVertex(null, column, null); + } else { + return serializer.parseEdge(null, column, null, true); } } } diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/business/BusinessHandler.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/business/BusinessHandler.java index 824d4ada77..31b336b735 100644 --- a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/business/BusinessHandler.java +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/business/BusinessHandler.java @@ -19,6 +19,9 @@ import java.util.List; import java.util.Map; +import java.util.Set; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Consumer; import java.util.function.Supplier; @@ -26,13 +29,16 @@ import org.apache.hugegraph.pd.grpc.pulse.CleanType; import org.apache.hugegraph.rocksdb.access.ScanIterator; +import org.apache.hugegraph.store.constant.HugeServerTables; import org.apache.hugegraph.store.grpc.Graphpb; import org.apache.hugegraph.store.grpc.common.Key; import org.apache.hugegraph.store.grpc.common.OpType; +import org.apache.hugegraph.store.grpc.query.DeDupOption; import org.apache.hugegraph.store.grpc.session.BatchEntry; import org.apache.hugegraph.store.meta.base.DBSessionBuilder; import org.apache.hugegraph.store.metric.HgStoreMetric; -import org.apache.hugegraph.store.raft.HgStoreStateMachine; +import org.apache.hugegraph.store.query.QueryTypeParam; +import org.apache.hugegraph.store.raft.PartitionStateMachine; import org.apache.hugegraph.store.term.HgPair; import org.apache.hugegraph.store.util.HgStoreException; import org.rocksdb.Cache; @@ -40,23 +46,16 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -public interface BusinessHandler extends DBSessionBuilder { +import com.google.protobuf.ByteString; - Logger log = LoggerFactory.getLogger(HgStoreStateMachine.class); - String tableUnknown = "unknown"; - String tableVertex = "g+v"; - String tableOutEdge = "g+oe"; - String tableInEdge = "g+ie"; - String tableIndex = "g+index"; - String tableTask = "g+task"; - String tableOlap = "g+olap"; - String tableServer = "g+server"; +public interface BusinessHandler extends DBSessionBuilder { - String[] tables = new String[]{tableUnknown, tableVertex, tableOutEdge, tableInEdge, tableIndex, - tableTask, tableOlap, tableServer}; + Logger log = LoggerFactory.getLogger(PartitionStateMachine.class); + int compactionCanStart = 0; + int compactionDone = 1; + int doing = -1; - void doPut(String graph, int code, String table, byte[] key, byte[] value) throws - HgStoreException; + void doPut(String graph, int code, String table, byte[] key, byte[] value) throws HgStoreException; byte[] doGet(String graph, int code, String table, byte[] key) throws HgStoreException; @@ -66,8 +65,15 @@ void doPut(String graph, int code, String table, byte[] key, byte[] value) throw ScanIterator scan(String graph, String table, int codeFrom, int codeTo) throws HgStoreException; - ScanIterator scan(String graph, int code, String table, byte[] start, byte[] end, - int scanType) throws HgStoreException; + ScanIterator scan(String graph, int code, String table, byte[] start, + byte[] end, int scanType) throws HgStoreException; + + /** + * primary index scan + */ + ScanIterator scan(String graph, String table, List params, + DeDupOption dedupOption) + throws HgStoreException; ScanIterator scan(String graph, int code, String table, byte[] start, byte[] end, int scanType, byte[] conditionQuery) throws HgStoreException; @@ -76,11 +82,17 @@ ScanIterator scan(String graph, int code, String table, byte[] start, byte[] end ScanIterator scanOriginal(Graphpb.ScanPartitionRequest request); - ScanIterator scanPrefix(String graph, int code, String table, byte[] prefix, - int scanType) throws HgStoreException; + ScanIterator scanPrefix(String graph, int code, String table, byte[] prefix, int scanType) throws HgStoreException; + + ScanIterator scanPrefix(String graph, int code, String table, byte[] prefix) throws HgStoreException; - ScanIterator scanPrefix(String graph, int code, String table, byte[] prefix) throws - HgStoreException; + ScanIterator scanIndex(String graph, List> param, + DeDupOption dedupOption, boolean transElement, boolean filterTTL) throws HgStoreException; + + ScanIterator scanIndex(String graph, String table, List> params, + DeDupOption dedupOption, boolean lookupBack, boolean transKey, + boolean filterTTL, int limit) + throws HgStoreException; HgStoreMetric.Partition getPartitionMetric(String graph, int partId, boolean accurateCount) throws HgStoreException; @@ -92,6 +104,8 @@ void batchGet(String graph, String table, Supplier> s, void flushAll(); + void closeDB(int partId); + void closeAll(); // @@ -99,6 +113,8 @@ void batchGet(String graph, String table, Supplier> s, List getLeaderPartitionIds(String graph); + Set getLeaderPartitionIdSet(); + HgStoreMetric.Graph getGraphMetric(String graph, int partId); void saveSnapshot(String snapshotPath, String graph, int partId) throws HgStoreException; @@ -129,12 +145,14 @@ boolean cleanPartition(String graph, int partId, long startKey, long endKey, TxBuilder txBuilder(String graph, int partId); + boolean cleanTtl(String graph, int partId, String table, List ids); + default void doBatch(String graph, int partId, List entryList) { BusinessHandler.TxBuilder builder = txBuilder(graph, partId); try { for (BatchEntry b : entryList) { Key start = b.getStartKey(); - String table = tables[b.getTable()]; + String table = HugeServerTables.TABLES[b.getTable()]; byte[] startKey = start.getKey().toByteArray(); int number = b.getOpType().getNumber(); if (number == OpType.OP_TYPE_PUT_VALUE) { @@ -186,10 +204,26 @@ default void doBatch(String graph, int partId, List entryList) { boolean dbCompaction(String graphName, int partitionId, String tableName); + boolean blockingCompact(String graphName, int partitionId); + void destroyGraphDB(String graphName, int partId) throws HgStoreException; long count(String graphName, String table); + void lock(String path) throws InterruptedException, + TimeoutException; + void unlock(String path); + + void awaitAndSetLock(int id, int expectedValue, int value) throws InterruptedException, + TimeoutException; + void setAndNotifyState(int id, int state); + + AtomicInteger getState(int id); + + String getLockPath(int partitionId); + + List getPartitionIds(String graph); + @NotThreadSafe interface TxBuilder { diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/business/BusinessHandlerImpl.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/business/BusinessHandlerImpl.java index 6421082cf1..3f63bb79a1 100644 --- a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/business/BusinessHandlerImpl.java +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/business/BusinessHandlerImpl.java @@ -17,28 +17,48 @@ package org.apache.hugegraph.store.business; -import static org.apache.hugegraph.store.util.HgStoreConst.EMPTY_BYTES; +import static org.apache.hugegraph.store.business.MultiPartitionIterator.EMPTY_BYTES; +import static org.apache.hugegraph.store.constant.HugeServerTables.INDEX_TABLE; +import static org.apache.hugegraph.store.constant.HugeServerTables.IN_EDGE_TABLE; +import static org.apache.hugegraph.store.constant.HugeServerTables.OUT_EDGE_TABLE; +import static org.apache.hugegraph.store.constant.HugeServerTables.VERTEX_TABLE; import static org.apache.hugegraph.store.util.HgStoreConst.SCAN_ALL_PARTITIONS_ID; +import java.io.File; import java.io.IOException; import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.Semaphore; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicInteger; import java.util.function.BiFunction; import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Supplier; +import java.util.function.ToLongFunction; import java.util.stream.Collectors; import javax.annotation.concurrent.NotThreadSafe; -import org.apache.commons.configuration2.MapConfiguration; +import org.apache.commons.io.FileUtils; import org.apache.commons.lang.ArrayUtils; import org.apache.commons.lang.StringUtils; +import org.apache.hugegraph.HugeGraphSupplier; +import org.apache.hugegraph.SchemaGraph; +import org.apache.hugegraph.backend.BackendColumn; import org.apache.hugegraph.config.HugeConfig; import org.apache.hugegraph.config.OptionSpace; +import org.apache.hugegraph.id.EdgeId; +import org.apache.hugegraph.id.Id; +import org.apache.hugegraph.pd.client.PDConfig; +import org.apache.hugegraph.pd.common.PartitionUtils; import org.apache.hugegraph.pd.grpc.pulse.CleanType; import org.apache.hugegraph.rocksdb.access.DBStoreException; import org.apache.hugegraph.rocksdb.access.RocksDBFactory; @@ -47,52 +67,83 @@ import org.apache.hugegraph.rocksdb.access.RocksDBSession; import org.apache.hugegraph.rocksdb.access.ScanIterator; import org.apache.hugegraph.rocksdb.access.SessionOperator; +import org.apache.hugegraph.serializer.BinaryElementSerializer; +import org.apache.hugegraph.serializer.BytesBuffer; +import org.apache.hugegraph.serializer.DirectBinarySerializer; import org.apache.hugegraph.store.HgStoreEngine; -import org.apache.hugegraph.store.cmd.CleanDataRequest; +import org.apache.hugegraph.store.PartitionEngine; +import org.apache.hugegraph.store.business.itrv2.BatchGetIterator; +import org.apache.hugegraph.store.business.itrv2.InAccurateIntersectionIterator; +import org.apache.hugegraph.store.business.itrv2.InAccurateUnionFilterIterator; +import org.apache.hugegraph.store.business.itrv2.IntersectionFilterIterator; +import org.apache.hugegraph.store.business.itrv2.IntersectionWrapper; +import org.apache.hugegraph.store.business.itrv2.MapJoinIterator; +import org.apache.hugegraph.store.business.itrv2.MapLimitIterator; +import org.apache.hugegraph.store.business.itrv2.MapUnionIterator; +import org.apache.hugegraph.store.business.itrv2.MultiListIterator; +import org.apache.hugegraph.store.business.itrv2.TypeTransIterator; +import org.apache.hugegraph.store.business.itrv2.UnionFilterIterator; +import org.apache.hugegraph.store.business.itrv2.io.SortShuffleSerializer; +import org.apache.hugegraph.store.cmd.HgCmdClient; +import org.apache.hugegraph.store.cmd.request.BlankTaskRequest; +import org.apache.hugegraph.store.cmd.request.CleanDataRequest; +import org.apache.hugegraph.store.consts.PoolNames; import org.apache.hugegraph.store.grpc.Graphpb.ScanPartitionRequest; import org.apache.hugegraph.store.grpc.Graphpb.ScanPartitionRequest.Request; import org.apache.hugegraph.store.grpc.Graphpb.ScanPartitionRequest.ScanType; +import org.apache.hugegraph.store.grpc.query.DeDupOption; import org.apache.hugegraph.store.meta.Partition; import org.apache.hugegraph.store.meta.PartitionManager; import org.apache.hugegraph.store.meta.asynctask.AsyncTaskState; import org.apache.hugegraph.store.meta.asynctask.CleanTask; import org.apache.hugegraph.store.metric.HgStoreMetric; +import org.apache.hugegraph.store.pd.DefaultPdProvider; import org.apache.hugegraph.store.pd.PdProvider; +import org.apache.hugegraph.store.query.QueryTypeParam; +import org.apache.hugegraph.store.raft.RaftClosure; +import org.apache.hugegraph.store.raft.RaftOperation; import org.apache.hugegraph.store.term.Bits; import org.apache.hugegraph.store.term.HgPair; +import org.apache.hugegraph.store.util.ExecutorUtil; import org.apache.hugegraph.store.util.HgStoreException; +import org.apache.hugegraph.structure.BaseElement; +import org.apache.hugegraph.util.Bytes; import org.rocksdb.Cache; import org.rocksdb.MemoryUsageType; import com.alipay.sofa.jraft.util.Utils; +import com.google.protobuf.ByteString; import lombok.extern.slf4j.Slf4j; @Slf4j public class BusinessHandlerImpl implements BusinessHandler { + private static final Map GRAPH_SUPPLIER_CACHE = + new ConcurrentHashMap<>(); private static final int batchSize = 10000; + private static Long indexDataSize = 50 * 1024L; private static final RocksDBFactory factory = RocksDBFactory.getInstance(); private static final HashMap tableMapping = new HashMap<>() {{ - put(ScanType.SCAN_VERTEX, tableVertex); - put(ScanType.SCAN_EDGE, tableOutEdge); + put(ScanType.SCAN_VERTEX, VERTEX_TABLE); + put(ScanType.SCAN_EDGE, OUT_EDGE_TABLE); }}; private static final Map dbNames = new ConcurrentHashMap<>(); - - static { - int code = tableUnknown.hashCode(); - code = tableVertex.hashCode(); - code = tableOutEdge.hashCode(); - code = tableInEdge.hashCode(); - code = tableIndex.hashCode(); - code = tableTask.hashCode(); - code = tableTask.hashCode(); - log.debug("init table code:{}", code); - } - + private static HugeGraphSupplier mockGraphSupplier = null; + private static final int compactionThreadCount = 64; + private static final ConcurrentMap pathLock = new ConcurrentHashMap<>(); + private static final ConcurrentMap compactionState = + new ConcurrentHashMap<>(); + private static final ThreadPoolExecutor compactionPool = + ExecutorUtil.createExecutor(PoolNames.COMPACT, compactionThreadCount, + compactionThreadCount * 4, Integer.MAX_VALUE); + private static final int timeoutMillis = 6 * 3600 * 1000; + private final BinaryElementSerializer serializer = BinaryElementSerializer.getInstance(); + private final DirectBinarySerializer directBinarySerializer = new DirectBinarySerializer(); private final PartitionManager partitionManager; private final PdProvider provider; private final InnerKeyCreator keyCreator; + private final Semaphore semaphore = new Semaphore(1); public BusinessHandlerImpl(PartitionManager partitionManager) { this.partitionManager = partitionManager; @@ -122,7 +173,7 @@ public static HugeConfig initRocksdb(Map rocksdbConfig, // Register rocksdb configuration OptionSpace.register("rocksdb", "org.apache.hugegraph.rocksdb.access.RocksDBOptions"); RocksDBOptions.instance(); - HugeConfig hConfig = new HugeConfig(new MapConfiguration(rocksdbConfig)); + HugeConfig hConfig = new HugeConfig(rocksdbConfig); factory.setHugeConfig(hConfig); if (listener != null) { factory.addRocksdbChangedListener(listener); @@ -130,6 +181,27 @@ public static HugeConfig initRocksdb(Map rocksdbConfig, return hConfig; } + public static void setIndexDataSize(long dataSize) { + if (dataSize > 0) { + indexDataSize = dataSize; + } + } + + /** + * FNV hash method + * + * @param key hash input + * @return a long hash value + */ + public static Long fnvHash(byte[] key) { + long rv = 0xcbf29ce484222325L; + for (var b : key) { + rv ^= b; + rv *= 0x100000001b3L; + } + return rv; + } + public static String getDbName(int partId) { String dbName = dbNames.get(partId); if (dbName == null) { @@ -140,6 +212,40 @@ public static String getDbName(int partId) { return dbName; } + public static ThreadPoolExecutor getCompactionPool() { + return compactionPool; + } + + /** + * used for testing, setting fake graph supplier + * + * @param supplier + */ + public static void setMockGraphSupplier(HugeGraphSupplier supplier) { + mockGraphSupplier = supplier; + } + + public static HugeGraphSupplier getGraphSupplier(String graph) { + if (mockGraphSupplier != null) { + return mockGraphSupplier; + } + + if (GRAPH_SUPPLIER_CACHE.get(graph) == null) { + synchronized (BusinessHandlerImpl.class) { + if (GRAPH_SUPPLIER_CACHE.get(graph) == null) { + var config = + PDConfig.of(HgStoreEngine.getInstance().getOption().getPdAddress()); + config.setAuthority(DefaultPdProvider.name, DefaultPdProvider.authority); + String[] parts = graph.split("/"); + assert (parts.length > 1); + GRAPH_SUPPLIER_CACHE.put(graph, new SchemaGraph(parts[0], parts[1], config)); + } + } + } + + return GRAPH_SUPPLIER_CACHE.get(graph); + } + @Override public void doPut(String graph, int code, String table, byte[] key, byte[] value) throws HgStoreException { @@ -149,7 +255,7 @@ public void doPut(String graph, int code, String table, byte[] key, byte[] value SessionOperator op = dbSession.sessionOp(); try { op.prepare(); - byte[] targetKey = keyCreator.getKey(partId, graph, code, key); + byte[] targetKey = keyCreator.getKeyOrCreate(partId, graph, code, key); op.put(table, targetKey, value); op.commit(); } catch (Exception e) { @@ -163,6 +269,9 @@ public void doPut(String graph, int code, String table, byte[] key, byte[] value @Override public byte[] doGet(String graph, int code, String table, byte[] key) throws HgStoreException { int partId = provider.getPartitionByCode(graph, code).getId(); + if (!partitionManager.hasPartition(graph, partId)) { + return null; + } try (RocksDBSession dbSession = getSession(graph, table, partId)) { byte[] targetKey = keyCreator.getKey(partId, graph, code, key); @@ -231,6 +340,76 @@ public ScanIterator scan(String graph, int code, String table, byte[] start, byt return MultiPartitionIterator.of(ids, function); } + /** + * 将 id scan 合并成一个 list,其他的调用 scan 函数 + * + * @param graph graph + * @param table table + * @param params primary scan params + * @param dedupOption de-duplicate option, 0: none, 1: none-exactly 2: exactly + * @return an iterator + * @throws HgStoreException when get db session fail + */ + @Override + public ScanIterator scan(String graph, String table, List params, + DeDupOption dedupOption) throws HgStoreException { + + var iterator = scan(graph, table, params); + + if (!(iterator instanceof MultiListIterator)) { + return iterator; + } + + switch (dedupOption) { + case NONE: + return iterator; + case DEDUP: + return new InAccurateUnionFilterIterator<>(iterator, + BusinessHandlerImpl::getColumnByteHash); + case LIMIT_DEDUP: + return new MapLimitIterator<>(iterator); + case PRECISE_DEDUP: + // todo: 优化? + var wrapper = + new IntersectionWrapper<>(iterator, BusinessHandlerImpl::getColumnByteHash); + wrapper.proc(); + // 再次扫描一遍 + return new UnionFilterIterator<>(scan(graph, table, params), wrapper, + (o1, o2) -> Arrays.compare(o1.name, o2.name), + SortShuffleSerializer.ofBackendColumnSerializer()); + default: + return null; + } + } + + private ScanIterator scan(String graph, String table, List params) throws + HgStoreException { + // id scan 单独放到一个列表中 + var idList = params.stream().filter(QueryTypeParam::isIdScan).collect(Collectors.toList()); + + var itr = new MultiListIterator(); + for (var param : params) { + if (param.isPrefixScan()) { + // prefix scan + itr.addIterator(scanPrefix(graph, param.getCode(), table, param.getStart(), + param.getBoundary())); + } else if (param.isRangeScan()) { + // ranged scan + itr.addIterator( + scan(graph, param.getCode(), table, param.getStart(), param.getEnd(), + param.getBoundary())); + } + } + + if (!idList.isEmpty()) { + itr.addIterator(new BatchGetIterator(idList.iterator(), + idParam -> doGet(graph, idParam.getCode(), table, + idParam.getStart()))); + } + + return itr.getIterators().size() == 1 ? itr.getIterators().get(0) : itr; + } + /** * According to keyCode range return data, left closed right open. * @@ -283,6 +462,395 @@ public GraphStoreIterator scan(ScanPartitionRequest spr) throws HgStoreException return new GraphStoreIterator(scanOriginal(spr), spr); } + private ToLongFunction getBaseElementHashFunction() { + return value -> fnvHash(value.id().asBytes()); + } + + @Override + public ScanIterator scanIndex(String graph, String table, List> params, + DeDupOption dedupOption, boolean lookupBack, boolean transKey, + boolean filterTTL, int limit) throws HgStoreException { + + ScanIterator result; + + boolean onlyPrimary = + params.stream().allMatch(sub -> sub.size() == 1 && !sub.get(0).isIndexScan()); + + boolean needLookup = lookupBack && !onlyPrimary; + + if (params.size() == 1) { + // no union operation + result = indexIntersection(graph, table, params.get(0), dedupOption, onlyPrimary, + filterTTL, needLookup, limit); + } else { + // 多个索引 + var sub = params.stream() + .map(p2 -> indexIntersection(graph, table, p2, dedupOption, onlyPrimary, + filterTTL, needLookup, limit)) + .collect(Collectors.toList()); + + switch (dedupOption) { + case NONE: + result = new MultiListIterator(sub); + break; + case DEDUP: + result = new InAccurateUnionFilterIterator<>(new MultiListIterator(sub), + BusinessHandlerImpl::getColumnByteHash); + break; + case LIMIT_DEDUP: + result = new MapLimitIterator<>(new MultiListIterator(sub)); + break; + case PRECISE_DEDUP: + if (limit > 0) { + // map limit 去重 + result = new MapLimitIterator( + new MultiListIterator(sub)); + } else { + // union operation + var fileSize = getQueryFileSize(graph, table, getLeaderPartitionIds(graph), + params); + if (fileSize < indexDataSize * params.size()) { + // using map + result = new MapUnionIterator(sub, + col -> Arrays.toString( + col.name)); + } else { + result = new MultiListIterator(sub); + var wrapper = new IntersectionWrapper<>(result, + BusinessHandlerImpl::getColumnByteHash); + wrapper.proc(); + + var round2 = new MultiListIterator(); + for (int i = 0; i < params.size(); i++) { + var itr = sub.get(i); + if (itr instanceof MapJoinIterator) { + // 放到内存的,可以不用重新算了 + ((MapJoinIterator) itr).reset(); + round2.addIterator(itr); + } else { + round2.addIterator( + indexIntersection(graph, table, params.get(i), + dedupOption, onlyPrimary, filterTTL, + needLookup, limit)); + } + } + result = new UnionFilterIterator<>(round2, wrapper, + (o1, o2) -> Arrays.compare(o1.name, + o2.name), + SortShuffleSerializer.ofBackendColumnSerializer()); + } + } + break; + default: + throw new HgStoreException("deduplication option not supported"); + } + } + + if (needLookup) { + // 回查原表 + result = + new TypeTransIterator( + result, column -> { + if (column != null && column.name != null) { + // var id = KeyUtil.getOwnerKey(table, backendColumn.name); + var value = + doGet(graph, PartitionUtils.calcHashcode(column.value), table, + column.name); + if (value != null && value.length > 0) { + return RocksDBSession.BackendColumn.of(column.name, value); + } + } + return null; + }, "lookup-back-table"); + } + return result; + } + + /** + * for no scan: + * case 1: count case, multi param + no dedup + no transElement + * case 2: transElement, one param + dedup + transElement + */ + @Override + public ScanIterator scanIndex(String graph, List> params, + DeDupOption dedupOption, boolean transElement, + boolean filterTTL) throws HgStoreException { + // case 1 + if (!transElement) { + if (params.size() == 1) { + var param = params.get(0).get(0); + if (param.isRangeIndexScan()) { + return scan(graph, param.getCode(), "g+index", param.getStart(), param.getEnd(), + param.getBoundary()); + } else { + return scanPrefix(graph, param.getCode(), "g+index", param.getStart(), + param.getBoundary()); + } + } else { + // todo: change multiListIterator of MultiPartition to ? , + // combine multi id? + var result = new MultiListIterator(); + params.forEach(sub -> { + var param = sub.get(0); + if (param.isRangeIndexScan()) { + result.addIterator(scan(graph, param.getCode(), "g+index", param.getStart(), + param.getEnd(), param.getBoundary())); + } else { + result.addIterator( + scanPrefix(graph, param.getCode(), "g+index", param.getStart(), + param.getBoundary())); + } + }); + return result; + } + } + + // case 2 + var param = params.get(0).get(0); + var result = scanIndexToBaseElement(graph, param, filterTTL); + + switch (dedupOption) { + case NONE: + return result; + case DEDUP: + return new InAccurateUnionFilterIterator<>(result, getBaseElementHashFunction()); + case LIMIT_DEDUP: + return new MapLimitIterator<>(result); + case PRECISE_DEDUP: + var wrapper = new IntersectionWrapper<>(result, getBaseElementHashFunction()); + wrapper.proc(); + return new UnionFilterIterator<>(scanIndexToBaseElement(graph, param, filterTTL), + wrapper, + (o1, o2) -> Arrays.compare(o1.id().asBytes(), + o2.id().asBytes()), + SortShuffleSerializer.ofBaseElementSerializer()); + default: + return null; + } + } + + public ScanIterator indexIntersection(String graph, String table, List params, + DeDupOption dedupOption, boolean onlyPrimary, + boolean filterTTL, boolean lookup, int limit) throws + HgStoreException { + + // 主键查询,不需要去重,只支持一个主键。如果有其他的 index 查询,要按照 BackendColumn 消重,抹除掉 value + if (params.size() == 1 && !params.get(0).isIndexScan()) { + var iterator = scan(graph, table, params); + // 需要删除掉 value,和 index 去重 + return onlyPrimary ? iterator : new TypeTransIterator<>(iterator, + (Function) column -> { + // todo: from key + // to owner key + BaseElement element; + try { + if (IN_EDGE_TABLE.equals( + table) || + OUT_EDGE_TABLE.equals( + table)) { + element = + serializer.parseEdge( + getGraphSupplier( + graph), + BackendColumn.of( + column.name, + column.value), + null, + false); + } else { + element = + serializer.parseVertex( + getGraphSupplier( + graph), + BackendColumn.of( + column.name, + column.value), + null); + } + } catch (Exception e) { + log.error("parse " + + "element " + + "error, " + + "graph" + + " " + + "{}, table," + + " {}", graph, + table, e); + return null; + } + // column.value = + // KeyUtil + // .idToBytes + // (BinaryElementSerializer.ownerId + // (element)); + column.value = + BinaryElementSerializer.ownerId( + element) + .asBytes(); + return column; + }, "replace-pk"); + } + + var iterators = + params.stream().map(param -> scanIndexToElementId(graph, param, filterTTL, lookup)) + .collect(Collectors.toList()); + + // 减少 iterator 层次结构 + ScanIterator result = + params.size() == 1 ? iterators.get(0) : new MultiListIterator(iterators); + + if (dedupOption == DeDupOption.NONE) { + return result; + } else if (dedupOption == DeDupOption.DEDUP) { + return params.size() == 1 ? new InAccurateUnionFilterIterator<>(result, + BusinessHandlerImpl::getColumnByteHash) : + new InAccurateIntersectionIterator<>(result, + BusinessHandlerImpl::getColumnByteHash); + } else if (dedupOption == DeDupOption.PRECISE_DEDUP && limit > 0 || + dedupOption == DeDupOption.LIMIT_DEDUP) { + // 精确去重+limit 使用 map 去重 + return new MapLimitIterator(result); + } else { + // todo: single index need not to deduplication + var ids = this.getLeaderPartitionIds(graph); + var sizes = params.stream().map(param -> getQueryFileSize(graph, "g+v", ids, param)) + .collect(Collectors.toList()); + + log.debug("queries: {} ,sizes : {}", params, sizes); + Long minSize = Long.MAX_VALUE; + int loc = -1; + for (int i = 0; i < sizes.size(); i++) { + if (sizes.get(i) < minSize) { + minSize = sizes.get(i); + loc = i; + } + } + + if (minSize < indexDataSize) { + return new MapJoinIterator(iterators, loc, + col -> Arrays.toString( + col.name)); + } else { + // 只能扫描 2 遍了 + var wrapper = + new IntersectionWrapper<>(result, BusinessHandlerImpl::getColumnByteHash, + true); + wrapper.proc(); + + var r2 = multiIndexIterator(graph, params, filterTTL, lookup); + return params.size() == 1 ? new UnionFilterIterator<>(r2, wrapper, + (o1, o2) -> Arrays.compare( + o1.name, o2.name), + SortShuffleSerializer.ofBackendColumnSerializer()) : + new IntersectionFilterIterator(r2, wrapper, params.size()); + } + } + } + + private long getQueryFileSize(String graph, String table, List partitions, + List> params) { + long total = 0; + for (var sub : params) { + var size = sub.stream().map(param -> getQueryFileSize(graph, + param.isIndexScan() ? "g+index" : + table, partitions, param)) + .min(Long::compareTo); + total += size.get(); + } + return total; + } + + private long getQueryFileSize(String graph, String table, List partitions, + QueryTypeParam param) { + long total = 0; + for (int partId : partitions) { + try (RocksDBSession dbSession = getSession(graph, partId)) { + total += dbSession.getApproximateDataSize(table, param.getStart(), param.getEnd()); + } + } + return total; + } + + private ScanIterator multiIndexIterator(String graph, List params, + boolean filterTTL, boolean lookup) { + var iterators = + params.stream().map(param -> scanIndexToElementId(graph, param, filterTTL, lookup)) + .collect(Collectors.toList()); + return params.size() == 1 ? iterators.get(0) : new MultiListIterator(iterators); + } + + private ScanIterator scanIndexToElementId(String graph, QueryTypeParam param, boolean filterTTL, + boolean lookup) { + long now = System.currentTimeMillis(); + return new TypeTransIterator( + param.isRangeIndexScan() ? + scan(graph, param.getCode(), INDEX_TABLE, param.getStart(), param.getEnd(), + param.getBoundary()) : + scanPrefix(graph, param.getCode(), INDEX_TABLE, param.getStart(), + param.getBoundary()), column -> { + if (filterTTL && isIndexExpire(column, now)) { + return null; + } + + // todo : 后面使用 parseIndex(BackendColumn indexCol) + var index = serializer.parseIndex(getGraphSupplier(graph), + BackendColumn.of(column.name, column.value), null); + + if (param.getIdPrefix() != null && + !Bytes.prefixWith(index.elementId().asBytes(), param.getIdPrefix())) { + return null; + } + + Id elementId = index.elementId(); + if (elementId instanceof EdgeId) { + column.name = new BytesBuffer().writeEdgeId(elementId).bytes(); + } else { + column.name = new BytesBuffer().writeId(elementId).bytes(); + } + + if (lookup) { + // 存放的 owner key + column.value = BinaryElementSerializer.ownerId(index).asBytes(); + // column.value = KeyUtil.idToBytes(BinaryElementSerializer.ownerId(index)); + } + return column; + }, "trans-index-to-element-id"); + } + + private ScanIterator scanIndexToBaseElement(String graph, QueryTypeParam param, + boolean filterTTL) { + + long now = System.currentTimeMillis(); + return new TypeTransIterator( + param.isRangeIndexScan() ? + scan(graph, param.getCode(), INDEX_TABLE, param.getStart(), param.getEnd(), + param.getBoundary()) : + scanPrefix(graph, param.getCode(), INDEX_TABLE, param.getStart(), + param.getBoundary()), column -> { + if (filterTTL && isIndexExpire(column, now)) { + return null; + } + + var e = serializer.index2Element(getGraphSupplier(graph), + BackendColumn.of(column.name, column.value)); + + if (param.getIdPrefix() != null && + !Bytes.prefixWith(e.id().asBytes(), param.getIdPrefix())) { + return null; + } + + return e; + // return new BaseVertex(IdUtil.readLong(String.valueOf(random.nextLong())), + // VertexLabel.GENERAL); + }, "trans-index-to-base-element"); + } + + private boolean isIndexExpire(RocksDBSession.BackendColumn column, long now) { + var e = directBinarySerializer.parseIndex(column.name, column.value); + return e.expiredTime() > 0 && e.expiredTime() < now; + } + @Override public ScanIterator scanOriginal(ScanPartitionRequest spr) throws HgStoreException { Request request = spr.getScanRequest(); @@ -439,7 +1007,8 @@ public void batchGet(String graph, String table, Supplier getLeaderPartitionIds(String graph) { return partitionManager.getLeaderPartitionIds(graph); } + @Override + public Set getLeaderPartitionIdSet() { + return partitionManager.getLeaderPartitionIdSet(); + } + @Override public void saveSnapshot(String snapshotPath, String graph, int partId) throws HgStoreException { @@ -574,7 +1153,8 @@ public boolean cleanPartition(String graph, int partId, long startKey, long endK /** * Clean up partition data, delete data not belonging to this partition. - * Traverse all keys of partId, read code, if code >= splitKey generate a new key, write to newPartId + * Traverse all keys of partId, read code, if code >= splitKey generate a new key, write to + * newPartId */ private boolean cleanPartition(Partition partition, Function belongsFunction) { @@ -671,8 +1251,15 @@ private RocksDBSession getSession(String graphName, int partId) throws HgStoreEx */ @Override public RocksDBSession getSession(int partId) throws HgStoreException { - // Each partition corresponds to a rocksdb instance, so the rocksdb instance name is rocksdb + partId + // Each partition corresponds to a rocksdb instance, so the rocksdb instance name is + // rocksdb + partId String dbName = getDbName(partId); + if (HgStoreEngine.getInstance().isClosing().get()) { + HgStoreException closeException = + new HgStoreException(HgStoreException.EC_CLOSE, "store is closing", dbName); + log.error("get session with error:", closeException); + throw closeException; + } RocksDBSession dbSession = factory.queryGraphDB(dbName); if (dbSession == null) { long version = HgStoreEngine.getInstance().getCommittedIndex(partId); @@ -693,15 +1280,32 @@ private void deleteGraphDatabase(String graph, int partId) throws IOException { truncate(graph, partId); } - private PartitionManager getPartManager() { - return this.partitionManager; - } - @Override public TxBuilder txBuilder(String graph, int partId) throws HgStoreException { return new TxBuilderImpl(graph, partId, getSession(graph, partId)); } + @Override + public boolean cleanTtl(String graph, int partId, String table, List ids) { + + try (RocksDBSession dbSession = getSession(graph, table, partId)) { + SessionOperator op = dbSession.sessionOp(); + try { + op.prepare(); + for (ByteString bs : ids) { + byte[] targetKey = keyCreator.getKey(partId, graph, bs.toByteArray()); + op.delete(table, targetKey); + } + op.commit(); + } catch (Exception e) { + log.error("Graph: " + graph + " cleanTTL exception", e); + op.rollback(); + throw new HgStoreException(HgStoreException.EC_RKDB_DODEL_FAIL, e.toString()); + } + } + return true; + } + @Override public boolean existsTable(String graph, int partId, String table) { try (RocksDBSession session = getSession(graph, partId)) { @@ -746,17 +1350,149 @@ public boolean dbCompaction(String graphName, int partitionId) { * Perform compaction on RocksDB */ @Override - public boolean dbCompaction(String graphName, int partitionId, String tableName) { - try (RocksDBSession session = getSession(graphName, partitionId)) { - SessionOperator op = session.sessionOp(); - if (tableName.isEmpty()) { - op.compactRange(); - } else { - op.compactRange(tableName); + public boolean dbCompaction(String graphName, int id, String tableName) { + try { + compactionPool.submit(() -> { + try { + String path = getLockPath(id); + try (RocksDBSession session = getSession(graphName, id)) { + SessionOperator op = session.sessionOp(); + pathLock.putIfAbsent(path, new AtomicInteger(compactionCanStart)); + compactionState.putIfAbsent(id, new AtomicInteger(0)); + log.info("Partition {} dbCompaction started", id); + if (tableName.isEmpty()) { + lock(path); + setState(id, doing); + log.info("Partition {}-{} got lock, dbCompaction start", id, path); + op.compactRange(); + setState(id, compactionDone); + log.info("Partition {} dbCompaction end and start to do snapshot", id); + PartitionEngine pe = HgStoreEngine.getInstance().getPartitionEngine(id); + // 执行完成后找 leader 发送 blankTask + if (pe.isLeader()) { + RaftClosure bc = (closure) -> { + }; + pe.addRaftTask(RaftOperation.create(RaftOperation.SYNC_BLANK_TASK), + bc); + } else { + HgCmdClient client = HgStoreEngine.getInstance().getHgCmdClient(); + BlankTaskRequest request = new BlankTaskRequest(); + request.setGraphName(""); + request.setPartitionId(id); + client.tryInternalCallSyncWithRpc(request); + } + setAndNotifyState(id, compactionDone); + } else { + op.compactRange(tableName); + } + } + log.info("Partition {}-{} dbCompaction end", id, path); + } catch (Exception e) { + log.error("do dbCompaction with error: ", e); + } finally { + try { + semaphore.release(); + } catch (Exception e) { + + } + } + }); + } catch (Exception e) { + + } + return true; + } + + @Override + public void lock(String path) throws InterruptedException, TimeoutException { + long start = System.currentTimeMillis(); + while (!compareAndSetLock(path)) { + AtomicInteger lock = pathLock.get(path); + synchronized (lock) { + lock.wait(1000); + if (System.currentTimeMillis() - start > timeoutMillis) { + throw new TimeoutException("wait compaction start timeout"); + } + } + } + } + + @Override + public void unlock(String path) { + AtomicInteger l = pathLock.get(path); + l.set(compactionCanStart); + synchronized (l) { + l.notifyAll(); + } + } + + private boolean compareAndSetLock(String path) { + AtomicInteger l = pathLock.get(path); + return l.compareAndSet(compactionCanStart, doing); + } + + @Override + public void awaitAndSetLock(int id, int expectedValue, int value) throws InterruptedException, + TimeoutException { + long start = System.currentTimeMillis(); + while (!compareAndSetState(id, expectedValue, value)) { + AtomicInteger state = compactionState.get(id); + synchronized (state) { + state.wait(500); + if (System.currentTimeMillis() - start > timeoutMillis) { + throw new TimeoutException("wait compaction start timeout"); + } } } + } - log.info("Partition {}-{} dbCompaction end", graphName, partitionId); + @Override + public void setAndNotifyState(int id, int state) { + AtomicInteger l = compactionState.get(id); + l.set(state); + synchronized (l) { + l.notifyAll(); + } + } + + @Override + public AtomicInteger getState(int id) { + AtomicInteger l = compactionState.get(id); + return l; + } + + private AtomicInteger setState(int id, int state) { + AtomicInteger l = compactionState.get(id); + l.set(state); + return l; + } + + private boolean compareAndSetState(int id, int expectedState, int newState) { + AtomicInteger l = compactionState.get(id); + return l.compareAndSet(expectedState, newState); + } + + @Override + public String getLockPath(int partitionId) { + String dataPath = partitionManager.getDbDataPath(partitionId); + File file = FileUtils.getFile(dataPath); + File pf = file.getParentFile(); + return pf.getAbsolutePath(); + } + + @Override + public List getPartitionIds(String graph) { + return partitionManager.getPartitionIds(graph); + } + + @Override + public boolean blockingCompact(String graphName, int partitionId) { + boolean locked = semaphore.tryAcquire(); + if (locked) { + dbCompaction(graphName, partitionId, ""); + } else { + return false; + } return true; } @@ -768,7 +1504,8 @@ public boolean dbCompaction(String graphName, int partitionId, String tableName) */ @Override public void destroyGraphDB(String graphName, int partId) throws HgStoreException { - // Each graph each partition corresponds to a rocksdb instance, so the rocksdb instance name is rocksdb + partId + // Each graph each partition corresponds to a rocksdb instance, so the rocksdb instance + // name is rocksdb + partId String dbName = getDbName(partId); factory.destroyGraphDB(dbName); @@ -806,6 +1543,14 @@ public long count(String graph, String table) { return all; } + public InnerKeyCreator getKeyCreator() { + return keyCreator; + } + + public static Long getColumnByteHash(RocksDBSession.BackendColumn column) { + return fnvHash(column.name); + } + @NotThreadSafe private class TxBuilderImpl implements TxBuilder { @@ -904,7 +1649,8 @@ public Tx build() { return new Tx() { @Override public void commit() throws HgStoreException { - op.commit(); // After an exception occurs in commit, rollback must be called, otherwise it will cause the lock not to be released. + op.commit(); // After an exception occurs in commit, rollback must be + // called, otherwise it will cause the lock not to be released. dbSession.close(); } diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/business/DataManager.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/business/DataManager.java new file mode 100644 index 0000000000..b890f4a3fd --- /dev/null +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/business/DataManager.java @@ -0,0 +1,81 @@ +/* + * 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.store.business; + +import java.util.List; + +import org.apache.hugegraph.pd.grpc.Metapb; +import org.apache.hugegraph.store.cmd.HgCmdClient; +import org.apache.hugegraph.store.cmd.request.BatchPutRequest; +import org.apache.hugegraph.store.cmd.request.CleanDataRequest; +import org.apache.hugegraph.store.meta.PartitionManager; + +import com.alipay.sofa.jraft.Status; + +/** + * 数据管理接口,实现分区数据管理,分裂和合并,支持跨机器转移数据 + */ +public interface DataManager { + + void setBusinessHandler(BusinessHandler handler); + + void setMetaManager(PartitionManager metaManager); + + void setCmdClient(HgCmdClient cmdClient); + + /** + * 拷贝分区source内的数据到其他分区targets + * 一个分区,迁移到多个分区 + * + * @param source source partition + * @param targets target partitions + * @return execution status + * @throws Exception execution exception + */ + Status move(Metapb.Partition source, List targets) throws Exception; + + /** + * 将source target的数据全部拷贝到target上 + * 从一个分区迁移到另外一个分区 + * + * @param source source partition + * @param target target partition + * @return execution result + * @throws Exception execution exception + */ + Status move(Metapb.Partition source, Metapb.Partition target) throws Exception; + + //// 同步副本之间的分区状态 + //UpdatePartitionResponse updatePartitionState(Metapb.Partition partition, Metapb + // .PartitionState state); + // + + /// / 同步副本之间分区的范围 + //UpdatePartitionResponse updatePartitionRange(Metapb.Partition partition, int startKey, int + // endKey); + + // 清理分区partition内的无效数据 + void cleanData(Metapb.Partition partition); + + // 写入数据 + void write(BatchPutRequest request); + + void clean(CleanDataRequest request); + + Status doBuildIndex(Metapb.BuildIndexParam param, Metapb.Partition partition) throws Exception; +} diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/business/DataManagerImpl.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/business/DataManagerImpl.java new file mode 100644 index 0000000000..520c0ba894 --- /dev/null +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/business/DataManagerImpl.java @@ -0,0 +1,430 @@ +/* + * 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.store.business; + +import static org.apache.hugegraph.store.constant.HugeServerTables.INDEX_TABLE; +import static org.apache.hugegraph.store.constant.HugeServerTables.OUT_EDGE_TABLE; +import static org.apache.hugegraph.store.constant.HugeServerTables.VERTEX_TABLE; + +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CountDownLatch; +import java.util.function.BiFunction; + +import org.apache.hugegraph.backend.BackendColumn; +import org.apache.hugegraph.id.IdUtil; +import org.apache.hugegraph.pd.common.PartitionUtils; +import org.apache.hugegraph.pd.grpc.Metapb; +import org.apache.hugegraph.pd.grpc.Metapb.PartitionState; +import org.apache.hugegraph.pd.grpc.pulse.CleanType; +import org.apache.hugegraph.rocksdb.access.RocksDBSession; +import org.apache.hugegraph.rocksdb.access.ScanIterator; +import org.apache.hugegraph.schema.IndexLabel; +import org.apache.hugegraph.serializer.BinaryElementSerializer; +import org.apache.hugegraph.store.HgStoreEngine; +import org.apache.hugegraph.store.cmd.HgCmdClient; +import org.apache.hugegraph.store.cmd.request.BatchPutRequest; +import org.apache.hugegraph.store.cmd.request.CleanDataRequest; +import org.apache.hugegraph.store.cmd.response.BatchPutResponse; +import org.apache.hugegraph.store.cmd.response.UpdatePartitionResponse; +import org.apache.hugegraph.store.meta.PartitionManager; +import org.apache.hugegraph.store.query.util.KeyUtil; +import org.apache.hugegraph.store.raft.RaftClosure; +import org.apache.hugegraph.store.raft.RaftOperation; +import org.apache.hugegraph.store.term.Bits; +import org.apache.hugegraph.structure.BaseEdge; +import org.apache.hugegraph.structure.BaseElement; +import org.apache.hugegraph.structure.BaseVertex; +import org.apache.hugegraph.structure.Index; +import org.apache.hugegraph.structure.builder.IndexBuilder; + +import com.alipay.sofa.jraft.Status; + +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class DataManagerImpl implements DataManager { + + public static final int BATCH_PUT_SIZE = 2000; + private BusinessHandler businessHandler; + private PartitionManager metaManager; + private HgCmdClient client; + + private static Metapb.Partition findPartition(List partitions, int code) { + for (Metapb.Partition partition : partitions) { + if (code >= partition.getStartKey() && code < partition.getEndKey()) { + return partition; + } + } + return null; + } + + @Override + public void setBusinessHandler(BusinessHandler handler) { + this.businessHandler = handler; + } + + @Override + public void setMetaManager(PartitionManager metaManager) { + this.metaManager = metaManager; + } + + @Override + public void setCmdClient(HgCmdClient client) { + this.client = client; + } + + @Override + public Status move(Metapb.Partition source, List targets) throws Exception { + Status status = Status.OK(); + // 开始移动数据之前,先把分区下线 + UpdatePartitionResponse response = + metaManager.updateState(source, PartitionState.PState_Offline); + if (response.getStatus().isOK()) { + status = move(source, targets, DataManagerImpl::findPartition); + + // 数据迁移成功后,设置新分区范围和上线新分区 + for (var target : targets) { + if (status.isOk()) { + if (!(metaManager.updateRange(target, (int) target.getStartKey(), + (int) target.getEndKey()) + .getStatus().isOK() + && + metaManager.updateState(target, PartitionState.PState_Normal).getStatus() + .isOK())) { + status.setError(-3, "new partition online fail"); + } + } + } + } else { + status.setError(-1, "source partition offline fail"); + } + + metaManager.updateState(source, PartitionState.PState_Normal); + + return status; + } + + @Override + public Status move(Metapb.Partition source, Metapb.Partition target) throws Exception { + // 只写入 target + return move(source, Collections.singletonList(target), (partitions, integer) -> target); + } + + /** + * move data from partition to targets + * + * @param source source partition + * @param targets target partitions + * @param partitionSelector the key of source partition belongs which target + * @return execution result + * @throws Exception exception when put data + */ + + private Status move(Metapb.Partition source, List targets, + BiFunction, Integer, Metapb.Partition> partitionSelector) + throws Exception { + + Status status = Status.OK(); + String graphName = source.getGraphName(); + List tables = businessHandler.getTableNames(graphName, source.getId()); + + log.info("moveData, graph:{}, partition id:{} tables:{}, {}-{}", source.getGraphName(), + source.getId(), tables, + source.getStartKey(), source.getEndKey()); + WriteBatch batch = new WriteBatch(graphName); + // target partition : count + Map moveCount = new HashMap<>(); + + for (String table : tables) { + int total = 0; + moveCount.clear(); + + try (ScanIterator iterator = + businessHandler.scan(graphName, table, (int) source.getStartKey(), + (int) source.getEndKey())) { + int count = 0; + while (iterator.hasNext() && status.isOk()) { + total += 1; + RocksDBSession.BackendColumn entry = iterator.next(); + byte[] innerKey = entry.name; + byte[] key = Arrays.copyOfRange(innerKey, 0, innerKey.length - Short.BYTES); + int code = Bits.getShort(innerKey, innerKey.length - Short.BYTES); + Metapb.Partition partition = partitionSelector.apply(targets, code); + if (partition != null) { + moveCount.put(partition.getId(), + moveCount.getOrDefault(partition.getId(), 0L) + 1); + batch.add(partition.getId(), + BatchPutRequest.KV.of(table, code, key, entry.value)); + if (++count >= BATCH_PUT_SIZE) { + if (!batch.sync()) { + status.setError(-2, "move data fail"); + } + count = 0; + } + } + } + if (count > 0) { + if (!batch.sync()) { + status.setError(-2, "move data fail"); + } + } + + for (var pair : moveCount.entrySet()) { + log.info("{}-{}, table: {}, move to partition id {}, count:{}, total:{}", + source.getGraphName(), source.getId(), table, pair.getKey(), + pair.getValue(), + total); + } + } + } + + return status; + } + + @Override + public void cleanData(Metapb.Partition partition) { + String graphName = partition.getGraphName(); + CleanDataRequest request = new CleanDataRequest(); + request.setGraphName(graphName); + request.setPartitionId(partition.getId()); + request.setCleanType(CleanType.CLEAN_TYPE_KEEP_RANGE); + request.setKeyStart(partition.getStartKey()); + request.setKeyEnd(partition.getEndKey()); + request.setDeletePartition(false); + + try { + client.cleanData(request); + } catch (Exception e) { + log.error("exception ", e); + } + } + + @Override + public void write(BatchPutRequest request) { + BusinessHandler.TxBuilder tx = + businessHandler.txBuilder(request.getGraphName(), request.getPartitionId()); + for (BatchPutRequest.KV kv : request.getEntries()) { + tx.put(kv.getCode(), kv.getTable(), kv.getKey(), kv.getValue()); + } + tx.build().commit(); + } + + @Override + public void clean(CleanDataRequest request) { + // raft 执行真实数据的清理 + businessHandler.cleanPartition(request.getGraphName(), request.getPartitionId(), + request.getKeyStart(), request.getKeyEnd(), + request.getCleanType()); + } + + @Override + public Status doBuildIndex(Metapb.BuildIndexParam param, Metapb.Partition source) throws + Exception { + + var partitionId = source.getId(); + var graphName = param.getGraph(); + log.info("doBuildIndex begin, partition id :{}, with param: {}", partitionId, param); + + Status status = Status.OK(); + var graphSupplier = BusinessHandlerImpl.getGraphSupplier(graphName); + + var labelId = IdUtil.fromBytes(param.getLabelId().toByteArray()); + IndexLabel indexLabel = null; + if (param.hasIndexLabel()) { + indexLabel = + graphSupplier.indexLabel(IdUtil.fromBytes(param.getIndexLabel().toByteArray())); + } + + WriteBatch batch = new WriteBatch(param.getGraph()); + IndexBuilder builder = new IndexBuilder(graphSupplier); + BinaryElementSerializer serializer = new BinaryElementSerializer(); + + long countTotal = 0; + long start = System.currentTimeMillis(); + long countRecord = 0; + + // todo : table scan or prefix scan + try (var itr = businessHandler.scan(graphName, + param.getIsVertexLabel() ? VERTEX_TABLE : + OUT_EDGE_TABLE, + (int) source.getStartKey(), (int) source.getEndKey())) { + + int count = 0; + while (itr.hasNext()) { + RocksDBSession.BackendColumn entry = itr.next(); + + byte[] innerKey = entry.name; + byte[] key = Arrays.copyOfRange(innerKey, 0, innerKey.length - Short.BYTES); + var column = BackendColumn.of(key, entry.value); + + BaseElement element = null; + + try { + if (param.getIsVertexLabel()) { + element = serializer.parseVertex(graphSupplier, column, null); + } else { + element = serializer.parseEdge(graphSupplier, column, null, true); + } + } catch (Exception e) { + log.error("parse element failed, graph:{}, key:{}", graphName, e); + continue; + } + + // filter by label id + if (!element.schemaLabel().id().equals(labelId)) { + continue; + } + + countRecord += 1; + + List array; + if (indexLabel != null) { + // label id + array = builder.buildIndex(element, indexLabel); + } else if (param.hasLabelIndex() && param.getLabelIndex()) { + // element type index + array = builder.buildLabelIndex(element); + } else { + // rebuild all index + if (param.getIsVertexLabel()) { + assert element instanceof BaseVertex; + array = builder.buildVertexIndex((BaseVertex) element); + } else { + assert element instanceof BaseEdge; + array = builder.buildEdgeIndex((BaseEdge) element); + } + } + + for (var index : array) { + var col = serializer.writeIndex(index); + int code = PartitionUtils.calcHashcode(KeyUtil.getOwnerId(index.elementId())); + // same partition id with element + batch.add(partitionId, BatchPutRequest.KV.of(INDEX_TABLE, code, col.name, + col.value == null ? new byte[0] : + col.value)); + + if (++count >= BATCH_PUT_SIZE) { + if (!batch.sync()) { + status.setError(-2, "sync index failed"); + break; + } + count = 0; + } + countTotal++; + } + + if (!status.isOk()) { + break; + } + } + + if (status.isOk()) { + if (count > 0) { + if (!batch.sync()) { + status.setError(-2, "sync index failed"); + } + } + } + + log.info("doBuildIndex end, partition id: {}, records: {}, total index: {}, cost: {}s", + source.getId(), + countRecord, countTotal, (System.currentTimeMillis() - start) / 1000); + } + + return status; + } + + class WriteBatch { + + private final Map> data = new HashMap<>(); + private final String graphName; + + public WriteBatch(String graphName) { + this.graphName = graphName; + } + + public WriteBatch add(int partition, BatchPutRequest.KV kv) { + if (!data.containsKey(partition)) { + data.put(partition, new LinkedList<>()); + } + data.get(partition).add(kv); + return this; + } + + public Boolean sync() { + boolean ret = true; + for (Map.Entry> entry : data.entrySet()) { + ret = ret && sendData(entry.getKey(), entry.getValue()); + } + for (List list : data.values()) { + list.clear(); + } + + return ret; + } + + public Boolean sendData(Integer partId, List kvs) { + BatchPutRequest request = new BatchPutRequest(); + request.setGraphName(graphName); + request.setPartitionId(partId); + request.setEntries(kvs); + + var engine = HgStoreEngine.getInstance().getPartitionEngine(partId); + + if (engine != null && engine.isLeader()) { + try { + CountDownLatch latch = new CountDownLatch(1); + + final Boolean[] ret = {Boolean.FALSE}; + engine.addRaftTask(RaftOperation.create(RaftOperation.IN_WRITE_OP, request), + new RaftClosure() { + @Override + public void run(Status status) { + if (status.isOk()) { + ret[0] = Boolean.TRUE; + } + latch.countDown(); + } + }); + latch.await(); + + if (ret[0]) { + return true; + } + } catch (Exception e) { + // using send data by client when exception occurs + log.warn("send data by raft: pid: {}, error: ", partId, e); + } + } + + BatchPutResponse response = client.batchPut(request); + if (response == null || !response.getStatus().isOK()) { + log.error("sendData error, pId:{} status:{}", partId, + response != null ? response.getStatus() : "EMPTY_RESPONSE"); + return false; + } + + return true; + } + } +} diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/business/DataMover.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/business/DataMover.java index a348f561c7..603448e4e7 100644 --- a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/business/DataMover.java +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/business/DataMover.java @@ -30,6 +30,7 @@ /** * Data transfer interface, implementing partition splitting and merging, supporting cross-machine data transfer. */ +@Deprecated public interface DataMover { void setBusinessHandler(BusinessHandler handler); diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/business/DefaultDataMover.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/business/DefaultDataMover.java index aeca3a3cae..26138b334e 100644 --- a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/business/DefaultDataMover.java +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/business/DefaultDataMover.java @@ -42,6 +42,7 @@ import lombok.extern.slf4j.Slf4j; @Slf4j +@Deprecated public class DefaultDataMover implements DataMover { public static int Batch_Put_Size = 2000; diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/business/FilterIterator.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/business/FilterIterator.java index e3c1380b93..093d6e793a 100644 --- a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/business/FilterIterator.java +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/business/FilterIterator.java @@ -17,20 +17,17 @@ package org.apache.hugegraph.store.business; -import java.util.Arrays; - import org.apache.commons.lang3.ArrayUtils; -import org.apache.hugegraph.backend.query.ConditionQuery; -import org.apache.hugegraph.backend.serializer.BinaryBackendEntry; -import org.apache.hugegraph.backend.store.BackendEntry; -import org.apache.hugegraph.rocksdb.access.RocksDBSession.BackendColumn; +import org.apache.hugegraph.backend.BackendColumn; +import org.apache.hugegraph.query.ConditionQuery; +import org.apache.hugegraph.rocksdb.access.RocksDBSession; import org.apache.hugegraph.rocksdb.access.ScanIterator; -import org.apache.hugegraph.structure.HugeElement; +import org.apache.hugegraph.structure.BaseElement; import lombok.extern.slf4j.Slf4j; @Slf4j -public class FilterIterator extends +public class FilterIterator extends AbstractSelectIterator implements ScanIterator { @@ -58,27 +55,20 @@ public boolean hasNext() { boolean match = false; if (this.query.resultType().isVertex() || this.query.resultType().isEdge()) { - BackendEntry entry = null; + while (iterator.hasNext()) { current = iterator.next(); - BackendEntry.BackendColumn column = - BackendEntry.BackendColumn.of( - current.name, current.value); - BackendEntry.BackendColumn[] columns = - new BackendEntry.BackendColumn[]{column}; - if (entry == null || !belongToMe(entry, column) || - this.query.resultType().isEdge()) { - entry = new BinaryBackendEntry(query.resultType(), - current.name); - entry.columns(Arrays.asList(columns)); + BaseElement element; + if (this.query.resultType().isVertex()) { + element = serializer.parseVertex(null, + BackendColumn.of(current.name, current.value), + null); } else { - // There may be cases that contain multiple columns - entry.columns(Arrays.asList(columns)); - continue; + element = serializer.parseEdge(null, + BackendColumn.of(current.name, current.value), + null, true); } - HugeElement element = this.parseEntry(entry, - this.query.resultType() - .isVertex()); + match = query.test(element); if (match) { break; diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/business/GraphStoreIterator.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/business/GraphStoreIterator.java index 0e8aa50706..8418ff23e2 100644 --- a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/business/GraphStoreIterator.java +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/business/GraphStoreIterator.java @@ -18,7 +18,6 @@ package org.apache.hugegraph.store.business; import java.util.ArrayList; -import java.util.Arrays; import java.util.Date; import java.util.HashSet; import java.util.Iterator; @@ -31,10 +30,9 @@ import javax.script.ScriptException; import org.apache.commons.lang.StringUtils; -import org.apache.hugegraph.backend.id.Id; -import org.apache.hugegraph.backend.serializer.BinaryBackendEntry; -import org.apache.hugegraph.backend.store.BackendEntry; -import org.apache.hugegraph.rocksdb.access.RocksDBSession.BackendColumn; +import org.apache.hugegraph.backend.BackendColumn; +import org.apache.hugegraph.id.Id; +import org.apache.hugegraph.rocksdb.access.RocksDBSession; import org.apache.hugegraph.rocksdb.access.ScanIterator; import org.apache.hugegraph.schema.EdgeLabel; import org.apache.hugegraph.schema.PropertyKey; @@ -47,19 +45,18 @@ import org.apache.hugegraph.store.grpc.Graphpb.Variant.Builder; import org.apache.hugegraph.store.grpc.Graphpb.VariantType; import org.apache.hugegraph.store.grpc.Graphpb.Vertex; -import org.apache.hugegraph.structure.HugeEdge; -import org.apache.hugegraph.structure.HugeElement; -import org.apache.hugegraph.structure.HugeProperty; -import org.apache.hugegraph.structure.HugeVertex; +import org.apache.hugegraph.structure.BaseEdge; +import org.apache.hugegraph.structure.BaseElement; +import org.apache.hugegraph.structure.BaseProperty; +import org.apache.hugegraph.structure.BaseVertex; import org.apache.hugegraph.type.HugeType; import org.apache.hugegraph.util.Blob; -import org.apache.tinkerpop.gremlin.structure.Property; -import org.apache.tinkerpop.gremlin.structure.VertexProperty; import org.codehaus.groovy.jsr223.GroovyScriptEngineImpl; import com.google.protobuf.ByteString; import com.google.protobuf.Descriptors; +import groovy.lang.MissingMethodException; import lombok.extern.slf4j.Slf4j; @Slf4j @@ -78,10 +75,11 @@ public class GraphStoreIterator extends AbstractSelectIterator private final Set properties; private Vertex.Builder vertex; private Edge.Builder edge; - private ArrayList data; + private ArrayList data; private GroovyScriptEngineImpl engine; private CompiledScript script; - private HugeElement current; + private BaseElement current; + private Exception stopCause; public GraphStoreIterator(ScanIterator iterator, ScanPartitionRequest scanRequest) { @@ -117,40 +115,27 @@ public GraphStoreIterator(ScanIterator iterator, } } - private HugeElement getElement(BackendColumn next) { - BackendEntry entry = null; - BackendEntry.BackendColumn column = BackendEntry.BackendColumn.of( - next.name, next.value); - if (entry == null || !belongToMe(entry, column) || !isVertex) { - try { - entry = new BinaryBackendEntry(type, next.name); - } catch (Exception e) { - log.error("using core to new entry with error:", e); - } - } - BackendEntry.BackendColumn[] columns = - new BackendEntry.BackendColumn[]{column}; - entry.columns(Arrays.asList(columns)); - return this.parseEntry(entry, isVertex); + private BaseElement getElement(RocksDBSession.BackendColumn next) { + return this.parseEntry(BackendColumn.of(next.name, next.value), isVertex); } @Override public boolean hasNext() { if (current == null) { while (iter.hasNext()) { - BackendColumn next = this.iter.next(); - HugeElement element = getElement(next); + RocksDBSession.BackendColumn next = this.iter.next(); + BaseElement element = getElement(next); try { boolean evalResult = true; if (isVertex) { - HugeVertex el = (HugeVertex) element; + BaseVertex el = (BaseVertex) element; if (engine != null) { Bindings bindings = engine.createBindings(); bindings.put("element", el); evalResult = (boolean) script.eval(bindings); } } else { - HugeEdge el = (HugeEdge) element; + BaseEdge el = (BaseEdge) element; if (engine != null) { Bindings bindings = engine.createBindings(); bindings.put("element", el); @@ -162,6 +147,10 @@ public boolean hasNext() { } current = element; return true; + } catch (ScriptException | MissingMethodException se) { + stopCause = se; + log.error("get next with error which cause to stop:", se); + return false; } catch (Exception e) { log.error("get next with error:", e); } @@ -189,8 +178,8 @@ public T next() { return next; } - public T select(BackendColumn current) { - HugeElement element = getElement(current); + public T select(RocksDBSession.BackendColumn current) { + BaseElement element = getElement(current); if (isVertex) { return (T) parseVertex(element); } else { @@ -206,7 +195,7 @@ public ArrayList convert() { return result; } - private

> List buildProperties( + private

> List buildProperties( Builder variant, int size, Iterator

eps) { @@ -215,7 +204,7 @@ private

> List buildProperties( pSize : size); Graphpb.Property.Builder pb = Graphpb.Property.newBuilder(); while (eps.hasNext()) { - HugeProperty property = (HugeProperty) eps.next(); + BaseProperty property = eps.next(); PropertyKey key = property.propertyKey(); long pkId = key.id().asLong(); if (pSize > 0 && !properties.contains(pkId)) { @@ -309,8 +298,8 @@ private void buildId(Builder variant, Id id) { } } - private Edge parseEdge(HugeElement element) { - HugeEdge e = (HugeEdge) element; + private Edge parseEdge(BaseElement element) { + BaseEdge e = (BaseEdge) element; edge.clear(); EdgeLabel label = e.schemaLabel(); edge.setLabel(label.longId()); @@ -323,14 +312,14 @@ private Edge parseEdge(HugeElement element) { buildId(variant, e.targetVertex().id()); edge.setTargetId(variant.build()); int size = e.sizeOfProperties(); - Iterator> eps = e.properties(); + Iterator> eps = e.properties().iterator(); List props = buildProperties(variant, size, eps); edge.setField(propertiesDesEdge, props); return edge.build(); } - private Vertex parseVertex(HugeElement element) { - HugeVertex v = (HugeVertex) element; + private Vertex parseVertex(BaseElement element) { + BaseVertex v = (BaseVertex) element; vertex.clear(); VertexLabel label = v.schemaLabel(); vertex.setLabel(label.longId()); @@ -338,7 +327,7 @@ private Vertex parseVertex(HugeElement element) { buildId(variant, v.id()); vertex.setId(variant.build()); int size = v.sizeOfProperties(); - Iterator> vps = v.properties(); + Iterator> vps = v.properties().iterator(); List props = buildProperties(variant, size, vps); vertex.setField(propertiesDesVertex, props); return vertex.build(); @@ -348,4 +337,8 @@ private Vertex parseVertex(HugeElement element) { public void close() { iter.close(); } + + public Exception getStopCause() { + return stopCause; + } } diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/business/InnerKeyCreator.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/business/InnerKeyCreator.java index 072d09cc4a..43db542f6b 100644 --- a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/business/InnerKeyCreator.java +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/business/InnerKeyCreator.java @@ -30,7 +30,7 @@ public class InnerKeyCreator { final BusinessHandler businessHandler; - private final Map graphIdCache = new ConcurrentHashMap<>(); + private volatile Map graphIdCache = new ConcurrentHashMap<>(); public InnerKeyCreator(BusinessHandler businessHandler) { this.businessHandler = businessHandler; @@ -49,6 +49,26 @@ public int getGraphId(Integer partId, String graphName) throws HgStoreException } } + /** + * + * @param partId partition id + * @param graphName graph name + * @return 65535 如果不存在 + * @throws HgStoreException + */ + public int getGraphIdOrCreate(Integer partId, String graphName) throws HgStoreException { + try { + GraphIdManager manager; + if ((manager = graphIdCache.get(partId)) == null) { + manager = new GraphIdManager(businessHandler, partId); + graphIdCache.put(partId, manager); + } + return (int) manager.getGraphIdOrCreate(graphName); + } catch (Exception e) { + throw new HgStoreException(HgStoreException.EC_RKDB_PD_FAIL, e.getMessage()); + } + } + public void delGraphId(Integer partId, String graphName) { if (graphIdCache.containsKey(partId)) { graphIdCache.get(partId).releaseGraphId(graphName); @@ -68,6 +88,15 @@ public int parseKeyCode(byte[] innerKey) { return Bits.getShort(innerKey, innerKey.length - Short.BYTES); } + public byte[] getKeyOrCreate(Integer partId, String graph, int code, byte[] key) { + int graphId = getGraphIdOrCreate(partId, graph); + byte[] buf = new byte[Short.BYTES + key.length + Short.BYTES]; + Bits.putShort(buf, 0, graphId); + Bits.put(buf, Short.BYTES, key); + Bits.putShort(buf, key.length + Short.BYTES, code); + return buf; + } + public byte[] getKey(Integer partId, String graph, int code, byte[] key) { int graphId = getGraphId(partId, graph); byte[] buf = new byte[Short.BYTES + key.length + Short.BYTES]; @@ -77,6 +106,21 @@ public byte[] getKey(Integer partId, String graph, int code, byte[] key) { return buf; } + /** + * + * @param partId + * @param graph + * @param key + * @return + */ + public byte[] getKey(Integer partId, String graph, byte[] key) { + int graphId = getGraphId(partId, graph); + byte[] buf = new byte[Short.BYTES + key.length]; + Bits.putShort(buf, 0, graphId); + Bits.put(buf, Short.BYTES, key); + return buf; + } + public byte[] getStartKey(Integer partId, String graph) { int graphId = getGraphId(partId, graph); byte[] buf = new byte[Short.BYTES]; diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/business/InnerKeyFilter.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/business/InnerKeyFilter.java index 34dc46063b..368032f2ce 100644 --- a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/business/InnerKeyFilter.java +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/business/InnerKeyFilter.java @@ -40,6 +40,14 @@ public InnerKeyFilter(ScanIterator iterator) { moveNext(); } + public InnerKeyFilter(ScanIterator iterator, boolean codeFilter) { + this.iterator = iterator; + this.codeFrom = Integer.MIN_VALUE; + this.codeTo = Integer.MAX_VALUE; + this.codeFilter = codeFilter; + moveNext(); + } + public InnerKeyFilter(ScanIterator iterator, int codeFrom, int codeTo) { this.iterator = iterator; this.codeFrom = codeFrom; diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/business/MultiPartitionIterator.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/business/MultiPartitionIterator.java index 44d77935d5..72cc472b21 100644 --- a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/business/MultiPartitionIterator.java +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/business/MultiPartitionIterator.java @@ -24,6 +24,7 @@ import java.util.NoSuchElementException; import java.util.Queue; import java.util.function.BiFunction; +import java.util.stream.Collectors; import org.apache.hugegraph.rocksdb.access.ScanIterator; @@ -198,4 +199,16 @@ private byte[] getPositionKey(int partitionId) { } + /** + * obtain iteration list of all partitions + * + * @return iteration list + */ + public List getIterators() { + return this.partitions.stream() + .map(id -> supplier.apply(id, getPositionKey(id))) + .filter(ScanIterator::hasNext) + .collect(Collectors.toList()); + } + } diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/business/SelectIterator.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/business/SelectIterator.java index 41a47efccf..2b51e98778 100644 --- a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/business/SelectIterator.java +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/business/SelectIterator.java @@ -21,10 +21,10 @@ import java.util.List; import java.util.Set; -import org.apache.hugegraph.backend.id.Id; -import org.apache.hugegraph.backend.serializer.BytesBuffer; +import org.apache.hugegraph.id.Id; import org.apache.hugegraph.rocksdb.access.RocksDBSession.BackendColumn; import org.apache.hugegraph.rocksdb.access.ScanIterator; +import org.apache.hugegraph.serializer.BytesBuffer; import org.apache.hugegraph.type.define.DataType; import org.apache.hugegraph.type.define.SerialEnum; diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/business/itrv2/BatchGetIterator.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/business/itrv2/BatchGetIterator.java new file mode 100644 index 0000000000..948a46aa89 --- /dev/null +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/business/itrv2/BatchGetIterator.java @@ -0,0 +1,88 @@ +/* + * 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.store.business.itrv2; + +import java.util.Iterator; +import java.util.function.Function; + +import org.apache.hugegraph.rocksdb.access.RocksDBSession; +import org.apache.hugegraph.rocksdb.access.ScanIterator; +import org.apache.hugegraph.store.query.QueryTypeParam; + +/** + * 传入多个id,根据id去查询数据,返回iterator + * ID 查询 + */ +public class BatchGetIterator implements ScanIterator { + + private final Iterator iterator; + + private final Function retriveFunction; + + private byte[] pos; + + public BatchGetIterator(Iterator iterator, + Function retriveFunction) { + this.iterator = iterator; + this.retriveFunction = retriveFunction; + } + + @Override + public boolean hasNext() { + return this.iterator.hasNext(); + } + + @Override + public boolean isValid() { + return this.iterator.hasNext(); + } + + @Override + public RocksDBSession.BackendColumn next() { + var param = iterator.next(); + byte[] key = param.getStart(); + this.pos = key; + var value = retriveFunction.apply(param); + return value == null ? null : RocksDBSession.BackendColumn.of(key, value); + } + + @Override + public void close() { + + } + + @Override + public byte[] position() { + return this.pos; + } + + @Override + public long count() { + long count = 0L; + while (this.iterator.hasNext()) { + this.iterator.next(); + count += 1; + } + return count; + } + + @Override + public void seek(byte[] position) { + // not supported + } +} diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/business/itrv2/FileObjectIterator.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/business/itrv2/FileObjectIterator.java new file mode 100644 index 0000000000..23fd7fbb55 --- /dev/null +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/business/itrv2/FileObjectIterator.java @@ -0,0 +1,71 @@ +/* + * 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.store.business.itrv2; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.Iterator; + +import org.apache.hugegraph.store.business.itrv2.io.SortShuffleSerializer; + +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class FileObjectIterator implements Iterator { + + private FileInputStream fis = null; + private T current; + private String fn; + private SortShuffleSerializer serializer; + + public FileObjectIterator(String filePath, SortShuffleSerializer serializer) { + this.fn = filePath; + this.serializer = serializer; + } + + @Override + public boolean hasNext() { + try { + if (fis == null) { + fis = new FileInputStream(this.fn); + } + current = readObject(fis); + + if (current != null) { + return true; + } else { + String parent = new File(this.fn).getParent(); + new File(parent).delete(); + } + } catch (Exception e) { + e.printStackTrace(); + } + return false; + } + + @Override + public T next() { + return current; + } + + public T readObject(InputStream input) throws IOException { + return serializer.read(input); + } +} diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/business/itrv2/InAccurateIntersectionIterator.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/business/itrv2/InAccurateIntersectionIterator.java new file mode 100644 index 0000000000..69d1699df6 --- /dev/null +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/business/itrv2/InAccurateIntersectionIterator.java @@ -0,0 +1,103 @@ +/* + * 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.store.business.itrv2; + +import java.util.NoSuchElementException; +import java.util.function.ToLongFunction; + +import org.apache.hugegraph.rocksdb.access.ScanIterator; +import org.roaringbitmap.longlong.Roaring64Bitmap; + +/** + * 不适用于单个的iterator, 单个的要使用union的版本(只是做去重) + * + * @param + */ +public class InAccurateIntersectionIterator implements ScanIterator { + + private final Roaring64Bitmap workBitmap; + + private final ToLongFunction toLongFunction; + + private final ScanIterator iterator; + + private T current; + + public InAccurateIntersectionIterator(ScanIterator iterator, ToLongFunction toLongFunction) { + assert (iterator instanceof MultiListIterator && + ((MultiListIterator) iterator).getIterators().size() > 0); + this.iterator = iterator; + this.workBitmap = new Roaring64Bitmap(); + this.toLongFunction = toLongFunction; + } + + @Override + public boolean hasNext() { + current = null; + while (iterator.hasNext()) { + var element = (T) iterator.next(); + if (element == null) { + continue; + } + + var key = toLongFunction.applyAsLong(element); + if (workBitmap.contains(key)) { + current = element; + return true; + } else { + workBitmap.add(key); + } + } + + return false; + } + + @Override + public boolean isValid() { + return iterator.isValid(); + } + + @Override + public E next() { + if (current == null) { + throw new NoSuchElementException(); + } + return (E) current; + } + + @Override + public long count() { + return iterator.count(); + } + + @Override + public byte[] position() { + return iterator.position(); + } + + @Override + public void seek(byte[] position) { + iterator.seek(position); + } + + @Override + public void close() { + iterator.close(); + this.workBitmap.clear(); + } +} diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/business/itrv2/InAccurateUnionFilterIterator.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/business/itrv2/InAccurateUnionFilterIterator.java new file mode 100644 index 0000000000..8e818c54f6 --- /dev/null +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/business/itrv2/InAccurateUnionFilterIterator.java @@ -0,0 +1,100 @@ +/* + * 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.store.business.itrv2; + +import java.util.NoSuchElementException; +import java.util.function.ToLongFunction; + +import org.apache.hugegraph.rocksdb.access.ScanIterator; +import org.roaringbitmap.longlong.Roaring64Bitmap; + +/** + * 不精确的去重,直接使用位图 + * + * @param + */ +public class InAccurateUnionFilterIterator implements ScanIterator { + + private final Roaring64Bitmap workBitmap; + + private final ToLongFunction toLongFunction; + + private final ScanIterator iterator; + + private T current; + + public InAccurateUnionFilterIterator(ScanIterator iterator, ToLongFunction toLongFunction) { + this.iterator = iterator; + this.workBitmap = new Roaring64Bitmap(); + this.toLongFunction = toLongFunction; + } + + @Override + public boolean hasNext() { + current = null; + while (iterator.hasNext()) { + var element = (T) iterator.next(); + if (element == null) { + continue; + } + + var key = toLongFunction.applyAsLong(element); + if (!workBitmap.contains(key)) { + current = element; + workBitmap.add(key); + return true; + } + } + + return false; + } + + @Override + public boolean isValid() { + return iterator.isValid(); + } + + @Override + public E next() { + if (current == null) { + throw new NoSuchElementException(); + } + return (E) current; + } + + @Override + public long count() { + return iterator.count(); + } + + @Override + public byte[] position() { + return iterator.position(); + } + + @Override + public void seek(byte[] position) { + iterator.seek(position); + } + + @Override + public void close() { + iterator.close(); + this.workBitmap.clear(); + } +} diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/business/itrv2/IntersectionFilterIterator.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/business/itrv2/IntersectionFilterIterator.java new file mode 100644 index 0000000000..8651b96f7d --- /dev/null +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/business/itrv2/IntersectionFilterIterator.java @@ -0,0 +1,243 @@ +/* + * 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.store.business.itrv2; + +import java.io.IOException; +import java.util.Arrays; +import java.util.Comparator; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; + +import org.apache.hugegraph.rocksdb.access.RocksDBSession; +import org.apache.hugegraph.rocksdb.access.ScanIterator; +import org.apache.hugegraph.store.business.itrv2.io.SortShuffleSerializer; +import org.apache.hugegraph.store.util.SortShuffle; + +/** + * 目前应用:2个及以上的iterator去重 (大数据量) + * 痛点: iterator 内部可能有重复的, 怎么处理 ?? + */ +public class IntersectionFilterIterator implements ScanIterator { + + private static final Integer MAX_SIZE = 100000; + protected Map map; + private ScanIterator iterator; + private IntersectionWrapper wrapper; + private boolean processed = false; + private Iterator innerIterator; + private SortShuffle sortShuffle; + + private int size = -1; + + @Deprecated + public IntersectionFilterIterator(ScanIterator iterator, IntersectionWrapper wrapper) { + this.iterator = iterator; + this.wrapper = wrapper; + this.map = new HashMap<>(); + } + + /** + * iterator中取交集(可以是multi list iterator + * 问题:对于multi list iterator,无法做到每个都存在,需要外部去重。但是保证总数 + * + * @param iterator 待遍历的iterator + * @param wrapper bitmap, 不在bitmap中的,丢弃 + * @param size the element count in the iterator by filtering + */ + public IntersectionFilterIterator(ScanIterator iterator, IntersectionWrapper wrapper, + int size) { + this(iterator, wrapper); + this.size = size; + } + + @Override + public boolean hasNext() { + if (!processed) { + try { + dedup(); + } catch (Exception e) { + throw new RuntimeException(e); + } + processed = true; + } + + return innerIterator.hasNext(); + } + + // TODO: 优化序列化器 + private void saveElements() throws IOException, ClassNotFoundException { + for (var entry : this.map.entrySet()) { + for (int i = 0; i < entry.getValue(); i++) { + sortShuffle.append((RocksDBSession.BackendColumn) entry.getKey()); + } + } + + this.map.clear(); + } + + /** + * todo: 如果一个iterator中存在重复的,目前是无解。 去重的代价太高了。 + * + * @throws IOException + * @throws ClassNotFoundException + */ + protected void dedup() throws IOException, ClassNotFoundException { + while (this.iterator.hasNext()) { + var object = this.iterator.next(); + if (wrapper.contains(object)) { + this.map.put(object, map.getOrDefault(object, 0) + 1); + if (this.map.size() >= MAX_SIZE) { + if (this.sortShuffle == null) { + this.sortShuffle = + new SortShuffle<>((o1, o2) -> Arrays.compare(o1.name, o2.name), + SortShuffleSerializer.ofBackendColumnSerializer()); + } + saveElements(); + } + } + } + + // last batch + if (this.sortShuffle != null) { + saveElements(); + this.sortShuffle.finish(); + } + + if (this.sortShuffle == null) { + // map 没填满 + this.innerIterator = + new MapValueFilterIterator<>(this.map, x -> x == size || size == -1 && x > 1); + } else { + // 需要读取文件 + var fileIterator = + (Iterator) this.sortShuffle.getIterator(); + this.innerIterator = new ReduceIterator<>(fileIterator, + (o1, o2) -> Arrays.compare(o1.name, o2.name), + this.size); + } + } + + @Override + public boolean isValid() { + if (this.processed) { + return false; + } + return iterator.isValid(); + } + + @Override + public T next() { + return (T) this.innerIterator.next(); + } + + @Override + public void close() { + this.iterator.close(); + this.map.clear(); + } + + @Override + public long count() { + return this.iterator.count(); + } + + @Override + public byte[] position() { + return this.iterator.position(); + } + + @Override + public void seek(byte[] position) { + this.iterator.seek(position); + } + + /** + * 只保留有重复元素的 + * + * @param + */ + public static class ReduceIterator implements Iterator { + + private E prev = null; + + private E current = null; + + private E data = null; + + private int count = 0; + + private Iterator iterator; + + private Comparator comparator; + + private int adjacent; + + public ReduceIterator(Iterator iterator, Comparator comparator, int adjacent) { + this.count = 0; + this.iterator = iterator; + this.comparator = comparator; + this.adjacent = adjacent; + } + + /** + * 连续重复结果消除. 当prev == current的时候,记录data + * 当不等的时候,放回之前的data. + * 注意最后的结果,可能重复 + */ + @Override + public boolean hasNext() { + while (iterator.hasNext()) { + if (prev == null) { + prev = iterator.next(); + continue; + } + + current = iterator.next(); + if (comparator.compare(prev, current) == 0) { + data = current; + count += 1; + } else { + // count starts from 0, so the size is count + 1 + if (count > 0 && this.adjacent == -1 || count + 1 == this.adjacent) { + count = 0; + prev = current; + return true; + } else { + count = 0; + prev = current; + } + } + } + + // 最后一个结果 + if (count > 0) { + count = 0; + return true; + } + + return false; + } + + @Override + public E next() { + return data; + } + } + +} diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/business/itrv2/IntersectionWrapper.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/business/itrv2/IntersectionWrapper.java new file mode 100644 index 0000000000..e6dd9f398b --- /dev/null +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/business/itrv2/IntersectionWrapper.java @@ -0,0 +1,115 @@ +/* + * 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.store.business.itrv2; + +import java.util.List; +import java.util.function.ToLongFunction; + +import org.apache.hugegraph.rocksdb.access.ScanIterator; +import org.roaringbitmap.longlong.Roaring64Bitmap; + +public class IntersectionWrapper { + + private Roaring64Bitmap workBitmap; + private Roaring64Bitmap resultBitmap; + private ScanIterator iterator; + private ToLongFunction hashFunction; + private boolean matchAll; + + public IntersectionWrapper(ScanIterator iterator, ToLongFunction hashFunction) { + this.iterator = iterator; + this.hashFunction = hashFunction; + this.workBitmap = new Roaring64Bitmap(); + this.resultBitmap = new Roaring64Bitmap(); + this.matchAll = false; + } + + /** + * 记录iterator中hash值相同的部分. + * + * @param iterator iterator + * @param hashFunction mapping the element to a long value + * @param matchAllIterator a value that all exists in the iterator( MultiListIterator) + */ + public IntersectionWrapper(ScanIterator iterator, ToLongFunction hashFunction, + boolean matchAllIterator) { + this(iterator, hashFunction); + this.matchAll = matchAllIterator; + } + + public void proc() { + if (matchAll && iterator instanceof MultiListIterator) { + var mIterators = ((MultiListIterator) iterator).getIterators(); + if (mIterators.size() > 1) { + procMulti(mIterators); + } + return; + } + + procSingle(this.iterator, false); + } + + /** + * 对于multi list iterator 取所有 iterator的交集 + * + * @param iterators iterators + */ + private void procMulti(List iterators) { + var itr = iterators.get(0); + procSingle(itr, true); + + for (int i = 1; i < iterators.size(); i++) { + // change last round result to the work map + workBitmap = resultBitmap.clone(); + resultBitmap.clear(); + procSingle(iterators.get(i), false); + } + } + + private void procSingle(ScanIterator itr, boolean firstRound) { + while (itr.hasNext()) { + var n = itr.next(); + if (n == null) { + continue; + } + var key = hashFunction.applyAsLong((T) n); + + if (firstRound) { + resultBitmap.add(key); + } else { + if (workBitmap.contains(key)) { + resultBitmap.add(key); + } else { + workBitmap.add(key); + } + } + } + workBitmap.clear(); + } + + /** + * return contains + * + * @param o input element + * @return true: 如果是hash的,可能存在,false则肯定不存在 + */ + public boolean contains(T o) { + return resultBitmap.contains(hashFunction.applyAsLong(o)); + } +} + diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/business/itrv2/MapJoinIterator.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/business/itrv2/MapJoinIterator.java new file mode 100644 index 0000000000..1da9b90d4a --- /dev/null +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/business/itrv2/MapJoinIterator.java @@ -0,0 +1,122 @@ +/* + * 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.store.business.itrv2; + +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.function.Function; + +import org.apache.hugegraph.rocksdb.access.ScanIterator; + +public class MapJoinIterator implements ScanIterator { + + private final List iteratorList; + + private final Function keyFunction; + + private final Map map = new HashMap<>(); + + private Iterator iterator; + + private int loc = -1; + + private boolean flag; + + /** + * 多个iterator取交集。 + * + * @param iteratorList iterator list + * @param loc the location of the iterator having smallest size + * @param keyFunction key mapping mapping + */ + public MapJoinIterator(List iteratorList, int loc, Function keyFunction) { + assert (iteratorList != null); + assert (loc >= 0 && loc < iteratorList.size()); + this.iteratorList = iteratorList; + this.keyFunction = keyFunction; + this.loc = loc; + this.flag = false; + } + + @Override + public boolean hasNext() { + if (!flag) { + proc(); + } + return this.iterator.hasNext(); + } + + @Override + public boolean isValid() { + return true; + } + + @Override + public T next() { + return (T) this.iterator.next(); + } + + @Override + public void close() { + iteratorList.forEach(ScanIterator::close); + this.map.clear(); + } + + public void reset() { + this.iterator = this.map.values().iterator(); + } + + private void proc() { + var itr = iteratorList.get(loc); + while (itr.hasNext()) { + var tmp = (T) itr.next(); + if (tmp != null) { + map.put(keyFunction.apply(tmp), tmp); + } + } + + for (int i = 0; i < iteratorList.size(); i++) { + + if (i == loc) { + continue; + } + + var workMap = new HashMap(); + + itr = iteratorList.get(i); + while (itr.hasNext()) { + var tmp = (T) itr.next(); + if (tmp != null) { + var key = keyFunction.apply(tmp); + if (map.containsKey(key)) { + workMap.put(key, tmp); + } + } + } + + map.clear(); + map.putAll(workMap); + } + + this.iterator = this.map.values().iterator(); + + this.flag = true; + } +} diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/business/itrv2/MapLimitIterator.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/business/itrv2/MapLimitIterator.java new file mode 100644 index 0000000000..354fc9bf3e --- /dev/null +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/business/itrv2/MapLimitIterator.java @@ -0,0 +1,133 @@ +/* + * 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.store.business.itrv2; + +import java.util.Set; + +import org.apache.hugegraph.rocksdb.access.ScanIterator; + +import com.alipay.sofa.jraft.util.concurrent.ConcurrentHashSet; + +/** + * 针对一个iterator做去重,前SET_MAX_SIZE做精确去重,后面的直接返回 + * + * @param + */ +public class MapLimitIterator implements ScanIterator { + + private static final Integer SET_MAX_SIZE = 100000; + private ScanIterator iterator; + private Set set; + private T current = null; + + public MapLimitIterator(ScanIterator iterator) { + this.iterator = iterator; + set = new ConcurrentHashSet<>(); + } + + /** + * {@inheritDoc} + * 返回下一个元素是否存在。 + * 检查集合中是否还有下一个可用的元素,如果有则返回true,否则返回false。 + * 如果当前元素为空或者已在集合中,将跳过该元素继续检查下一个元素。 + * 在检查完所有符合条件的元素后,再次调用hasNext方法会重新检查一遍元素, + * 如果满足条件(即不为null且未包含在集合中),则将当前元素添加到集合中并返回true。 + * 当集合中已经包含SET_MAX_SIZE个元素时,将不会再添加任何新元素,并且返回false。 + * + * @return 下一个元素是否存在 + */ + @Override + public boolean hasNext() { + current = null; + while (iterator.hasNext()) { + var tmp = (T) iterator.next(); + if (tmp != null && !set.contains(tmp)) { + current = tmp; + break; + } + } + + // 控制set的大小 + if (current != null && set.size() <= SET_MAX_SIZE) { + set.add(current); + } + + return current != null; + } + + /** + * {@inheritDoc} + * 返回当前对象。 + * + * @return 当前对象的类型为T1的引用。 + */ + @Override + public T1 next() { + return (T1) current; + } + + /** + * 返回当前迭代器是否有效。 + * + * @return 当前迭代器是否有效,即是否还有下一个元素。 + */ + @Override + public boolean isValid() { + return iterator.isValid(); + } + + /** + * 返回集合的数量。 + * + * @return 集合的数量。 + */ + @Override + public long count() { + return iterator.count(); + } + + /** + * 返回当前迭代器的位置。 + * + * @return 当前迭代器的位置。 + */ + @Override + public byte[] position() { + return iterator.position(); + } + + /** + * {@inheritDoc} + * 将文件指针移动到指定的位置。 + * + * @param position 指定的字节数组,表示要移动到的位置。 + */ + @Override + public void seek(byte[] position) { + iterator.seek(position); + } + + /** + * 关闭迭代器。 + */ + @Override + public void close() { + iterator.close(); + this.set.clear(); + } +} diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/business/itrv2/MapUnionIterator.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/business/itrv2/MapUnionIterator.java new file mode 100644 index 0000000000..8fc7ecee8f --- /dev/null +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/business/itrv2/MapUnionIterator.java @@ -0,0 +1,82 @@ +/* + * 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.store.business.itrv2; + +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.function.Function; + +import org.apache.hugegraph.rocksdb.access.ScanIterator; + +public class MapUnionIterator implements ScanIterator { + + private final List iteratorList; + + private final Function keyFunction; + + private final Map map = new HashMap<>(); + + private Iterator iterator; + + private boolean flag = false; + + public MapUnionIterator(List iteratorList, Function keyFunction) { + this.iteratorList = iteratorList; + this.keyFunction = keyFunction; + } + + @Override + public boolean hasNext() { + if (!this.flag) { + this.proc(); + } + return this.iterator.hasNext(); + } + + @Override + public boolean isValid() { + return true; + } + + @Override + public T next() { + return (T) this.iterator.next(); + } + + @Override + public void close() { + iteratorList.forEach(ScanIterator::close); + this.map.clear(); + } + + private void proc() { + for (ScanIterator itr : this.iteratorList) { + while (itr.hasNext()) { + var item = (T) itr.next(); + if (item != null) { + map.put(keyFunction.apply(item), item); + } + } + } + + this.iterator = map.values().iterator(); + this.flag = true; + } +} diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/business/itrv2/MapValueFilterIterator.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/business/itrv2/MapValueFilterIterator.java new file mode 100644 index 0000000000..94d9402bbc --- /dev/null +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/business/itrv2/MapValueFilterIterator.java @@ -0,0 +1,57 @@ +/* + * 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.store.business.itrv2; + +import java.util.Iterator; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.function.IntPredicate; + +public class MapValueFilterIterator implements Iterator { + + Iterator> mapIterator; + private IntPredicate filter; + private K value; + + public MapValueFilterIterator(Map map, IntPredicate filter) { + this.mapIterator = map.entrySet().iterator(); + this.filter = filter; + } + + @Override + public boolean hasNext() { + while (mapIterator.hasNext()) { + Map.Entry entry = mapIterator.next(); + if (filter.test(entry.getValue())) { + value = entry.getKey(); + return true; + } + } + this.value = null; + return false; + } + + @Override + public K next() { + if (value == null) { + throw new NoSuchElementException(); + } + + return value; + } +} diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/business/itrv2/MultiListIterator.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/business/itrv2/MultiListIterator.java new file mode 100644 index 0000000000..909f1fa873 --- /dev/null +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/business/itrv2/MultiListIterator.java @@ -0,0 +1,158 @@ +/* + * 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.store.business.itrv2; + +import java.util.Iterator; +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; + +import org.apache.hugegraph.rocksdb.access.ScanIterator; +import org.apache.hugegraph.store.business.MultiPartitionIterator; + +/** + * 一组的同类型iterator,依次按照iterator输出 + */ +public class MultiListIterator implements ScanIterator { + + /** + * iterator 容器 + */ + private List iterators; + + /** + * iterator 容器的迭代器 + */ + private Iterator innerListIterator; + + /** + * 当前的element + */ + private ScanIterator innerIterator; + + public MultiListIterator() { + this.iterators = new CopyOnWriteArrayList<>(); + } + + public MultiListIterator(List iterators) { + this.iterators = new CopyOnWriteArrayList<>(iterators); + } + + /** + * 将迭代器添加到扫描迭代器列表中。 + * + * @param iterator 要添加的扫描迭代器。 + */ + public void addIterator(ScanIterator iterator) { + this.iterators.add(iterator); + } + + public List getIterators() { + return iterators; + } + + /** + * 获取内部迭代器 + */ + private void getInnerIterator() { + if (this.innerIterator != null && this.innerIterator.hasNext()) { + return; + } + + // close prev one + if (this.innerIterator != null) { + this.innerIterator.close(); + } + + if (this.innerListIterator == null) { + this.innerListIterator = this.iterators.iterator(); + } + + while (this.innerListIterator.hasNext()) { + this.innerIterator = this.innerListIterator.next(); + if (this.innerIterator.hasNext()) { + return; + } else { + // whole empty + this.innerIterator.close(); + } + } + + this.innerIterator = null; + } + + @Override + public boolean hasNext() { + getInnerIterator(); + return this.innerIterator != null; + } + + @Override + public boolean isValid() { + getInnerIterator(); + if (this.innerIterator != null) { + return this.innerIterator.isValid(); + } + return true; + } + + /** + * 关闭迭代器。 + */ + @Override + public void close() { + if (this.innerIterator != null) { + this.innerIterator.close(); + } + if (this.innerListIterator != null) { + while (this.innerListIterator.hasNext()) { + this.innerListIterator.next().close(); + } + } + this.iterators.clear(); + } + + @Override + public T next() { + return (T) this.innerIterator.next(); + } + + @Override + public long count() { + long count = 0; + while (hasNext()) { + next(); + count += 1; + } + return count; + } + + @Override + public byte[] position() { + return this.innerIterator.position(); + } + + @Override + public void seek(byte[] position) { + if (this.iterators.size() == 1) { + // range scan or prefix scan + if (this.innerIterator instanceof MultiPartitionIterator) { + this.innerIterator.seek(position); + } + } + } +} diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/business/itrv2/TypeTransIterator.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/business/itrv2/TypeTransIterator.java new file mode 100644 index 0000000000..610d495bcc --- /dev/null +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/business/itrv2/TypeTransIterator.java @@ -0,0 +1,176 @@ +/* + * 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.store.business.itrv2; + +import java.util.Iterator; +import java.util.NoSuchElementException; +import java.util.function.Function; +import java.util.function.Supplier; + +import org.apache.hugegraph.rocksdb.access.ScanIterator; + +/** + * 封装 iterator, 通过 function 做type的转化, + * 最后发送一个supplier.get的指令 + * + * @param 原来的类型 + * @param 转换后的类型 + */ +public class TypeTransIterator implements ScanIterator { + + private final Iterator iterator; + private final Function function; + private String name = ""; + private ScanIterator originalIterator; + private Supplier additionSupplier; + + /** + * is used once. return supper. apply and set to true. + */ + private boolean flag = false; + + private E data; + + public TypeTransIterator(ScanIterator scanIterator, Function function) { + this.originalIterator = scanIterator; + this.iterator = new Iterator() { + @Override + public boolean hasNext() { + return scanIterator.hasNext(); + } + + @Override + public F next() { + return scanIterator.next(); + } + }; + this.function = function; + } + + public TypeTransIterator(ScanIterator scanIterator, Function function, String name) { + this(scanIterator, function); + this.name = name; + } + + public TypeTransIterator(Iterator iterator, Function function) { + this.iterator = iterator; + this.function = function; + } + + public TypeTransIterator(Iterator iterator, Function function, Supplier supplier) { + this.iterator = iterator; + this.function = function; + this.additionSupplier = supplier; + } + + @Override + public boolean hasNext() { + if (this.data != null) { + return true; + } + + while (this.iterator.hasNext()) { + var n = this.iterator.next(); + if (n != null && (data = this.function.apply(n)) != null) { + return true; + } + } + + // look up for the default supplier + if (this.additionSupplier != null && !this.flag) { + data = this.additionSupplier.get(); + this.flag = true; + } + + return data != null; + } + + @Override + public boolean isValid() { + return true; + } + + @Override + public T next() { + if (this.data == null) { + throw new NoSuchElementException(); + } + try { + return (T) this.data; + } finally { + // 取出去之后,将data置空 + this.data = null; + } + } + + @Override + public void close() { + if (this.originalIterator != null) { + this.originalIterator.close(); + } + } + + @Override + public String toString() { + return "TypeTransIterator{" + + "name='" + name + '\'' + + ", function=" + function + + ", additionSupplier=" + additionSupplier + + ", flag=" + flag + + ", iterator=" + (originalIterator == null ? iterator : originalIterator) + + '}'; + } + + /** + * to java.util.Iterator + * + * @return iterator + */ + public Iterator toIterator() { + return new InnerIterator(this); + } + + private class InnerIterator implements Iterator, ScanIterator { + + private final TypeTransIterator iterator; + + public InnerIterator(TypeTransIterator iterator) { + this.iterator = iterator; + } + + @Override + public boolean hasNext() { + return this.iterator.hasNext(); + } + + @Override + public boolean isValid() { + return true; + } + + @Override + public void close() { + + } + + @Override + public E next() { + return this.iterator.next(); + } + } +} diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/business/itrv2/UnionFilterIterator.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/business/itrv2/UnionFilterIterator.java new file mode 100644 index 0000000000..d08bc7dd73 --- /dev/null +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/business/itrv2/UnionFilterIterator.java @@ -0,0 +1,228 @@ +/* + * 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.store.business.itrv2; + +import java.io.IOException; +import java.io.Serializable; +import java.util.Comparator; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.NoSuchElementException; + +import org.apache.hugegraph.pd.common.HgAssert; +import org.apache.hugegraph.rocksdb.access.ScanIterator; +import org.apache.hugegraph.store.business.itrv2.io.SortShuffleSerializer; +import org.apache.hugegraph.store.util.SortShuffle; + +public class UnionFilterIterator implements ScanIterator { + + private static final Integer MAP_SIZE = 10000; + + private final ScanIterator iterator; + + private final IntersectionWrapper wrapper; + private final Comparator comparator; + protected Map map; + private Iterator innerIterator; + private SortShuffle sortShuffle; + private SortShuffleSerializer serializer; + private Object current; + private boolean isProcessed = false; + + public UnionFilterIterator(ScanIterator iterator, IntersectionWrapper wrapper, + Comparator comparator, SortShuffleSerializer serializer) { + HgAssert.isNotNull(wrapper, "wrapper is null"); + this.iterator = iterator; + this.wrapper = wrapper; + this.map = new HashMap<>(); + this.comparator = comparator; + this.serializer = serializer; + } + + /** + * 将当前元素保存到sortShuffle中。 + */ + private void saveElement() { + for (var entry : this.map.entrySet()) { + try { + sortShuffle.append(entry.getKey()); + if (entry.getValue() > 1) { + sortShuffle.append(entry.getKey()); + } + } catch (IOException e) { + throw new RuntimeException(e); + } + } + this.map.clear(); + } + + @Override + public boolean hasNext() { + while (this.iterator.hasNext()) { + var obj = (T) this.iterator.next(); + // batch get or index lookup may generate null + if (obj == null) { + continue; + } + + // 肯定是唯一的 + if (!wrapper.contains(obj)) { + this.current = obj; + return true; + } else { + // System.out.println("----->" + Arrays.toString(((RocksDBSession.BackendColumn) + // obj).name)); + this.map.put(obj, map.getOrDefault(obj, 0) + 1); + if (this.map.size() > MAP_SIZE) { + if (this.sortShuffle == null) { + sortShuffle = new SortShuffle<>(this.comparator, this.serializer); + } + saveElement(); + } + } + } + + if (!isProcessed) { + if (sortShuffle != null) { + try { + saveElement(); + sortShuffle.finish(); + + var fileIterator = sortShuffle.getIterator(); + this.innerIterator = new NoRepeatValueIterator<>(fileIterator, this.comparator); + } catch (IOException e) { + throw new RuntimeException(e); + } + } else { + this.innerIterator = new MapValueFilterIterator<>(this.map, x -> x > 0); + } + + isProcessed = true; + } + + var ret = this.innerIterator.hasNext(); + if (ret) { + this.current = this.innerIterator.next(); + return true; + } + + if (sortShuffle != null) { + sortShuffle.close(); + sortShuffle = null; + } + + return false; + } + + @Override + public boolean isValid() { + // todo: check logic + return this.iterator.isValid() || hasNext(); + } + + @Override + public X next() { + if (current == null) { + throw new NoSuchElementException(); + } + + return (X) current; + } + + @Override + public void close() { + this.iterator.close(); + if (this.sortShuffle != null) { + this.sortShuffle.close(); + } + } + + @Override + public long count() { + return this.iterator.count(); + } + + @Override + public byte[] position() { + return this.iterator.position(); + } + + @Override + public void seek(byte[] position) { + this.iterator.seek(position); + } + + private static class NoRepeatValueIterator implements Iterator { + + private final Iterator iterator; + private final Comparator comparator; + private E prev = null; + private E data = null; + private int count = 0; + + public NoRepeatValueIterator(Iterator iterator, Comparator comparator) { + this.count = 0; + this.iterator = iterator; + this.comparator = comparator; + } + + @Override + public boolean hasNext() { + while (iterator.hasNext()) { + var n = iterator.next(); + if (prev == null) { + // prev = iterator.next(); + prev = n; + continue; + } + + // E current = iterator.next(); + E current = n; + + if (comparator.compare(prev, current) == 0) { + count += 1; + } else { + if (count > 0) { + // --- pre is dup + prev = current; + } else { + data = prev; + prev = current; + return true; + } + count = 0; + } + } + + // 最后一个结果 + if (count == 0) { + data = prev; + count = 1; + return true; + } + + return false; + } + + @Override + public E next() { + return data; + } + } +} diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/business/itrv2/io/SortShuffleSerializer.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/business/itrv2/io/SortShuffleSerializer.java new file mode 100644 index 0000000000..c321c33a14 --- /dev/null +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/business/itrv2/io/SortShuffleSerializer.java @@ -0,0 +1,275 @@ +/* + * 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.store.business.itrv2.io; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.ByteBuffer; + +import org.apache.hugegraph.backend.BackendColumn; +import org.apache.hugegraph.rocksdb.access.RocksDBSession; +import org.apache.hugegraph.serializer.BinaryElementSerializer; +import org.apache.hugegraph.store.query.KvSerializer; +import org.apache.hugegraph.store.query.Tuple2; +import org.apache.hugegraph.store.util.MultiKv; +import org.apache.hugegraph.structure.BaseEdge; +import org.apache.hugegraph.structure.BaseElement; +import org.apache.hugegraph.structure.BaseVertex; + +import lombok.extern.slf4j.Slf4j; + +/** + * support backend column, Multi kv, BaseElement + * format : object | object | object + * todo: need write object type header ? + * + * @param object type + */ +@Slf4j +public class SortShuffleSerializer { + + private static final byte TYPE_HEADER_MULTI_KV = 1; + private static final byte TYPE_HEADER_BACKEND_COLUMN = 2; + private static final byte TYPE_HEADER_BASE_ELEMENT = 3; + + private static SortShuffleSerializer backendSerializer = + new SortShuffleSerializer<>(new BackendColumnSerializer()); + + private static SortShuffleSerializer mkv = + new SortShuffleSerializer<>(new MultiKvSerializer()); + + private static SortShuffleSerializer element = + new SortShuffleSerializer<>(new BaseElementSerializer()); + + private final ObjectSerializer serializer; + + private SortShuffleSerializer(ObjectSerializer serializer) { + this.serializer = serializer; + } + + public static SortShuffleSerializer ofBackendColumnSerializer() { + return backendSerializer; + } + + public static SortShuffleSerializer ofMultiKvSerializer() { + return mkv; + } + + public static SortShuffleSerializer ofBaseElementSerializer() { + return element; + } + + public static byte[] toByte(int i) { + byte[] result = new byte[4]; + result[0] = (byte) ((i >> 24) & 0xff); + result[1] = (byte) ((i >> 16) & 0xff); + result[2] = (byte) ((i >> 8) & 0xff); + result[3] = (byte) (i & 0xff); + return result; + } + + public static int toInt(byte[] b) { + assert b.length == 4; + int value = 0; + for (int i = 0; i < 4; i++) { + int shift = (3 - i) * 8; + value += (b[i] & 0xff) << shift; + } + return value; + } + + private static byte[] kvBytesToByte(byte[] key, byte[] value) { + + int len = (key == null ? 0 : key.length) + (value == null ? 0 : value.length) + 8; + ByteBuffer buffer = ByteBuffer.allocate(len); + buffer.putInt(key == null ? 0 : key.length); + if (key != null) { + buffer.put(key); + } + buffer.putInt(value == null ? 0 : value.length); + if (value != null) { + buffer.put(value); + } + return buffer.array(); + } + + private static Tuple2 fromKvBytes(byte[] bytes) { + assert bytes != null; + ByteBuffer buffer = ByteBuffer.wrap(bytes); + + int nameLen = buffer.getInt(); + byte[] name = null; + if (nameLen != 0) { + name = new byte[nameLen]; + buffer.get(name); + } + + int valueLen = buffer.getInt(); + byte[] value = null; + if (valueLen != 0) { + value = new byte[valueLen]; + buffer.get(value); + } + + return Tuple2.of(name, value); + } + + public void write(OutputStream output, T data) throws IOException { + // input.write(serializer.getTypeHeader()); + var b = serializer.getBytes(data); + output.write(toByte(b.length)); + output.write(b); + } + + public T read(InputStream input) { + try { + var bytes = input.readNBytes(4); + + if (bytes.length == 0) { + return null; + } + + int sz = toInt(bytes); + return serializer.fromBytes(input.readNBytes(sz)); + } catch (IOException e) { + log.debug("error: {}", e.getMessage()); + return null; + } + } + + private abstract static class ObjectSerializer { + + public abstract T fromBytes(byte[] bytes); + + public abstract byte[] getBytes(T t); + + public abstract byte getTypeHeader(); + } + + /** + * format : + * key bytes len| key | value bytes len | value bytes + */ + + private static class MultiKvSerializer extends ObjectSerializer { + + @Override + public MultiKv fromBytes(byte[] bytes) { + var tuple = fromKvBytes(bytes); + return MultiKv.of(KvSerializer.fromObjectBytes(tuple.getV1()), + KvSerializer.fromObjectBytes(tuple.getV2())); + } + + @Override + public byte[] getBytes(MultiKv multiKv) { + return kvBytesToByte(KvSerializer.toBytes(multiKv.getKeys()), + KvSerializer.toBytes(multiKv.getValues())); + } + + @Override + public byte getTypeHeader() { + return TYPE_HEADER_MULTI_KV; + } + } + + /** + * format: + * name.len | name | value.len | value + */ + private static class BackendColumnSerializer extends + ObjectSerializer { + + @Override + public RocksDBSession.BackendColumn fromBytes(byte[] bytes) { + var tuple = fromKvBytes(bytes); + return RocksDBSession.BackendColumn.of(tuple.getV1(), tuple.getV2()); + } + + @Override + public byte[] getBytes(RocksDBSession.BackendColumn column) { + return kvBytesToByte(column.name, column.value); + } + + @Override + public byte getTypeHeader() { + return TYPE_HEADER_BACKEND_COLUMN; + } + } + + /** + * format: + * vertex/edge | name.len | name | value.len | value + */ + private static class BaseElementSerializer extends ObjectSerializer { + + private final BinaryElementSerializer serializer = new BinaryElementSerializer(); + + @Override + public BaseElement fromBytes(byte[] bytes) { + ByteBuffer buffer = ByteBuffer.wrap(bytes); + + boolean isVertex = buffer.get() == 0; + + int nameLen = buffer.getInt(); + byte[] name = new byte[nameLen]; + buffer.get(name); + int valueLen = buffer.getInt(); + byte[] value = new byte[valueLen]; + buffer.get(value); + + if (isVertex) { + return serializer.parseVertex(null, BackendColumn.of(name, value), null); + } + return serializer.parseEdge(null, BackendColumn.of(name, value), null, true); + } + + @Override + public byte[] getBytes(BaseElement element) { + assert element != null; + + BackendColumn column; + boolean isVertex = false; + if (element instanceof BaseVertex) { + column = serializer.writeVertex((BaseVertex) element); + isVertex = true; + } else { + column = serializer.writeEdge((BaseEdge) element); + } + + ByteBuffer buffer = ByteBuffer.allocate(column.name.length + column.value.length + 9); + if (isVertex) { + buffer.put((byte) 0); + } else { + buffer.put((byte) 1); + } + + buffer.putInt(column.name.length); + buffer.put(column.name); + buffer.putInt(column.value.length); + buffer.put(column.value); + return buffer.array(); + } + + @Override + public byte getTypeHeader() { + return TYPE_HEADER_BASE_ELEMENT; + } + } + +} diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/BatchPutRequest.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/BatchPutRequest.java index a776e6d4e1..ba51f82759 100644 --- a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/BatchPutRequest.java +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/BatchPutRequest.java @@ -23,6 +23,7 @@ import lombok.Data; @Data +@Deprecated public class BatchPutRequest extends HgCmdBase.BaseRequest { private List entries; diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/BatchPutResponse.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/BatchPutResponse.java index 98a72f5655..ad2bd4b638 100644 --- a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/BatchPutResponse.java +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/BatchPutResponse.java @@ -17,6 +17,7 @@ package org.apache.hugegraph.store.cmd; +@Deprecated public class BatchPutResponse extends HgCmdBase.BaseResponse { } diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/CleanDataResponse.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/CleanDataResponse.java index f7773075de..ce4f5fa98a 100644 --- a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/CleanDataResponse.java +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/CleanDataResponse.java @@ -16,7 +16,7 @@ */ package org.apache.hugegraph.store.cmd; - +@Deprecated public class CleanDataResponse extends HgCmdBase.BaseResponse { } diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/CreateRaftRequest.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/CreateRaftRequest.java index be5c384205..ad3cb063b4 100644 --- a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/CreateRaftRequest.java +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/CreateRaftRequest.java @@ -28,6 +28,7 @@ import lombok.extern.slf4j.Slf4j; @Slf4j +@Deprecated public class CreateRaftRequest extends HgCmdBase.BaseRequest { List values = new ArrayList<>(); diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/CreateRaftResponse.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/CreateRaftResponse.java index 9e14ffc97d..8bfdf9c551 100644 --- a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/CreateRaftResponse.java +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/CreateRaftResponse.java @@ -17,6 +17,7 @@ package org.apache.hugegraph.store.cmd; +@Deprecated public class CreateRaftResponse extends HgCmdBase.BaseResponse { } diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/DbCompactionResponse.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/DbCompactionResponse.java index 228aae1078..ae589f212f 100644 --- a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/DbCompactionResponse.java +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/DbCompactionResponse.java @@ -17,6 +17,7 @@ package org.apache.hugegraph.store.cmd; +@Deprecated public class DbCompactionResponse extends HgCmdBase.BaseResponse { } diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/DestroyRaftResponse.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/DestroyRaftResponse.java index cb24b2fc49..8d015f2f94 100644 --- a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/DestroyRaftResponse.java +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/DestroyRaftResponse.java @@ -17,6 +17,7 @@ package org.apache.hugegraph.store.cmd; +@Deprecated public class DestroyRaftResponse extends HgCmdBase.BaseResponse { } diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/FutureClosureAdapter.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/FutureClosureAdapter.java index 8579a7d4f1..b7633160d4 100644 --- a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/FutureClosureAdapter.java +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/FutureClosureAdapter.java @@ -22,6 +22,7 @@ import com.alipay.sofa.jraft.Closure; import com.alipay.sofa.jraft.Status; +@Deprecated public class FutureClosureAdapter implements Closure { public final CompletableFuture future = new CompletableFuture<>(); diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/GetStoreInfoResponse.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/GetStoreInfoResponse.java index df32cd99fe..7ed2e3d054 100644 --- a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/GetStoreInfoResponse.java +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/GetStoreInfoResponse.java @@ -25,6 +25,7 @@ import lombok.extern.slf4j.Slf4j; @Slf4j +@Deprecated public class GetStoreInfoResponse extends HgCmdBase.BaseResponse { private byte[] store; diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/HgCmdBase.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/HgCmdBase.java index b612f3fc44..0f7923e210 100644 --- a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/HgCmdBase.java +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/HgCmdBase.java @@ -32,6 +32,10 @@ public class HgCmdBase { public static final byte ROCKSDB_COMPACTION = 0x05; public static final byte CREATE_RAFT = 0x06; public static final byte DESTROY_RAFT = 0x07; + public static final byte TTL_CLEAN = 0x08; + public static final byte BLANK_TASK = 0x09; + + public static final byte REDIRECT_RAFT_TASK = 0x10; @Data public abstract static class BaseRequest implements Serializable { diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/HgCmdClient.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/HgCmdClient.java index 6a73639e67..890dfe095f 100644 --- a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/HgCmdClient.java +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/HgCmdClient.java @@ -23,6 +23,18 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; +import org.apache.hugegraph.store.cmd.request.BatchPutRequest; +import org.apache.hugegraph.store.cmd.request.CleanDataRequest; +import org.apache.hugegraph.store.cmd.request.CreateRaftRequest; +import org.apache.hugegraph.store.cmd.request.DestroyRaftRequest; +import org.apache.hugegraph.store.cmd.request.GetStoreInfoRequest; +import org.apache.hugegraph.store.cmd.request.RedirectRaftTaskRequest; +import org.apache.hugegraph.store.cmd.request.UpdatePartitionRequest; +import org.apache.hugegraph.store.cmd.response.BatchPutResponse; +import org.apache.hugegraph.store.cmd.response.CleanDataResponse; +import org.apache.hugegraph.store.cmd.response.GetStoreInfoResponse; +import org.apache.hugegraph.store.cmd.response.RedirectRaftTaskResponse; +import org.apache.hugegraph.store.cmd.response.UpdatePartitionResponse; import org.apache.hugegraph.store.meta.Partition; import org.apache.hugegraph.store.meta.Store; @@ -137,6 +149,10 @@ public UpdatePartitionResponse raftUpdatePartition(UpdatePartitionRequest reques return (UpdatePartitionResponse) tryInternalCallSyncWithRpc(request); } + public RedirectRaftTaskResponse redirectRaftTask(RedirectRaftTaskRequest request) { + return (RedirectRaftTaskResponse) tryInternalCallSyncWithRpc(request); + } + /** * Find Leader, retry on error, handle Leader redirection * @@ -165,6 +181,7 @@ public HgCmdBase.BaseResponse tryInternalCallSyncWithRpc(HgCmdBase.BaseRequest r && response.partitionLeaders != null ) { // When returning leader drift, and partitionLeaders is not empty, need to reset the leader. + Thread.sleep(i * 1000L); } else { log.error( "HgCmdClient tryInternalCallSyncWithRpc error msg {} leaders is {}", diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/HgCmdProcessor.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/HgCmdProcessor.java index e0710ef97e..56bc7918bc 100644 --- a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/HgCmdProcessor.java +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/HgCmdProcessor.java @@ -22,6 +22,23 @@ import java.util.concurrent.TimeUnit; import org.apache.hugegraph.store.HgStoreEngine; +import org.apache.hugegraph.store.PartitionEngine; +import org.apache.hugegraph.store.cmd.request.BatchPutRequest; +import org.apache.hugegraph.store.cmd.request.BlankTaskRequest; +import org.apache.hugegraph.store.cmd.request.CleanDataRequest; +import org.apache.hugegraph.store.cmd.request.CreateRaftRequest; +import org.apache.hugegraph.store.cmd.request.DestroyRaftRequest; +import org.apache.hugegraph.store.cmd.request.GetStoreInfoRequest; +import org.apache.hugegraph.store.cmd.request.RedirectRaftTaskRequest; +import org.apache.hugegraph.store.cmd.request.UpdatePartitionRequest; +import org.apache.hugegraph.store.cmd.response.BatchPutResponse; +import org.apache.hugegraph.store.cmd.response.CleanDataResponse; +import org.apache.hugegraph.store.cmd.response.CreateRaftResponse; +import org.apache.hugegraph.store.cmd.response.DefaultResponse; +import org.apache.hugegraph.store.cmd.response.DestroyRaftResponse; +import org.apache.hugegraph.store.cmd.response.GetStoreInfoResponse; +import org.apache.hugegraph.store.cmd.response.RedirectRaftTaskResponse; +import org.apache.hugegraph.store.cmd.response.UpdatePartitionResponse; import org.apache.hugegraph.store.meta.Partition; import org.apache.hugegraph.store.raft.RaftClosure; import org.apache.hugegraph.store.raft.RaftOperation; @@ -56,6 +73,8 @@ public static void registerProcessor(final RpcServer rpcServer, final HgStoreEng rpcServer.registerProcessor(new HgCmdProcessor<>(UpdatePartitionRequest.class, engine)); rpcServer.registerProcessor(new HgCmdProcessor<>(CreateRaftRequest.class, engine)); rpcServer.registerProcessor(new HgCmdProcessor<>(DestroyRaftRequest.class, engine)); + rpcServer.registerProcessor(new HgCmdProcessor<>(BlankTaskRequest.class, engine)); + rpcServer.registerProcessor(new HgCmdProcessor<>(ProcessBuilder.Redirect.class, engine)); } @Override @@ -93,6 +112,17 @@ public void handleRequest(RpcContext rpcCtx, T request) { handleDestroyRaft((DestroyRaftRequest) request, (DestroyRaftResponse) response); break; } + case HgCmdBase.BLANK_TASK: { + response = new DefaultResponse(); + addBlankTask((BlankTaskRequest) request, (DefaultResponse) response); + break; + } + case HgCmdBase.REDIRECT_RAFT_TASK: { + response = new RedirectRaftTaskResponse(); + handleRedirectRaftTask((RedirectRaftTaskRequest) request, + (RedirectRaftTaskResponse) response); + break; + } default: { log.warn("HgCmdProcessor magic {} is not recognized ", request.magic()); } @@ -138,6 +168,39 @@ public void handleDestroyRaft(DestroyRaftRequest request, DestroyRaftResponse re response.setStatus(Status.OK); } + public void handleRedirectRaftTask(RedirectRaftTaskRequest request, + RedirectRaftTaskResponse response) { + log.info("RedirectRaftTaskNode rpc call received, {}", request.getPartitionId()); + raftSyncTask(request.getGraphName(), request.getPartitionId(), request.getRaftOp(), + request.getData(), response); + response.setStatus(Status.OK); + } + + public void addBlankTask(BlankTaskRequest request, DefaultResponse response) { + try { + int partitionId = request.getPartitionId(); + PartitionEngine pe = engine.getPartitionEngine(partitionId); + if (pe.isLeader()) { + CountDownLatch latch = new CountDownLatch(1); + RaftClosure closure = s -> { + if (s.isOk()) { + response.setStatus(Status.OK); + } else { + log.error("doBlankTask in cmd with error: {}", s.getErrorMsg()); + response.setStatus(Status.EXCEPTION); + } + latch.countDown(); + }; + pe.addRaftTask(RaftOperation.create(RaftOperation.SYNC_BLANK_TASK), closure); + latch.await(); + } else { + response.setStatus(Status.LEADER_REDIRECT); + } + } catch (Exception e) { + response.setStatus(Status.EXCEPTION); + } + } + /** * raft notify replica synchronization execution * @@ -147,9 +210,14 @@ public void handleDestroyRaft(DestroyRaftRequest request, DestroyRaftResponse re */ private void raftSyncTask(HgCmdBase.BaseRequest request, HgCmdBase.BaseResponse response, final byte op) { + raftSyncTask(request.getGraphName(), request.getPartitionId(), op, request, response); + } + + private void raftSyncTask(String graph, int partId, byte op, Object raftReq, + HgCmdBase.BaseResponse response) { CountDownLatch latch = new CountDownLatch(1); - engine.addRaftTask(request.getGraphName(), request.getPartitionId(), - RaftOperation.create(op, request), new RaftClosure() { + engine.addRaftTask(graph, partId, + RaftOperation.create(op, raftReq), new RaftClosure() { @Override public void run(com.alipay.sofa.jraft.Status status) { Status responseStatus = Status.UNKNOWN; diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/UpdatePartitionRequest.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/UpdatePartitionRequest.java index 016b162870..12dbc372ce 100644 --- a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/UpdatePartitionRequest.java +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/UpdatePartitionRequest.java @@ -22,6 +22,7 @@ import lombok.Data; @Data +@Deprecated public class UpdatePartitionRequest extends HgCmdBase.BaseRequest { private int startKey; diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/UpdatePartitionResponse.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/UpdatePartitionResponse.java index 49bb1c7cb5..5cec121442 100644 --- a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/UpdatePartitionResponse.java +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/UpdatePartitionResponse.java @@ -17,6 +17,7 @@ package org.apache.hugegraph.store.cmd; +@Deprecated public class UpdatePartitionResponse extends HgCmdBase.BaseResponse { } diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/request/BatchPutRequest.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/request/BatchPutRequest.java new file mode 100644 index 0000000000..1e09424da1 --- /dev/null +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/request/BatchPutRequest.java @@ -0,0 +1,54 @@ +/* + * 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.store.cmd.request; + +import java.io.Serializable; +import java.util.List; + +import org.apache.hugegraph.store.cmd.HgCmdBase; + +import lombok.Data; + +@Data +public class BatchPutRequest extends HgCmdBase.BaseRequest { + + private List entries; + + @Override + public byte magic() { + return HgCmdBase.BATCH_PUT; + } + + @Data + public static class KV implements Serializable { + + private String table; + private int code; + private byte[] key; + private byte[] value; + + public static KV of(String table, int code, byte[] key, byte[] value) { + KV kv = new KV(); + kv.table = table; + kv.code = code; + kv.key = key; + kv.value = value; + return kv; + } + } +} diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/request/BlankTaskRequest.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/request/BlankTaskRequest.java new file mode 100644 index 0000000000..08abc59a21 --- /dev/null +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/request/BlankTaskRequest.java @@ -0,0 +1,32 @@ +/* + * 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.store.cmd.request; + +import org.apache.hugegraph.store.cmd.HgCmdBase; + +/** + * @author zhangyingjie + * @date 2023/8/21 + **/ +public class BlankTaskRequest extends HgCmdBase.BaseRequest { + + @Override + public byte magic() { + return HgCmdBase.BLANK_TASK; + } +} diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/CleanDataRequest.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/request/CleanDataRequest.java similarity index 96% rename from hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/CleanDataRequest.java rename to hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/request/CleanDataRequest.java index 35540687bf..1fbfb5656d 100644 --- a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/CleanDataRequest.java +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/request/CleanDataRequest.java @@ -15,10 +15,11 @@ * limitations under the License. */ -package org.apache.hugegraph.store.cmd; +package org.apache.hugegraph.store.cmd.request; import org.apache.hugegraph.pd.grpc.pulse.CleanPartition; import org.apache.hugegraph.pd.grpc.pulse.CleanType; +import org.apache.hugegraph.store.cmd.HgCmdBase; import org.apache.hugegraph.store.meta.Partition; import lombok.Data; diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/request/CreateRaftRequest.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/request/CreateRaftRequest.java new file mode 100644 index 0000000000..1897c850c8 --- /dev/null +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/request/CreateRaftRequest.java @@ -0,0 +1,73 @@ +/* + * 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.store.cmd.request; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.hugegraph.pd.grpc.Metapb; +import org.apache.hugegraph.store.cmd.HgCmdBase; + +import com.alipay.sofa.jraft.conf.Configuration; +import com.google.protobuf.InvalidProtocolBufferException; + +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class CreateRaftRequest extends HgCmdBase.BaseRequest { + + List values = new ArrayList<>(); + String peers; + + public List getPartitions() { + try { + List partitions = new ArrayList<>(); + for (byte[] partition : values) { + partitions.add(Metapb.Partition.parseFrom(partition)); + } + return partitions; + } catch (InvalidProtocolBufferException e) { + log.error("CreateRaftNodeProcessor parse partition exception }", e); + } + return new ArrayList<>(); + } + + public void addPartition(Metapb.Partition partition) { + values.add(partition.toByteArray()); + } + + public Configuration getConf() { + Configuration conf = null; + if (peers != null) { + conf = new Configuration(); + conf.parse(this.peers); + } + return conf; + } + + public void setConf(Configuration conf) { + if (conf != null) { + this.peers = conf.toString(); + } + } + + @Override + public byte magic() { + return HgCmdBase.CREATE_RAFT; + } +} diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/DbCompactionRequest.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/request/DbCompactionRequest.java similarity index 91% rename from hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/DbCompactionRequest.java rename to hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/request/DbCompactionRequest.java index 7952f170d1..5da60f0bf0 100644 --- a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/DbCompactionRequest.java +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/request/DbCompactionRequest.java @@ -15,7 +15,9 @@ * limitations under the License. */ -package org.apache.hugegraph.store.cmd; +package org.apache.hugegraph.store.cmd.request; + +import org.apache.hugegraph.store.cmd.HgCmdBase; import lombok.Data; @@ -29,3 +31,4 @@ public byte magic() { return HgCmdBase.ROCKSDB_COMPACTION; } } + diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/DestroyRaftRequest.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/request/DestroyRaftRequest.java similarity index 87% rename from hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/DestroyRaftRequest.java rename to hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/request/DestroyRaftRequest.java index 10bf1c30b7..ecd7e7cf0e 100644 --- a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/DestroyRaftRequest.java +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/request/DestroyRaftRequest.java @@ -15,17 +15,19 @@ * limitations under the License. */ -package org.apache.hugegraph.store.cmd; +package org.apache.hugegraph.store.cmd.request; import java.util.ArrayList; import java.util.List; +import org.apache.hugegraph.store.cmd.HgCmdBase; + import lombok.Data; @Data public class DestroyRaftRequest extends HgCmdBase.BaseRequest { - private final List graphNames = new ArrayList<>(); + private List graphNames = new ArrayList<>(); public void addGraphName(String graphName) { graphNames.add(graphName); diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/GetStoreInfoRequest.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/request/GetStoreInfoRequest.java similarity index 90% rename from hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/GetStoreInfoRequest.java rename to hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/request/GetStoreInfoRequest.java index 68f0d7f329..0b194a5051 100644 --- a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/GetStoreInfoRequest.java +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/request/GetStoreInfoRequest.java @@ -15,7 +15,9 @@ * limitations under the License. */ -package org.apache.hugegraph.store.cmd; +package org.apache.hugegraph.store.cmd.request; + +import org.apache.hugegraph.store.cmd.HgCmdBase; public class GetStoreInfoRequest extends HgCmdBase.BaseRequest { diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/request/RedirectRaftTaskRequest.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/request/RedirectRaftTaskRequest.java new file mode 100644 index 0000000000..efb430a696 --- /dev/null +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/request/RedirectRaftTaskRequest.java @@ -0,0 +1,42 @@ +/* + * 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.store.cmd.request; + +import org.apache.hugegraph.store.cmd.HgCmdBase; + +import lombok.Data; + +@Data +public class RedirectRaftTaskRequest extends HgCmdBase.BaseRequest { + + final byte raftOp; + + private Object data; + + public RedirectRaftTaskRequest(String graph, Integer partitionId, byte raftOp, Object data) { + this.raftOp = raftOp; + this.data = data; + setGraphName(graph); + setPartitionId(partitionId); + } + + @Override + public byte magic() { + return HgCmdBase.REDIRECT_RAFT_TASK; + } +} diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/request/UpdatePartitionRequest.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/request/UpdatePartitionRequest.java new file mode 100644 index 0000000000..430756178a --- /dev/null +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/request/UpdatePartitionRequest.java @@ -0,0 +1,37 @@ +/* + * 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.store.cmd.request; + +import org.apache.hugegraph.pd.grpc.Metapb; +import org.apache.hugegraph.store.cmd.HgCmdBase; + +import lombok.Data; + +@Data +public class UpdatePartitionRequest extends HgCmdBase.BaseRequest { + + private int startKey; + private int endKey; + + private Metapb.PartitionState workState; + + @Override + public byte magic() { + return HgCmdBase.RAFT_UPDATE_PARTITION; + } +} diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/response/BatchPutResponse.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/response/BatchPutResponse.java new file mode 100644 index 0000000000..c687a1c8ea --- /dev/null +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/response/BatchPutResponse.java @@ -0,0 +1,24 @@ +/* + * 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.store.cmd.response; + +import org.apache.hugegraph.store.cmd.HgCmdBase; + +public class BatchPutResponse extends HgCmdBase.BaseResponse { + +} diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/response/CleanDataResponse.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/response/CleanDataResponse.java new file mode 100644 index 0000000000..cfa9454166 --- /dev/null +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/response/CleanDataResponse.java @@ -0,0 +1,24 @@ +/* + * 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.store.cmd.response; + +import org.apache.hugegraph.store.cmd.HgCmdBase; + +public class CleanDataResponse extends HgCmdBase.BaseResponse { + +} diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/response/CreateRaftResponse.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/response/CreateRaftResponse.java new file mode 100644 index 0000000000..c58dddfe1c --- /dev/null +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/response/CreateRaftResponse.java @@ -0,0 +1,24 @@ +/* + * 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.store.cmd.response; + +import org.apache.hugegraph.store.cmd.HgCmdBase; + +public class CreateRaftResponse extends HgCmdBase.BaseResponse { + +} diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/response/DbCompactionResponse.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/response/DbCompactionResponse.java new file mode 100644 index 0000000000..5c81833aa2 --- /dev/null +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/response/DbCompactionResponse.java @@ -0,0 +1,24 @@ +/* + * 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.store.cmd.response; + +import org.apache.hugegraph.store.cmd.HgCmdBase; + +public class DbCompactionResponse extends HgCmdBase.BaseResponse { + +} diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/response/DefaultResponse.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/response/DefaultResponse.java new file mode 100644 index 0000000000..bfa27cbe49 --- /dev/null +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/response/DefaultResponse.java @@ -0,0 +1,28 @@ +/* + * 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.store.cmd.response; + +import org.apache.hugegraph.store.cmd.HgCmdBase.BaseResponse; + +/** + * @author zhangyingjie + * @date 2023/8/21 + **/ +public class DefaultResponse extends BaseResponse { + +} diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/response/DestroyRaftResponse.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/response/DestroyRaftResponse.java new file mode 100644 index 0000000000..0e037e0435 --- /dev/null +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/response/DestroyRaftResponse.java @@ -0,0 +1,24 @@ +/* + * 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.store.cmd.response; + +import org.apache.hugegraph.store.cmd.HgCmdBase; + +public class DestroyRaftResponse extends HgCmdBase.BaseResponse { + +} diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/response/GetStoreInfoResponse.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/response/GetStoreInfoResponse.java new file mode 100644 index 0000000000..779c2785de --- /dev/null +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/response/GetStoreInfoResponse.java @@ -0,0 +1,45 @@ +/* + * 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.store.cmd.response; + +import org.apache.hugegraph.pd.grpc.Metapb; +import org.apache.hugegraph.store.cmd.HgCmdBase; +import org.apache.hugegraph.store.meta.Store; + +import com.google.protobuf.InvalidProtocolBufferException; + +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class GetStoreInfoResponse extends HgCmdBase.BaseResponse { + + private byte[] store; + + public Store getStore() { + try { + return new Store(Metapb.Store.parseFrom(this.store)); + } catch (InvalidProtocolBufferException e) { + log.error("GetStoreResponse parse exception {}", e); + } + return null; + } + + public void setStore(Store store) { + this.store = store.getProtoObj().toByteArray(); + } +} diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/response/RedirectRaftTaskResponse.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/response/RedirectRaftTaskResponse.java new file mode 100644 index 0000000000..9ee7ca45c5 --- /dev/null +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/response/RedirectRaftTaskResponse.java @@ -0,0 +1,24 @@ +/* + * 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.store.cmd.response; + +import org.apache.hugegraph.store.cmd.HgCmdBase; + +public class RedirectRaftTaskResponse extends HgCmdBase.BaseResponse { + +} diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/response/UpdatePartitionResponse.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/response/UpdatePartitionResponse.java new file mode 100644 index 0000000000..9901ab5428 --- /dev/null +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/response/UpdatePartitionResponse.java @@ -0,0 +1,24 @@ +/* + * 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.store.cmd.response; + +import org.apache.hugegraph.store.cmd.HgCmdBase; + +public class UpdatePartitionResponse extends HgCmdBase.BaseResponse { + +} diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/consts/PoolNames.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/consts/PoolNames.java new file mode 100644 index 0000000000..b3d2ed586a --- /dev/null +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/consts/PoolNames.java @@ -0,0 +1,35 @@ +/* + * 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.store.consts; + +/** + * @author zhangyingjie + * @date 2023/10/30 + **/ +public class PoolNames { + + public static final String GRPC = "hg-grpc"; + public static final String SCAN = "hg-scan"; + public static final String SCAN_V2 = "hg-scan-v2"; + public static final String I_JOB = "hg-i-job"; + public static final String U_JOB = "hg-u-job"; + public static final String COMPACT = "hg-compact"; + public static final String HEARTBEAT = "hg-heartbeat"; + public static final String P_HEARTBEAT = "hg-p-heartbeat"; + +} diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/listener/PartitionChangedListener.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/listener/PartitionChangedListener.java new file mode 100644 index 0000000000..5b4e5f80f4 --- /dev/null +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/listener/PartitionChangedListener.java @@ -0,0 +1,34 @@ +/* + * 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.store.listener; + +import org.apache.hugegraph.store.cmd.request.UpdatePartitionRequest; +import org.apache.hugegraph.store.cmd.response.UpdatePartitionResponse; +import org.apache.hugegraph.store.meta.Partition; + +/** + * @author zhangyingjie + * @date 2023/9/11 + * Partition对象被修改消息 + **/ +public interface PartitionChangedListener { + + void onChanged(Partition partition); + + UpdatePartitionResponse rangeOrStateChanged(UpdatePartitionRequest request); +} diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/listener/PartitionStateListener.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/listener/PartitionStateListener.java new file mode 100644 index 0000000000..06b9918005 --- /dev/null +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/listener/PartitionStateListener.java @@ -0,0 +1,34 @@ +/* + * 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.store.listener; + +import java.util.List; + +import org.apache.hugegraph.pd.grpc.Metapb; +import org.apache.hugegraph.store.meta.Partition; +import org.apache.hugegraph.store.meta.PartitionRole; + +public interface PartitionStateListener { + + // 分区角色发生改变 + void partitionRoleChanged(Partition partition, PartitionRole newRole); + + // 分区发生改变 + void partitionShardChanged(Partition partition, List oldShards, + List newShards); +} diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/listener/StoreStateListener.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/listener/StoreStateListener.java new file mode 100644 index 0000000000..11c607338b --- /dev/null +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/listener/StoreStateListener.java @@ -0,0 +1,26 @@ +/* + * 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.store.listener; + +import org.apache.hugegraph.pd.grpc.Metapb; +import org.apache.hugegraph.store.meta.Store; + +public interface StoreStateListener { + + void stateChanged(Store store, Metapb.StoreState oldState, Metapb.StoreState newState); +} diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/meta/GraphIdManager.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/meta/GraphIdManager.java index c98b03935d..d3cc00f216 100644 --- a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/meta/GraphIdManager.java +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/meta/GraphIdManager.java @@ -18,19 +18,25 @@ package org.apache.hugegraph.store.meta; import java.nio.ByteBuffer; +import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.Collectors; import org.apache.hugegraph.store.meta.base.DBSessionBuilder; import org.apache.hugegraph.store.meta.base.PartitionMetaStore; +import org.apache.hugegraph.store.term.Bits; import org.apache.hugegraph.store.util.HgStoreException; import com.google.protobuf.Int64Value; +import lombok.extern.slf4j.Slf4j; + /** * GraphId Manager, maintains a self-incrementing circular ID, responsible for managing the mapping between GraphName and GraphId. */ +@Slf4j public class GraphIdManager extends PartitionMetaStore { protected static final String GRAPH_ID_PREFIX = "@GRAPH_ID@"; @@ -39,27 +45,6 @@ public class GraphIdManager extends PartitionMetaStore { static Object cidLock = new Object(); final DBSessionBuilder sessionBuilder; final int partitionId; - // public long getGraphId(String graphName) { - // if (!graphIdCache.containsKey(graphName)) { - // synchronized (graphIdLock) { - // if (!graphIdCache.containsKey(graphName)) { - // byte[] key = MetadataKeyHelper.getGraphIDKey(graphName); - // Int64Value id = get(Int64Value.parser(), key); - // if (id == null) { - // id = Int64Value.of(getCId(GRAPH_ID_PREFIX, maxGraphID)); - // if (id.getValue() == -1) { - // throw new HgStoreException(HgStoreException.EC_FAIL, - // "The number of graphs exceeds the maximum 65535"); - // } - // put(key, id); - // flush(); - // } - // graphIdCache.put(graphName, id.getValue()); - // } - // } - // } - // return graphIdCache.get(graphName); - // } private final Map graphIdCache = new ConcurrentHashMap<>(); public GraphIdManager(DBSessionBuilder sessionBuilder, int partitionId) { @@ -79,12 +64,34 @@ public long getGraphId(String graphName) { byte[] key = MetadataKeyHelper.getGraphIDKey(graphName); Int64Value id = get(Int64Value.parser(), key); if (id == null) { - id = Int64Value.of(getCId(GRAPH_ID_PREFIX, maxGraphID)); + id = Int64Value.of(maxGraphID); + } + l = id.getValue(); + graphIdCache.put(graphName, l); + } + } + } + return l; + } + + public long getGraphIdOrCreate(String graphName) { + + Long l = graphIdCache.get(graphName); + if (l == null || l == maxGraphID) { + synchronized (graphIdLock) { + if ((l = graphIdCache.get(graphName)) == null || l == maxGraphID) { + byte[] key = MetadataKeyHelper.getGraphIDKey(graphName); + Int64Value id = get(Int64Value.parser(), key); + if (id == null) { + id = Int64Value.of(getCId(GRAPH_ID_PREFIX, maxGraphID - 1)); if (id.getValue() == -1) { throw new HgStoreException(HgStoreException.EC_FAIL, "The number of graphs exceeds the maximum " + "65535"); } + log.info("partition: {}, Graph ID {} is allocated for graph {}, stack: {}", + this.partitionId, id.getValue(), graphName, + Arrays.toString(Thread.currentThread().getStackTrace())); put(key, id); flush(); } @@ -112,7 +119,20 @@ public long releaseGraphId(String graphName) { } /** - * Get auto-increment non-repetitive id, start from 0 after reaching the limit. + * 为了兼容受影响的 graph,保证 g+v 表中没有数据 + * + * @return 有数据返回 false,没有返回 true + */ + private boolean checkCount(long l) { + var start = new byte[2]; + Bits.putShort(start, 0, (short) l); + try (var itr = sessionBuilder.getSession(partitionId).sessionOp().scan("g+v", start)) { + return itr == null || !itr.hasNext(); + } + } + + /** + * 获取自增循环不重复 id, 达到上限后从 0 开始自增 * * @param key key * @param max max id limit, after reaching this value, it will reset to 0 and start incrementing again. @@ -127,24 +147,19 @@ protected long getCId(String key, long max) { // Find an unused cid List ids = scan(Int64Value.parser(), genCIDSlotKey(key, current), genCIDSlotKey(key, max)); - for (Int64Value id : ids) { - if (current == id.getValue()) { + var idSet = ids.stream().map(Int64Value::getValue).collect(Collectors.toSet()); + + while (idSet.contains(current) || !checkCount(current)) { current++; - } else { - break; - } } - if (current == max) { + if (current == max - 1) { current = 0; ids = scan(Int64Value.parser(), genCIDSlotKey(key, current), genCIDSlotKey(key, last)); - for (Int64Value id : ids) { - if (current == id.getValue()) { + idSet = ids.stream().map(Int64Value::getValue).collect(Collectors.toSet()); + while (idSet.contains(current) || !checkCount(current)) { current++; - } else { - break; - } } } diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/meta/PartitionManager.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/meta/PartitionManager.java index ffd1349a91..ac98d39282 100644 --- a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/meta/PartitionManager.java +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/meta/PartitionManager.java @@ -26,6 +26,7 @@ import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReadWriteLock; @@ -37,8 +38,10 @@ import org.apache.hugegraph.pd.grpc.Metapb; import org.apache.hugegraph.store.HgStoreEngine; import org.apache.hugegraph.store.business.BusinessHandlerImpl; -import org.apache.hugegraph.store.cmd.UpdatePartitionRequest; -import org.apache.hugegraph.store.cmd.UpdatePartitionResponse; +import org.apache.hugegraph.store.cmd.HgCmdClient; +import org.apache.hugegraph.store.cmd.request.UpdatePartitionRequest; +import org.apache.hugegraph.store.cmd.response.UpdatePartitionResponse; +import org.apache.hugegraph.store.listener.PartitionChangedListener; import org.apache.hugegraph.store.meta.base.GlobalMetaStore; import org.apache.hugegraph.store.options.HgStoreEngineOptions; import org.apache.hugegraph.store.options.MetadataOptions; @@ -72,6 +75,7 @@ public class PartitionManager extends GlobalMetaStore { // Record all partition information of this machine, consistent with rocksdb storage. private Map> partitions; + private HgCmdClient cmdClient; public PartitionManager(PdProvider pdProvider, HgStoreEngineOptions options) { super(new MetadataOptions() {{ @@ -225,7 +229,7 @@ private void loadPartitions() { var partIds = new HashSet(); for (String path : this.options.getDataPath().split(",")) { File[] dirs = new File(path + "/" + HgStoreEngineOptions.DB_Path_Prefix).listFiles(); - if (dirs == null) { + if (dirs == null || dirs.length == 0) { continue; } @@ -241,6 +245,8 @@ private void loadPartitions() { } } + Set normalPartitions = new HashSet<>(); + // Once according to the partition read for (int partId : partIds) { if (!resetPartitionPath(partId)) { @@ -249,18 +255,23 @@ private void loadPartitions() { continue; } - for (var metaPart : wrapper.scan(partId, Metapb.Partition.parser(), key)) { + var metaParts = wrapper.scan(partId, Metapb.Partition.parser(), key); + int countOfPartition = 0; + + var shards = pdProvider.getShardGroup(partId).getShardsList(); + + for (var metaPart : metaParts) { var graph = metaPart.getGraphName(); var pdPartition = pdProvider.getPartitionByID(graph, metaPart.getId()); boolean isLegeal = false; - var shards = pdProvider.getShardGroup(metaPart.getId()).getShardsList(); - if (pdPartition != null) { // Check if it contains this store id if (shards.stream().anyMatch(s -> s.getStoreId() == storeId)) { isLegeal = true; } + } else { + continue; } if (isLegeal) { @@ -268,6 +279,8 @@ private void loadPartitions() { partitions.put(graph, new ConcurrentHashMap<>()); } + countOfPartition += 1; + Partition partition = new Partition(metaPart); partition.setWorkState(Metapb.PartitionState.PState_Normal); // Start recovery work state partitions.get(graph).put(partition.getId(), partition); @@ -284,6 +297,19 @@ private void loadPartitions() { System.exit(0); } } + + if (countOfPartition > 0) { + // 分区数据正常 + normalPartitions.add(partId); + } + wrapper.close(partId); + } + + // 删掉多余的分区存储路径,被迁移走的分区,有可能还会迁回来 + for (var location : storeMetadata.getPartitionStores()) { + if (!normalPartitions.contains(location.getPartitionId())) { + storeMetadata.removePartitionStore(location.getPartitionId()); + } } } @@ -611,7 +637,7 @@ public ShardGroup getShardGroup(int partitionId) { Metapb.ShardGroup.parser()); if (shardGroup == null) { - shardGroup = pdProvider.getShardGroup(partitionId); + shardGroup = pdProvider.getShardGroupDirect(partitionId); if (shardGroup != null) { // local not found, write back to db from pd @@ -726,6 +752,18 @@ public List getLeaderPartitionIds(String graph) { return ids; } + public Set getLeaderPartitionIdSet() { + Set ids = new HashSet<>(); + partitions.forEach((key, value) -> { + value.forEach((k, v) -> { + if (!useRaft || v.isLeader()) { + ids.add(k); + } + }); + }); + return ids; + } + /** * Generate partition peer string, containing priority information * * @@ -833,15 +871,15 @@ public Store getStoreByRaftEndpoint(ShardGroup group, String endpoint) { return result[0]; } - public Shard getShardByRaftEndpoint(ShardGroup group, String endpoint) { - final Shard[] result = {new Shard()}; - group.getShards().forEach((shard) -> { + public Shard getShardByEndpoint(ShardGroup group, String endpoint) { + List shards = group.getShards(); + for (Shard shard : shards) { Store store = getStore(shard.getStoreId()); if (store != null && store.getRaftAddress().equalsIgnoreCase(endpoint)) { - result[0] = shard; + return shard; } - }); - return result[0]; + } + return new Shard(); } /** @@ -885,6 +923,16 @@ public String getDbDataPath(int partitionId, String dbName) { return location; } + /** + * db 存储路径 + * + * @return location/db + */ + public String getDbDataPath(int partitionId) { + String dbName = BusinessHandlerImpl.getDbName(partitionId); + return getDbDataPath(partitionId, dbName); + } + public void reportTask(MetaTask.Task task) { try { pdProvider.reportTask(task); @@ -908,14 +956,39 @@ public PartitionMetaStoreWrapper getWrapper() { return wrapper; } - /** - * Partition object is modified message - */ - public interface PartitionChangedListener { + public void setCmdClient(HgCmdClient client) { + this.cmdClient = client; + } - void onChanged(Partition partition); + public UpdatePartitionResponse updateState(Metapb.Partition partition, + Metapb.PartitionState state) { + // 分区分裂时,主动需要查找 leader 进行同步信息 + UpdatePartitionRequest request = new UpdatePartitionRequest(); + request.setWorkState(state); + request.setPartitionId(partition.getId()); + request.setGraphName(partition.getGraphName()); + return cmdClient.raftUpdatePartition(request); + } - UpdatePartitionResponse rangeOrStateChanged(UpdatePartitionRequest request); + public UpdatePartitionResponse updateRange(Metapb.Partition partition, int startKey, + int endKey) { + // 分区分裂时,主动需要查找 leader 进行同步信息 + UpdatePartitionRequest request = new UpdatePartitionRequest(); + request.setStartKey(startKey); + request.setEndKey(endKey); + request.setPartitionId(partition.getId()); + request.setGraphName(partition.getGraphName()); + return cmdClient.raftUpdatePartition(request); + } + + public List getPartitionIds(String graph) { + List ids = new ArrayList<>(); + if (partitions.containsKey(graph)) { + partitions.get(graph).forEach((k, v) -> { + ids.add(k); + }); + } + return ids; } } diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/meta/ShardGroup.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/meta/ShardGroup.java index 892af940b3..4b3a5a618f 100644 --- a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/meta/ShardGroup.java +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/meta/ShardGroup.java @@ -52,11 +52,17 @@ public static ShardGroup from(Metapb.ShardGroup meta) { shardGroup.setId(meta.getId()); shardGroup.setVersion(meta.getVersion()); shardGroup.setConfVersion(meta.getConfVer()); - shardGroup.setShards(meta.getShardsList().stream().map(Shard::fromMetaPbShard) - .collect(Collectors.toList())); + shardGroup.setShards(new CopyOnWriteArrayList<>( + meta.getShardsList().stream().map(Shard::fromMetaPbShard) + .collect(Collectors.toList()))); return shardGroup; } + public ShardGroup addShard(Shard shard) { + this.shards.add(shard); + return this; + } + public synchronized ShardGroup changeLeader(long storeId) { shards.forEach(shard -> { shard.setRole(shard.getStoreId() == storeId ? Metapb.ShardRole.Leader : diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/meta/StoreMetadata.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/meta/StoreMetadata.java index 662b6521f1..6fb54710cc 100644 --- a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/meta/StoreMetadata.java +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/meta/StoreMetadata.java @@ -24,6 +24,7 @@ import java.util.List; import java.util.Map; +import org.apache.commons.io.FileUtils; import org.apache.hugegraph.pd.grpc.Metapb; import org.apache.hugegraph.store.meta.base.GlobalMetaStore; import org.apache.hugegraph.store.options.MetadataOptions; @@ -115,6 +116,16 @@ public Metapb.PartitionStore getPartitionStore(int partitionId) { return get(Metapb.PartitionStore.parser(), key); } + /** + * 删除指定分区对应的存储元数据。 + * + * @param partitionId 分区ID。 + */ + public void removePartitionStore(int partitionId) { + byte[] key = MetadataKeyHelper.getPartitionStoreKey(partitionId); + delete(key); + } + public List getPartitionStores() { byte[] key = MetadataKeyHelper.getPartitionStorePrefix(); return scan(Metapb.PartitionStore.parser(), key); @@ -141,16 +152,14 @@ public void savePartitionRaft(Metapb.PartitionRaft partitionRaft) { } private String getMinDataLocation() { - Map counter = new HashMap<>(); - dataLocations.forEach(l -> { - counter.put(l, Integer.valueOf(0)); - }); - getPartitionStores().forEach(ptStore -> { - if (counter.containsKey(ptStore.getStoreLocation())) { - counter.put(ptStore.getStoreLocation(), - counter.get(ptStore.getStoreLocation()) + 1); + var counter = stateLocByFreeSpace(); + if (counter.isEmpty()) { + counter = stateLocByPartitionCount(); + log.info("allocate db path using partition count: db count stats: {}", counter); + } else { + log.info("allocate db path using free space: db size stats: {}", counter); } - }); + int min = Integer.MAX_VALUE; String location = ""; for (String k : counter.keySet()) { @@ -162,6 +171,91 @@ private String getMinDataLocation() { return location; } + /** + * get location count by allocated db count + * + * @return loc -> db count + */ + private Map stateLocByPartitionCount() { + Map counter = new HashMap<>(); + dataLocations.forEach(l -> counter.put(l, 0)); + + getPartitionStores().forEach(ptStore -> { + if (counter.containsKey(ptStore.getStoreLocation())) { + counter.put(ptStore.getStoreLocation(), + counter.get(ptStore.getStoreLocation()) + 1); + } + }); + return counter; + } + + /** + * get location count by free space + * + * @return location -> free space, return null when disk usage greater than 20% + */ + private Map stateLocByFreeSpace() { + Map counter = new HashMap<>(); + double maxRate = 0; + for (String loc : dataLocations) { + var file = new File(loc); + if (!file.exists()) { + file.mkdirs(); + } + + // 预估大小 + long left = (file.getFreeSpace() - getLocDbSizeDelta(loc)) / 1024 / 1024 * -1; + + var dbSizeRate = FileUtils.sizeOfDirectory(file) / file.getTotalSpace(); + // log.info("loc: {}, dir size {}, total size: {}, rate :{}", loc, FileUtils + // .sizeOfDirectory(file), + // file.getTotalSpace(), dbSizeRate); + if (dbSizeRate > maxRate) { + maxRate = dbSizeRate; + } + counter.put(loc, (int) left); + } + // log.info("max rate: {}", maxRate); + + if (maxRate < 0.2) { + counter.clear(); + } + return counter; + } + + /** + * db file delta by dbs, considering new db + * + * @param path + * @return + */ + private long getLocDbSizeDelta(String path) { + File file = new File(path + "/db"); + if (!file.exists()) { + return 0; + } + + long max = 0; + int n = 0; + int sum = 0; + File[] fs = file.listFiles(); + if (fs != null) { + for (File sub : fs) { + if (sub.isDirectory()) { + continue; + } + + long size = FileUtils.sizeOfDirectory(sub); + if (size > max) { + max = size; + } + n += 1; + } + } + + return max * n - sum; + } + private String getMinRaftLocation() { Map counter = new HashMap<>(); raftLocations.forEach(l -> { diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/meta/asynctask/AbstractAsyncTask.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/meta/asynctask/AbstractAsyncTask.java index 87605c0ad1..485f3b96f2 100644 --- a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/meta/asynctask/AbstractAsyncTask.java +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/meta/asynctask/AbstractAsyncTask.java @@ -25,6 +25,7 @@ import java.io.Serializable; import java.util.UUID; + import lombok.extern.slf4j.Slf4j; @Slf4j diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/meta/asynctask/CleanTask.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/meta/asynctask/CleanTask.java index 1d25c0fa81..473b9c4341 100644 --- a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/meta/asynctask/CleanTask.java +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/meta/asynctask/CleanTask.java @@ -19,7 +19,7 @@ import org.apache.hugegraph.pd.grpc.pulse.CleanType; import org.apache.hugegraph.store.HgStoreEngine; -import org.apache.hugegraph.store.cmd.CleanDataRequest; +import org.apache.hugegraph.store.cmd.request.CleanDataRequest; import lombok.extern.slf4j.Slf4j; diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/metric/SystemMetricService.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/metric/SystemMetricService.java index d376c413e8..80d83ab0d8 100644 --- a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/metric/SystemMetricService.java +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/metric/SystemMetricService.java @@ -78,10 +78,10 @@ public Map getSystemMetrics() { loadDiskInfo(systemMetrics); // disk io - loadDiskIo(systemMetrics); - + //loadDiskIo(systemMetrics); + // // network - loadNetFlowInfo(systemMetrics); + //loadNetFlowInfo(systemMetrics); // rocksdb loadRocksDbInfo(systemMetrics); @@ -134,25 +134,25 @@ private void loadDiskInfo(Map map) { map.put("disk.usable_size", usable / MIB); } - private void loadDiskIo(Map map) { - for (Map.Entry entry : getDiskIoData().entrySet()) { - map.put(entry.getKey(), entry.getValue().longValue()); - } - } - - private void loadNetFlowInfo(Map map) { - for (Map.Entry> entry : getTraffic().entrySet()) { - // exclude none-functional network interface - map.put("network." + entry.getKey() + ".sent_bytes", - entry.getValue().get(0) / 1024 / 1024); - map.put("network." + entry.getKey() + ".recv_bytes", - entry.getValue().get(1) / 1024 / 1024); - map.put("network." + entry.getKey() + ".sent_rates", - entry.getValue().get(2) / 1024 / 1024); - map.put("network." + entry.getKey() + ".recv_rates", - entry.getValue().get(3) / 1024 / 1024); - } - } + //private void loadDiskIo(Map map) { + // for (Map.Entry entry : getDiskIoData().entrySet()) { + // map.put(entry.getKey(), entry.getValue().longValue()); + // } + //} + // + //private void loadNetFlowInfo(Map map) { + // for (Map.Entry> entry : getTraffic().entrySet()) { + // // exclude none-functional network interface + // map.put("network." + entry.getKey() + ".sent_bytes", entry.getValue().get(0) / 1024 + // / 1024); + // map.put("network." + entry.getKey() + ".recv_bytes", entry.getValue().get(1) / 1024 + // / 1024); + // map.put("network." + entry.getKey() + ".sent_rates", entry.getValue().get(2) / 1024 + // / 1024); + // map.put("network." + entry.getKey() + ".recv_rates", entry.getValue().get(3) / 1024 + // / 1024); + // } + //} private void loadRocksDbInfo(Map map) { Map dbMem = @@ -205,89 +205,89 @@ private void loadRocksDbInfo(Map map) { * * @return */ - private Map> getTraffic() { - deque.add(loadTrafficData()); - - if (deque.size() < 2) { - return new HashMap<>(); - } - // keep 2 copies - while (deque.size() > 2) { - deque.removeFirst(); - } - - // compare - Map> result = new HashMap<>(); - Map> currentFlows = deque.getLast(); - Map> preFlows = deque.getFirst(); - - for (Map.Entry> entry : currentFlows.entrySet()) { - if (preFlows.containsKey(entry.getKey())) { - List prev = preFlows.get(entry.getKey()); - List now = preFlows.get(entry.getKey()); - // no traffic - if (now.get(0) == 0) { - continue; - } - long diff = now.get(2) - prev.get(2); - diff = diff > 0 ? diff : 1L; - result.put( - entry.getKey(), - Arrays.asList( - now.get(0) - prev.get(0), - now.get(1) - prev.get(1), - // rate rate - (now.get(0) - prev.get(0)) / diff, - // recv rate - (now.get(1) - prev.get(1)) / diff)); - } - } - return result; - } + //private Map> getTraffic() { + // deque.add(loadTrafficData()); + // + // if (deque.size() < 2) { + // return new HashMap<>(); + // } + // // keep 2 copies + // while (deque.size() > 2) { + // deque.removeFirst(); + // } + // + // // compare + // Map> result = new HashMap<>(); + // Map> currentFlows = deque.getLast(); + // Map> preFlows = deque.getFirst(); + // + // for (Map.Entry> entry : currentFlows.entrySet()) { + // if (preFlows.containsKey(entry.getKey())) { + // List prev = preFlows.get(entry.getKey()); + // List now = preFlows.get(entry.getKey()); + // // no traffic + // if (now.get(0) == 0) { + // continue; + // } + // long diff = now.get(2) - prev.get(2); + // diff = diff > 0 ? diff : 1L; + // result.put( + // entry.getKey(), + // Arrays.asList( + // now.get(0) - prev.get(0), + // now.get(1) - prev.get(1), + // // rate rate + // (now.get(0) - prev.get(0)) / diff, + // // recv rate + // (now.get(1) - prev.get(1)) / diff)); + // } + // } + // return result; + //} /** * load traffic according to os, now only support mac os and linux * * @return */ - private Map> loadTrafficData() { - String osName = System.getProperty("os.name").toLowerCase(); - if (osName.startsWith("linux")) { - return loadLinuxTrafficData(); - } else if (osName.startsWith("mac")) { - return loadMacOsTrafficData(); - } - return new HashMap<>(); - } + //private Map> loadTrafficData() { + // String osName = System.getProperty("os.name").toLowerCase(); + // if (osName.startsWith("linux")) { + // return loadLinuxTrafficData(); + // } else if (osName.startsWith("mac")) { + // return loadMacOsTrafficData(); + // } + // return new HashMap<>(); + //} /** * read the result of "netstat -ib". (lo is ignored) * * @return */ - private Map> loadMacOsTrafficData() { - Map> flows = new HashMap<>(); - Long current = System.currentTimeMillis() / 1000; - for (String line : executeCmd("netstat -ib")) { - if (line.startsWith("Name") || line.startsWith("lo")) { - // first table header line - continue; - } - - List arr = Arrays.stream(line.split(" ")).filter(x -> x.length() > 0) - .collect(Collectors.toList()); - - long sentBytes = Long.parseLong(arr.get(arr.size() - 2)); - long recvBytes = Long.parseLong(arr.get(arr.size() - 5)); - String name = arr.get(0); - // log.debug("mac: {}, -> {},{},{}", line, sentBytes, recvBytes, name); - if (sentBytes > 0 && recvBytes > 0) { - flows.put(name, Arrays.asList(sentBytes, recvBytes, current)); - } - } - - return flows; - } + //private Map> loadMacOsTrafficData() { + // Map> flows = new HashMap<>(); + // Long current = System.currentTimeMillis() / 1000; + // for (String line : executeCmd("netstat -ib")) { + // if (line.startsWith("Name") || line.startsWith("lo")) { + // // first table header line + // continue; + // } + // + // List arr = Arrays.stream(line.split(" ")).filter(x -> x.length() > 0) + // .collect(Collectors.toList()); + // + // long sentBytes = Long.parseLong(arr.get(arr.size() - 2)); + // long recvBytes = Long.parseLong(arr.get(arr.size() - 5)); + // String name = arr.get(0); + // // log.debug("mac: {}, -> {},{},{}", line, sentBytes, recvBytes, name); + // if (sentBytes > 0 && recvBytes > 0) { + // flows.put(name, Arrays.asList(sentBytes, recvBytes, current)); + // } + // } + // + // return flows; + //} /** * read the statistics file for network interface @@ -345,82 +345,82 @@ private List getAllNetworkInterfaces() throws SocketException { return names; } - private Map getDiskIoData() { - String osName = System.getProperty("os.name").toLowerCase(); - if (osName.startsWith("linux")) { - return loadLinuxDiskIoData(); - } else if (osName.startsWith("mac")) { - return loadMacDiskIoData(); - } - return new HashMap<>(); - } + //private Map getDiskIoData() { + // String osName = System.getProperty("os.name").toLowerCase(); + // if (osName.startsWith("linux")) { + // return loadLinuxDiskIoData(); + // } else if (osName.startsWith("mac")) { + // return loadMacDiskIoData(); + // } + // return new HashMap<>(); + //} /** * get io data using iostat -d -x -k * * @return */ - private Map loadLinuxDiskIoData() { - Map result = new HashMap<>(); - boolean contentFlag = false; - for (String line : executeCmd("iostat -d -x -k")) { - // header - if (line.startsWith("Device")) { - contentFlag = true; - continue; - } - - if (contentFlag) { - List arr = - Arrays.stream(line.split(" ")).filter(x -> x.length() > 0) - .collect(Collectors.toList()); - try { - // util% - result.put("disk.io." + arr.get(0) + ".util", - Float.valueOf(arr.get(arr.size() - 1)) * 100); - // wait - result.put("disk.io." + arr.get(0) + ".wait", - Float.valueOf(arr.get(arr.size() - 5)) * 100); - } catch (Exception e) { - log.debug("error get disk io data {}", line); - } - } - } - return result; - } + //private Map loadLinuxDiskIoData() { + // Map result = new HashMap<>(); + // boolean contentFlag = false; + // for (String line : executeCmd("iostat -d -x -k")) { + // // header + // if (line.startsWith("Device")) { + // contentFlag = true; + // continue; + // } + // + // if (contentFlag) { + // List arr = + // Arrays.stream(line.split(" ")).filter(x -> x.length() > 0).collect + // (Collectors.toList()); + // try { + // // util% + // result.put("disk.io." + arr.get(0) + ".util", Float.valueOf(arr.get(arr + // .size() - 1)) * 100); + // // wait + // result.put("disk.io." + arr.get(0) + ".wait", Float.valueOf(arr.get(arr + // .size() - 5)) * 100); + // } catch (Exception e) { + // log.debug("error get disk io data {}", line); + // } + // } + // } + // return result; + //} /** * get io data using iostat * * @return */ - private Map loadMacDiskIoData() { - - Map result = new HashMap<>(); - List lines = executeCmd("iostat -oK"); - // disks - List disks = - Arrays.stream(lines.get(0).split(" ")) - .filter(x -> x.length() > 0 && x.startsWith("disk")) - .collect(Collectors.toList()); - // datas - List data = - Arrays.stream(lines.get(2).split(" ")).filter(x -> x.length() > 0) - .collect(Collectors.toList()); - // zip data - for (int i = 0; i < disks.size(); i++) { - try { - // msps - result.put("disk.io." + disks.get(i) + ".wait", - Float.valueOf(data.get(i * 3 + 2)) * 100); - // no such value - result.put("disk.io." + disks.get(i) + ".util", 0.0F); - } catch (Exception e) { - log.debug("error get io data {}", data.get(i)); - } - } - return result; - } + //private Map loadMacDiskIoData() { + // + // Map result = new HashMap<>(); + // List lines = executeCmd("iostat -oK"); + // // disks + // List disks = + // Arrays.stream(lines.get(0).split(" ")) + // .filter(x -> x.length() > 0 && x.startsWith("disk")) + // .collect(Collectors.toList()); + // // datas + // List data = + // Arrays.stream(lines.get(2).split(" ")).filter(x -> x.length() > 0).collect + // (Collectors.toList()); + // // zip data + // for (int i = 0; i < disks.size(); i++) { + // try { + // // msps + // result.put("disk.io." + disks.get(i) + ".wait", Float.valueOf(data.get(i * 3 + + // 2)) * 100); + // // no such value + // result.put("disk.io." + disks.get(i) + ".util", 0.0F); + // } catch (Exception e) { + // log.debug("error get io data {}", data.get(i)); + // } + // } + // return result; + //} /** * execute cmd and get the output @@ -428,21 +428,21 @@ private Map loadMacDiskIoData() { * @param cmd * @return */ - private List executeCmd(String cmd) { - List result = new ArrayList<>(); - try { - Process pr = Runtime.getRuntime().exec(cmd); - BufferedReader in = new BufferedReader(new InputStreamReader(pr.getInputStream())); - String line; - while ((line = in.readLine()) != null) { - if (line.length() > 0) { - result.add(line); - } - } - pr.waitFor(); - in.close(); - } catch (IOException | InterruptedException e) { - } - return result; - } + //private List executeCmd(String cmd) { + // List result = new ArrayList<>(); + // try { + // Process pr = Runtime.getRuntime().exec(cmd); + // BufferedReader in = new BufferedReader(new InputStreamReader(pr.getInputStream())); + // String line; + // while ((line = in.readLine()) != null) { + // if (line.length() > 0) { + // result.add(line); + // } + // } + // pr.waitFor(); + // in.close(); + // } catch (IOException | InterruptedException e) { + // } + // return result; + //} } diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/options/HgStoreEngineOptions.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/options/HgStoreEngineOptions.java index c315d3440e..a76c760892 100644 --- a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/options/HgStoreEngineOptions.java +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/options/HgStoreEngineOptions.java @@ -19,7 +19,7 @@ import java.util.Map; -import org.apache.hugegraph.store.business.DataMover; +import org.apache.hugegraph.store.business.DataManager; import org.apache.hugegraph.store.pd.PdProvider; import org.apache.hugegraph.store.raft.RaftTaskHandler; @@ -33,6 +33,7 @@ @Data public class HgStoreEngineOptions { + public static final String PLACE_HOLDER_PREFIX = "placeholder"; public static String Raft_Path_Prefix = "raft"; public static String DB_Path_Prefix = "db"; public static String Snapshot_Path_Prefix = "snapshot"; @@ -43,11 +44,13 @@ public class HgStoreEngineOptions { // Waiting for leader timeout, in seconds private final int waitLeaderTimeout = 30; private final int raftRpcThreadPoolSize = Utils.cpus() * 6; + private int raftRpcThreadPoolSizeOfBasic = 256; // No PD mode, for development and debugging use only private boolean fakePD = false; // fakePd configuration items private FakePdOptions fakePdOptions = new FakePdOptions(); private RaftOptions raftOptions = new RaftOptions(); + private QueryPushDownOption queryPushDownOption = new QueryPushDownOption(); // pd server address private String pdAddress; // External service address @@ -64,9 +67,9 @@ public class HgStoreEngineOptions { private RaftTaskHandler taskHandler; private PdProvider pdProvider; - // Data Migration Service - private DataMover dataTransfer; + private DataManager dataTransfer; + private JobOptions jobConfig; @Data public static class FakePdOptions { @@ -120,6 +123,7 @@ public static class RaftOptions { // // Default: 3600 (1 hour) private int snapshotIntervalSecs = 3600; + private int snapshotDownloadingThreads = 4; // A snapshot saving would be triggered every |snapshot_interval_s| seconds, // and at this moment when state machine's lastAppliedIndex value // minus lastSnapshotId value is greater than snapshotLogIndexMargin value, @@ -149,4 +153,29 @@ public static class RaftOptions { private boolean useRocksDBSegmentLogStorage = true; private int maxSegmentFileSize = 64 * 1024 * 1024; } + + @Data + public static class QueryPushDownOption { + + /** + * thread pool size + */ + private int threadPoolSize; + /** + * the batch size that each request gets + */ + private int fetchBatchSize; + + private long fetchTimeout; + + /** + * the limit count of memory operations, like sort etc. + */ + private int memoryLimitCount; + + /** + * sst file size limit using for sort + */ + private int indexSizeLimitCount; + } } diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/options/JobOptions.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/options/JobOptions.java new file mode 100644 index 0000000000..d79d2ee2e4 --- /dev/null +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/options/JobOptions.java @@ -0,0 +1,33 @@ +/* + * 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.store.options; + +import lombok.Data; + +@Data +public class JobOptions { + + private int core; + private int max; + private int queueSize; + private int batchSize; + private int startTime; + private int uninterruptibleCore; + private int uninterruptibleMax; + private int uninterruptibleQueueSize; +} diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/pd/DefaultPdProvider.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/pd/DefaultPdProvider.java index 164b43a6c9..3d0734609e 100644 --- a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/pd/DefaultPdProvider.java +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/pd/DefaultPdProvider.java @@ -18,22 +18,22 @@ package org.apache.hugegraph.store.pd; import java.util.ArrayList; -import java.util.Collections; import java.util.List; import java.util.function.Consumer; import org.apache.hugegraph.pd.client.PDClient; import org.apache.hugegraph.pd.client.PDConfig; import org.apache.hugegraph.pd.client.PDPulse; -import org.apache.hugegraph.pd.client.PDPulseImpl; import org.apache.hugegraph.pd.common.KVPair; import org.apache.hugegraph.pd.common.PDException; import org.apache.hugegraph.pd.grpc.MetaTask; import org.apache.hugegraph.pd.grpc.Metapb; +import org.apache.hugegraph.pd.grpc.Metapb.PartitionStats; import org.apache.hugegraph.pd.grpc.pulse.PartitionHeartbeatRequest; import org.apache.hugegraph.pd.grpc.pulse.PartitionHeartbeatResponse; import org.apache.hugegraph.pd.grpc.pulse.PdInstructionType; import org.apache.hugegraph.pd.grpc.pulse.PulseResponse; +import org.apache.hugegraph.pd.grpc.watch.WatchChangeType; import org.apache.hugegraph.pd.grpc.watch.WatchGraphResponse; import org.apache.hugegraph.pd.grpc.watch.WatchResponse; import org.apache.hugegraph.pd.pulse.PulseServerNotice; @@ -43,8 +43,10 @@ import org.apache.hugegraph.store.meta.Graph; import org.apache.hugegraph.store.meta.GraphManager; import org.apache.hugegraph.store.meta.Partition; +import org.apache.hugegraph.store.meta.ShardGroup; import org.apache.hugegraph.store.meta.Store; import org.apache.hugegraph.store.metric.HgMetricService; +import org.apache.hugegraph.store.processor.Processors; import org.apache.hugegraph.store.util.Asserts; import org.apache.hugegraph.util.Log; import org.slf4j.Logger; @@ -61,7 +63,12 @@ public class DefaultPdProvider implements PdProvider { private Consumer hbOnError = null; private List partitionCommandListeners; private PDPulse.Notifier pdPulse; + private Processors processors; private GraphManager graphManager = null; + + public static String name = "store"; + public static String authority = "default"; + PDClient.PDEventListener listener = new PDClient.PDEventListener() { // Listening to pd change information listener @Override @@ -72,9 +79,9 @@ public void onStoreChanged(NodeEvent event) { HgStoreEngine.getInstance().rebuildRaftGroup(event.getNodeId()); } else if (event.getEventType() == NodeEvent.EventType.NODE_PD_LEADER_CHANGE) { log.info("pd leader changed!, {}. restart heart beat", event); - if (pulseClient.resetStub(event.getGraph(), pdPulse)) { - startHeartbeatStream(hbOnError); - } +// if (pulseClient.resetStub(event.getGraph(), pdPulse)) { +// startHeartbeatStream(hbOnError); +// } } } @@ -92,15 +99,31 @@ public void onGraphChanged(WatchResponse event) { } } + + @Override + public void onShardGroupChanged(WatchResponse event) { + var response = event.getShardGroupResponse(); + if (response.getType() == WatchChangeType.WATCH_CHANGE_TYPE_SPECIAL1) { + HgStoreEngine.getInstance().handleShardGroupOp(response.getShardGroupId(), + response.getShardGroup() + .getShardsList()); + } else if (response.getType() == WatchChangeType.WATCH_CHANGE_TYPE_ADD) { + var shardGroup = response.getShardGroup(); + HgStoreEngine.getInstance().createPartitionEngine(shardGroup.getId(), + ShardGroup.from(shardGroup), + null); + } + } }; public DefaultPdProvider(String pdAddress) { - this.pdClient = PDClient.create(PDConfig.of(pdAddress).setEnableCache(true)); + PDConfig config = PDConfig.of(pdAddress).setEnableCache(true); + config.setAuthority(name, authority); + this.pdClient = PDClient.create(config); this.pdClient.addEventListener(listener); this.pdServerAddress = pdAddress; - partitionCommandListeners = Collections.synchronizedList(new ArrayList()); log.info("pulse client connect to {}", pdClient.getLeaderIp()); - this.pulseClient = new PDPulseImpl(pdClient.getLeaderIp()); + this.pulseClient = this.pdClient.getPulse(); } @Override @@ -286,72 +309,29 @@ public void onNotice(PulseServerNotice response) { } PartitionHeartbeatResponse instruct = content.getPartitionHeartbeatResponse(); - LOG.debug("Partition heartbeat receive instruction: {}", instruct); - - Partition partition = new Partition(instruct.getPartition()); + processors.process(instruct, consumer); - for (PartitionInstructionListener event : partitionCommandListeners) { - if (instruct.hasChangeShard()) { - event.onChangeShard(instruct.getId(), partition, instruct - .getChangeShard(), - consumer); - } - if (instruct.hasSplitPartition()) { - event.onSplitPartition(instruct.getId(), partition, - instruct.getSplitPartition(), consumer); - } - if (instruct.hasTransferLeader()) { - event.onTransferLeader(instruct.getId(), partition, - instruct.getTransferLeader(), consumer); - } - if (instruct.hasDbCompaction()) { - event.onDbCompaction(instruct.getId(), partition, - instruct.getDbCompaction(), consumer); - } - - if (instruct.hasMovePartition()) { - event.onMovePartition(instruct.getId(), partition, - instruct.getMovePartition(), consumer); - } - - if (instruct.hasCleanPartition()) { - event.onCleanPartition(instruct.getId(), partition, - instruct.getCleanPartition(), - consumer); - } - - if (instruct.hasKeyRange()) { - event.onPartitionKeyRangeChanged(instruct.getId(), partition, - instruct.getKeyRange(), - consumer); - } - } } @Override public void onError(Throwable throwable) { - LOG.error("Partition heartbeat stream error. {}", throwable); - pulseClient.resetStub(pdClient.getLeaderIp(), pdPulse); - onError.accept(throwable); + LOG.error("Partition heartbeat stream error.", throwable); } @Override public void onCompleted() { LOG.info("Partition heartbeat stream complete"); + if (pulseClient.resetStub(pdClient.getLeaderIp(), pdPulse)) { + startHeartbeatStream(hbOnError); + } } }); return true; } - /** - * Add server-side message listening - * - * @param listener - * @return - */ @Override - public boolean addPartitionInstructionListener(PartitionInstructionListener listener) { - partitionCommandListeners.add(listener); + public boolean setCommandProcessors(Processors processors) { + this.processors = processors; return true; } @@ -365,6 +345,16 @@ public boolean partitionHeartbeat(List statsList) { return false; } + @Override + public boolean partitionHeartbeat(PartitionStats stats) { + PartitionHeartbeatRequest.Builder request = PartitionHeartbeatRequest.newBuilder() + .setStates(stats); + synchronized (pdPulse) { + pdPulse.notifyServer(request); + } + return false; + } + @Override public boolean isLocalPartition(long storeId, int partitionId) { try { @@ -425,6 +415,9 @@ public Metapb.ClusterStats storeHeartbeat(Store node) throws PDException { Metapb.StoreStats.Builder stats = HgMetricService.getInstance().getMetrics(); LOG.debug("storeHeartbeat StoreStats: {}", stats); stats.setCores(node.getCores()); + var executor = HgStoreEngine.getUninterruptibleJobs(); + stats.setExecutingTask( + executor.getActiveCount() != 0 || !executor.getQueue().isEmpty()); return pdClient.storeHeartbeat(stats.build()); } catch (PDException e) { @@ -465,8 +458,28 @@ public Metapb.ShardGroup getShardGroup(int partitionId) { return null; } + @Override + public Metapb.ShardGroup getShardGroupDirect(int partitionId) { + try { + return pdClient.getShardGroupDirect(partitionId); + } catch (PDException e) { + log.error("get shard group :{} from pd failed: {}", partitionId, e.getMessage()); + } + return null; + } + @Override public void updateShardGroup(Metapb.ShardGroup shardGroup) throws PDException { pdClient.updateShardGroup(shardGroup); } + + @Override + public String getPdServerAddress() { + return pdServerAddress; + } + + @Override + public void resetPulseClient() { + pulseClient.resetStub(pdClient.getLeaderIp(), pdPulse); + } } diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/pd/FakePdServiceProvider.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/pd/FakePdServiceProvider.java index 8c062b8e22..61d36febd2 100644 --- a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/pd/FakePdServiceProvider.java +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/pd/FakePdServiceProvider.java @@ -21,7 +21,6 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; import java.util.function.Consumer; import org.apache.hugegraph.pd.client.PDClient; @@ -29,11 +28,11 @@ import org.apache.hugegraph.pd.common.PartitionUtils; import org.apache.hugegraph.pd.grpc.MetaTask; import org.apache.hugegraph.pd.grpc.Metapb; -import org.apache.hugegraph.pd.grpc.Pdpb; import org.apache.hugegraph.store.meta.GraphManager; import org.apache.hugegraph.store.meta.Partition; import org.apache.hugegraph.store.meta.Store; import org.apache.hugegraph.store.options.HgStoreEngineOptions; +import org.apache.hugegraph.store.processor.Processors; import lombok.extern.slf4j.Slf4j; @@ -43,11 +42,15 @@ @Slf4j public class FakePdServiceProvider implements PdProvider { - private final Map stores; - private final int shardCount = 0; - private final Map partitions = new ConcurrentHashMap<>(); + private static long specifyStoreId = -1L; + private Map stores; private int partitionCount = 0; private GraphManager graphManager = null; + private List partitions; + /** + * 存储register的store + */ + private Store registerStore; public FakePdServiceProvider(HgStoreEngineOptions.FakePdOptions options) { stores = new LinkedHashMap<>(); @@ -64,21 +67,11 @@ public FakePdServiceProvider(HgStoreEngineOptions.FakePdOptions options) { } public static long makeStoreId(String storeAddress) { - return storeAddress.hashCode(); + return specifyStoreId != -1L ? specifyStoreId : storeAddress.hashCode(); } - /** - * For unit test - * - * @return - */ - public static Store getDefaultStore() { - Store store = new Store(); - store.setId(1); - store.setStoreAddress("127.0.0.1:8501"); - store.setRaftAddress("127.0.0.1:8511"); - store.setPartitionCount(1); - return store; + public static void setSpecifyStoreId(long specifyStoreId) { + FakePdServiceProvider.specifyStoreId = specifyStoreId; } private void addStore(String storeAddr, String raftAddr) { @@ -86,69 +79,84 @@ private void addStore(String storeAddr, String raftAddr) { setId(makeStoreId(storeAddr)); setRaftAddress(raftAddr); setStoreAddress(storeAddr); + setDeployPath(""); + setDataPath(""); }}; stores.put(store.getId(), store); } - public void addStore(Store store) { - stores.put(store.getId(), store); - } - @Override public long registerStore(Store store) throws PDException { log.info("registerStore storeId:{}, storeAddress:{}", store.getId(), store.getStoreAddress()); - // id does not match, login prohibited - if (store.getId() != 0 && store.getId() != makeStoreId(store.getStoreAddress())) { - throw new PDException(Pdpb.ErrorType.STORE_ID_NOT_EXIST_VALUE, - "Store id does not matched"); + var storeId = makeStoreId(store.getStoreAddress()); + if (store.getId() == 0) { + store.setId(storeId); } - if (!stores.containsKey(makeStoreId(store.getStoreAddress()))) { - store.setId(makeStoreId(store.getStoreAddress())); + if (!stores.containsKey(store.getId())) { stores.put(store.getId(), store); } - Store s = stores.get(makeStoreId(store.getStoreAddress())); - store.setId(s.getId()); + + registerStore = store; return store.getId(); } @Override - public Partition getPartitionByID(String graph, int partId) { - List storeList = new ArrayList(stores.values()); - int shardCount = this.shardCount; - if (shardCount == 0 || shardCount >= stores.size()) { - shardCount = stores.size(); + public Metapb.ShardGroup getShardGroup(int partitionId) { + Long storeId; + if (registerStore != null) { + storeId = registerStore.getId(); + } else { + storeId = (Long) stores.keySet().toArray()[0]; } - int storeIdx = partId % storeList.size(); - List shards = new ArrayList<>(); - for (int i = 0; i < shardCount; i++) { - Metapb.Shard shard = - Metapb.Shard.newBuilder().setStoreId(storeList.get(storeIdx).getId()) - .setRole(i == 0 ? Metapb.ShardRole.Leader : - Metapb.ShardRole.Follower) // + return Metapb.ShardGroup.newBuilder() + .setId(partitionId) + .setConfVer(0) + .setVersion(0) + .addAllShards(List.of(Metapb.Shard.newBuilder() + .setRole(Metapb.ShardRole.Leader) + .setStoreId(storeId).build())) + .setState(Metapb.PartitionState.PState_Normal) .build(); - shards.add(shard); - storeIdx = (storeIdx + 1) >= storeList.size() ? 0 : ++storeIdx; // Sequential selection - } + } + + @Override + public Metapb.ShardGroup getShardGroupDirect(int partitionId) { + return getShardGroup(partitionId); + } + + @Override + public void updateShardGroup(Metapb.ShardGroup shardGroup) throws PDException { + PdProvider.super.updateShardGroup(shardGroup); + } + /** + * 获取指定图表的分区信息,根据分区ID获取分区对象。 + * + * @param graph 图表名称 + * @param partId 分区ID + * @return 指定图表的分区对象 + */ + @Override + public Partition getPartitionByID(String graph, int partId) { int partLength = getPartitionLength(); Metapb.Partition partition = Metapb.Partition.newBuilder() .setGraphName(graph) .setId(partId) .setStartKey(partLength * partId) .setEndKey(partLength * (partId + 1)) - //.addAllShards(shards) + .setState(Metapb.PartitionState.PState_Normal) .build(); return new Partition(partition); } @Override public Metapb.Shard getPartitionLeader(String graph, int partId) { - return null; + return getShardGroup(partId).getShardsList().get(0); } private int getPartitionLength() { @@ -193,15 +201,25 @@ public boolean startHeartbeatStream(Consumer onError) { } @Override - public boolean addPartitionInstructionListener(PartitionInstructionListener listener) { - return false; + public boolean setCommandProcessors(Processors processors) { + return true; } + //@Override + //public boolean addPartitionInstructionListener(PartitionInstructionListener listener) { + // return false; + //} + @Override public boolean partitionHeartbeat(List statsList) { return true; } + @Override + public boolean partitionHeartbeat(Metapb.PartitionStats stats) { + return false; + } + @Override public boolean isLocalPartition(long storeId, int partitionId) { return true; @@ -210,7 +228,8 @@ public boolean isLocalPartition(long storeId, int partitionId) { @Override public Metapb.Graph getGraph(String graphName) { return Metapb.Graph.newBuilder().setGraphName(graphName) - //.setId(PartitionUtils.calcHashcode(graphName.getBytes())) + .setPartitionCount(partitionCount) + .setState(Metapb.PartitionState.PState_Normal) .build(); } @@ -261,4 +280,22 @@ public void setGraphManager(GraphManager graphManager) { public void deleteShardGroup(int groupId) { } + + public List getStores() { + return List.copyOf(stores.values()); + } + + public void setPartitionCount(int partitionCount) { + this.partitionCount = partitionCount; + } + + @Override + public String getPdServerAddress() { + return null; + } + + @Override + public void resetPulseClient() { + PdProvider.super.resetPulseClient(); + } } diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/pd/PartitionInstructionListener.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/pd/PartitionInstructionListener.java index 641495fed7..50e1c08e4d 100644 --- a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/pd/PartitionInstructionListener.java +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/pd/PartitionInstructionListener.java @@ -28,6 +28,7 @@ import org.apache.hugegraph.pd.grpc.pulse.TransferLeader; import org.apache.hugegraph.store.meta.Partition; +@Deprecated public interface PartitionInstructionListener { void onChangeShard(long taskId, Partition partition, ChangeShard changeShard, diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/pd/PdProvider.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/pd/PdProvider.java index 794c7e4187..7d028965c4 100644 --- a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/pd/PdProvider.java +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/pd/PdProvider.java @@ -24,9 +24,11 @@ import org.apache.hugegraph.pd.common.PDException; import org.apache.hugegraph.pd.grpc.MetaTask; import org.apache.hugegraph.pd.grpc.Metapb; +import org.apache.hugegraph.pd.grpc.Metapb.PartitionStats; import org.apache.hugegraph.store.meta.GraphManager; import org.apache.hugegraph.store.meta.Partition; import org.apache.hugegraph.store.meta.Store; +import org.apache.hugegraph.store.processor.Processors; import org.apache.hugegraph.store.util.HgStoreException; public interface PdProvider { @@ -57,9 +59,11 @@ public interface PdProvider { boolean startHeartbeatStream(Consumer onError); - boolean addPartitionInstructionListener(PartitionInstructionListener listener); + boolean setCommandProcessors(Processors processors); - boolean partitionHeartbeat(List statsList); + boolean partitionHeartbeat(List statsList); + + boolean partitionHeartbeat(PartitionStats stats); boolean isLocalPartition(long storeId, int partitionId); @@ -86,7 +90,15 @@ default Metapb.ShardGroup getShardGroup(int partitionId) { return null; } + default Metapb.ShardGroup getShardGroupDirect(int partitionId) { + return null; + } + default void updateShardGroup(Metapb.ShardGroup shardGroup) throws PDException { } + String getPdServerAddress(); + + default void resetPulseClient() { + } } diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/processor/BuildIndexProcessor.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/processor/BuildIndexProcessor.java new file mode 100644 index 0000000000..3682b89488 --- /dev/null +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/processor/BuildIndexProcessor.java @@ -0,0 +1,74 @@ +/* + * 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.store.processor; + +import java.util.function.Consumer; + +import org.apache.hugegraph.pd.grpc.MetaTask; +import org.apache.hugegraph.pd.grpc.Metapb; +import org.apache.hugegraph.pd.grpc.pulse.PartitionHeartbeatResponse; +import org.apache.hugegraph.store.HgStoreEngine; +import org.apache.hugegraph.store.PartitionEngine; +import org.apache.hugegraph.store.meta.Partition; + +import com.google.protobuf.GeneratedMessageV3; + +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class BuildIndexProcessor extends CommandProcessor { + + public BuildIndexProcessor(HgStoreEngine storeEngine) { + super(storeEngine); + } + + @Override + public void process(long taskId, Partition partition, GeneratedMessageV3 data, + Consumer raftCompleteCallback) { + if (preCheckTaskId(taskId, partition.getId())) { + return; + } + PartitionEngine engine = storeEngine.getPartitionEngine(partition.getId()); + if (engine != null) { + Metapb.BuildIndex param = (Metapb.BuildIndex) data; + MetaTask.Task task = MetaTask.Task.newBuilder() + .setId(param.getTaskId()) + .setPartition(partition.getProtoObj()) + .setType(MetaTask.TaskType.Build_Index) + .setState(MetaTask.TaskState.Task_Ready) + .setBuildIndex(param) + .build(); + log.info("receive build index task: {}, graph: {}, partition id:{}", + taskId, partition.getGraphName(), partition.getId()); + engine.buildIndex(task); + } + } + + @Override + public boolean isRaftTask() { + return false; + } + + @Override + public GeneratedMessageV3 getTaskMeta(PartitionHeartbeatResponse instruct) { + if (instruct.hasBuildIndex()) { + return instruct.getBuildIndex(); + } + return null; + } +} diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/processor/ChangeShardProcessor.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/processor/ChangeShardProcessor.java new file mode 100644 index 0000000000..8b176d4842 --- /dev/null +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/processor/ChangeShardProcessor.java @@ -0,0 +1,77 @@ +/* + * 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.store.processor; + +import java.util.function.Consumer; + +import org.apache.hugegraph.pd.grpc.MetaTask; +import org.apache.hugegraph.pd.grpc.pulse.ChangeShard; +import org.apache.hugegraph.pd.grpc.pulse.PartitionHeartbeatResponse; +import org.apache.hugegraph.store.HgStoreEngine; +import org.apache.hugegraph.store.PartitionEngine; +import org.apache.hugegraph.store.meta.Partition; + +import com.google.protobuf.GeneratedMessageV3; + +import lombok.extern.slf4j.Slf4j; + +/** + * @author zhangyingjie + * @date 2023/10/10 + **/ +@Slf4j +public class ChangeShardProcessor extends CommandProcessor { + + public ChangeShardProcessor(HgStoreEngine storeEngine) { + super(storeEngine); + } + + @Override + public void process(long taskId, Partition partition, GeneratedMessageV3 data, + Consumer raftCompleteCallback) { + PartitionEngine engine = storeEngine.getPartitionEngine(partition.getId()); + + if (engine != null) { + log.info("Partition {}-{} receive change shard message, {}", partition.getGraphName(), + partition.getId(), data); + String graphName = partition.getGraphName(); + int partitionId = partition.getId(); + MetaTask.Task task = MetaTask.Task.newBuilder() + .setId(taskId) + .setPartition(partition.getProtoObj()) + .setType(MetaTask.TaskType.Change_Shard) + .setState(MetaTask.TaskState.Task_Ready) + .setChangeShard((ChangeShard) data) + .build(); + + engine.doChangeShard(task, status -> { + log.info("Partition {}-{} change shard complete, status is {}", + graphName, partitionId, status); + raftCompleteCallback.accept(0); + }); + } + } + + @Override + public GeneratedMessageV3 getTaskMeta(PartitionHeartbeatResponse instruct) { + if (instruct.hasChangeShard()) { + return instruct.getChangeShard(); + } + return null; + } +} diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/processor/CleanPartitionProcessor.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/processor/CleanPartitionProcessor.java new file mode 100644 index 0000000000..37b6f7c74c --- /dev/null +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/processor/CleanPartitionProcessor.java @@ -0,0 +1,79 @@ +/* + * 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.store.processor; + +import java.util.function.Consumer; + +import org.apache.hugegraph.pd.grpc.pulse.CleanPartition; +import org.apache.hugegraph.pd.grpc.pulse.PartitionHeartbeatResponse; +import org.apache.hugegraph.store.HgStoreEngine; +import org.apache.hugegraph.store.PartitionEngine; +import org.apache.hugegraph.store.cmd.request.CleanDataRequest; +import org.apache.hugegraph.store.meta.Partition; +import org.apache.hugegraph.store.raft.RaftOperation; + +import com.google.protobuf.GeneratedMessageV3; + +import lombok.extern.slf4j.Slf4j; + +/** + * @author zhangyingjie + * @date 2023/10/10 + **/ +@Slf4j +public class CleanPartitionProcessor extends CommandProcessor { + + public CleanPartitionProcessor(HgStoreEngine storeEngine) { + super(storeEngine); + } + + @Override + public void process(long taskId, Partition partition, GeneratedMessageV3 data, + Consumer raftCompleteCallback) { + if (preCheckTaskId(taskId, partition.getId())) { + return; + } + PartitionEngine engine = storeEngine.getPartitionEngine(partition.getId()); + if (engine != null) { + CleanPartition cleanPartition = (CleanPartition) data; + CleanDataRequest request = + CleanDataRequest.fromCleanPartitionTask(cleanPartition, partition, taskId); + sendRaftTask(partition.getGraphName(), partition.getId(), RaftOperation.IN_CLEAN_OP, + request, + status -> { + log.info("onCleanPartition {}-{}, cleanType: {}, range:{}-{}, " + + "status:{}", + partition.getGraphName(), + partition.getId(), + cleanPartition.getCleanType(), + cleanPartition.getKeyStart(), + cleanPartition.getKeyEnd(), + status); + raftCompleteCallback.accept(0); + }); + } + } + + @Override + public GeneratedMessageV3 getTaskMeta(PartitionHeartbeatResponse instruct) { + if (instruct.hasCleanPartition()) { + return instruct.getCleanPartition(); + } + return null; + } +} diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/processor/CommandProcessor.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/processor/CommandProcessor.java new file mode 100644 index 0000000000..f1dba5c146 --- /dev/null +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/processor/CommandProcessor.java @@ -0,0 +1,238 @@ +/* + * 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.store.processor; + +import java.util.Map; +import java.util.concurrent.BlockingDeque; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.LinkedBlockingDeque; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.Consumer; + +import org.apache.hugegraph.pd.grpc.pulse.PartitionHeartbeatResponse; +import org.apache.hugegraph.store.HgStoreEngine; +import org.apache.hugegraph.store.cmd.request.RedirectRaftTaskRequest; +import org.apache.hugegraph.store.meta.MetadataKeyHelper; +import org.apache.hugegraph.store.meta.Partition; +import org.apache.hugegraph.store.raft.RaftClosure; +import org.apache.hugegraph.store.raft.RaftOperation; + +import com.alipay.sofa.jraft.Status; +import com.alipay.sofa.jraft.util.OnlyForTest; +import com.google.protobuf.GeneratedMessageV3; + +import lombok.extern.slf4j.Slf4j; + +/** + * @author zhangyingjie + * @date 2023/10/10 + **/ +@Slf4j +public abstract class CommandProcessor { + + /** + * 按照分区,将改分区的指令排队 + */ + private static final Map> TASKS = new ConcurrentHashMap<>(); + /** + * 分区任务的执行状态(是否在执行) + */ + private static final Map TASK_STATS = new ConcurrentHashMap<>(); + protected static ExecutorService threadPool = HgStoreEngine.getUninterruptibleJobs(); + protected HgStoreEngine storeEngine; + + public CommandProcessor(HgStoreEngine storeEngine) { + this.storeEngine = storeEngine; + } + + /** + * 是否有指令正在执行 + * + * @return true if there is a task running, otherwise false + */ + public static boolean isRunning() { + return TASK_STATS.entrySet().stream().anyMatch(p -> p.getValue().get()); + } + + /** + * 是否还有指令在排队 + * + * @return true if there are tasks waiting to be executed, otherwise false + */ + @OnlyForTest + public static boolean isEmpty() { + return TASKS.entrySet().stream().allMatch(p -> p.getValue().isEmpty()); + } + + /** + * using for test + * + * @throws InterruptedException + */ + @OnlyForTest + public static void waitingToFinished() throws InterruptedException { + while (!isEmpty() || isRunning()) { + Thread.sleep(1000); + } + } + + public abstract void process(long taskId, Partition partition, GeneratedMessageV3 data, + Consumer raftCompleteCallback); + + /** + * 是否有改process要处理的任务数据 + * + * @param instruct pd instruction + * @return task metadata if the processor should handle, otherwise null + */ + protected abstract GeneratedMessageV3 getTaskMeta(PartitionHeartbeatResponse instruct); + + /** + * 是否要通过线程池执行(要在分区内阻塞) + * + * @return true if execute in thread pool, false otherwise + */ + protected boolean executeInBlockingMode() { + return true; + } + + /** + * 是否是一个raft的任务 + * + * @return true if the task need to distributed to other followers + */ + protected boolean isRaftTask() { + return true; + } + + /** + * is the task exists + * + * @param taskId task id + * @param partId partition id + * @return true if exists, false otherwise + */ + protected boolean preCheckTaskId(long taskId, int partId) { + if (storeEngine.getPartitionEngine(partId) == null) { + return false; + } + byte[] key = MetadataKeyHelper.getInstructionIdKey(taskId); + var wrapper = storeEngine.getPartitionManager().getWrapper(); + byte[] value = wrapper.get(partId, key); + if (value != null) { + return true; + } + wrapper.put(partId, key, new byte[0]); + return false; + } + + /** + * 如果是leader,直接添加发送raft task, 否则redirect to leader + * + * @param partId partition id + * @param raftOp raft operation + * @param data data + * @param closure raft closure + */ + protected void sendRaftTask(String graph, Integer partId, byte raftOp, Object data, + RaftClosure closure) { + + var partitionEngine = storeEngine.getPartitionEngine(partId); + + if (partitionEngine != null) { + if (partitionEngine.isLeader()) { + partitionEngine.addRaftTask(RaftOperation.create(raftOp, data), closure); + } else { + var request = new RedirectRaftTaskRequest(graph, partId, raftOp, data); + var response = storeEngine.getHgCmdClient().redirectRaftTask(request); + closure.run(response.getStatus().isOK() ? Status.OK() : + new Status(response.getStatus().getCode(), + response.getStatus().getMsg())); + } + } + } + + /** + * 1. check if the processor should execute the instruction + * 2. check if the task should be submitted to thread pool + * 3. run in thread pool + * 3.1: check whether where is a task in same partition executing + * 3.2: process the instruction according to whether the task is raft task + * + * @param instruct pd instruction + */ + public void executeInstruct(PartitionHeartbeatResponse instruct) { + var meta = getTaskMeta(instruct); + if (meta == null) { + return; + } + + var partition = new Partition(instruct.getPartition()); + if (!executeInBlockingMode()) { + process(instruct.getId(), partition, meta, null); + } else { + // need to submit thread pool + // checking prev execution state + var partitionId = partition.getId(); + if (!TASKS.containsKey(partitionId)) { + synchronized (this) { + if (!TASKS.containsKey(partitionId)) { + TASKS.put(partitionId, new LinkedBlockingDeque<>()); + TASK_STATS.put(partitionId, new AtomicBoolean(false)); + } + } + } + + TASKS.get(partitionId).add(() -> { + while (!TASK_STATS.get(partitionId).compareAndSet(false, true)) { + try { + Thread.sleep(10); + } catch (InterruptedException e) { + log.warn("interrupted: {}", e.getMessage()); + } + } + + if (isRaftTask()) { + var consumerWrapper = new Consumer() { + @Override + public void accept(Integer integer) { + TASK_STATS.get(partitionId).set(false); + runNextTask(partitionId); + } + }; + process(instruct.getId(), partition, meta, consumerWrapper); + } else { + process(instruct.getId(), partition, meta, null); + TASK_STATS.get(partitionId).set(false); + runNextTask(partitionId); + } + }); + runNextTask(partitionId); + } + } + + private void runNextTask(int partitionId) { + if (!TASK_STATS.get(partitionId).get()) { + var task = TASKS.get(partitionId).poll(); + if (task != null) { + threadPool.submit(task); + } + } + } +} diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/processor/DbCompactionProcessor.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/processor/DbCompactionProcessor.java new file mode 100644 index 0000000000..868aa448f3 --- /dev/null +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/processor/DbCompactionProcessor.java @@ -0,0 +1,74 @@ +/* + * 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.store.processor; + +import java.util.function.Consumer; + +import org.apache.hugegraph.pd.grpc.pulse.DbCompaction; +import org.apache.hugegraph.pd.grpc.pulse.PartitionHeartbeatResponse; +import org.apache.hugegraph.store.HgStoreEngine; +import org.apache.hugegraph.store.PartitionEngine; +import org.apache.hugegraph.store.cmd.request.DbCompactionRequest; +import org.apache.hugegraph.store.meta.Partition; +import org.apache.hugegraph.store.raft.RaftOperation; + +import com.google.protobuf.GeneratedMessageV3; + +import lombok.extern.slf4j.Slf4j; + +/** + * @author zhangyingjie + * @date 2023/10/10 + **/ +@Slf4j +public class DbCompactionProcessor extends CommandProcessor { + + public DbCompactionProcessor(HgStoreEngine storeEngine) { + super(storeEngine); + } + + @Override + public void process(long taskId, Partition partition, GeneratedMessageV3 data, + Consumer raftCompleteCallback) { + PartitionEngine engine = storeEngine.getPartitionEngine(partition.getId()); + if (engine != null) { + DbCompaction dbCompaction = (DbCompaction) data; + DbCompactionRequest dbCompactionRequest = new DbCompactionRequest(); + dbCompactionRequest.setPartitionId(partition.getId()); + dbCompactionRequest.setTableName(dbCompaction.getTableName()); + dbCompactionRequest.setGraphName(partition.getGraphName()); + + sendRaftTask(partition.getGraphName(), partition.getId(), RaftOperation.DB_COMPACTION, + dbCompactionRequest, + status -> { + log.info("onRocksdbCompaction {}-{} sync partition status is {}", + partition.getGraphName(), partition.getId(), status); + raftCompleteCallback.accept(0); + } + ); + } + } + + @Override + public GeneratedMessageV3 getTaskMeta(PartitionHeartbeatResponse instruct) { + if (instruct.hasDbCompaction()) { + return instruct.getDbCompaction(); + } + return null; + } +} diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/processor/MovePartitionProcessor.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/processor/MovePartitionProcessor.java new file mode 100644 index 0000000000..48a10a9e85 --- /dev/null +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/processor/MovePartitionProcessor.java @@ -0,0 +1,85 @@ +/* + * 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.store.processor; + +import com.google.protobuf.GeneratedMessageV3; + +import lombok.extern.slf4j.Slf4j; + +import org.apache.hugegraph.pd.grpc.MetaTask; +import org.apache.hugegraph.pd.grpc.pulse.MovePartition; +import org.apache.hugegraph.pd.grpc.pulse.PartitionHeartbeatResponse; +import org.apache.hugegraph.store.HgStoreEngine; +import org.apache.hugegraph.store.PartitionEngine; +import org.apache.hugegraph.store.meta.Partition; + +import java.util.function.Consumer; + +/** + * @author zhangyingjie + * @date 2023/10/10 + **/ +@Slf4j +public class MovePartitionProcessor extends CommandProcessor { + + public MovePartitionProcessor(HgStoreEngine storeEngine) { + super(storeEngine); + } + + @Override + public void process(long taskId, Partition partition, GeneratedMessageV3 data, + Consumer consumer) { + if (preCheckTaskId(taskId, partition.getId())) { + return; + } + + PartitionEngine engine = storeEngine.getPartitionEngine(partition.getId()); + if (engine != null) { + // 先应答,避免超时造成pd重复发送 + MovePartition movePartition = (MovePartition) data; + String graphName = partition.getGraphName(); + int partitionId = partition.getId(); + MetaTask.Task task = MetaTask.Task.newBuilder() + .setId(taskId) + .setPartition(partition.getProtoObj()) + .setType(MetaTask.TaskType.Move_Partition) + .setState(MetaTask.TaskState.Task_Ready) + .setMovePartition(movePartition) + .build(); + try { + engine.moveData(task); + } catch (Exception e) { + log.error("Partition {}-{} onMovePartition exception {}", graphName, partitionId, + e); + } + } + } + + @Override + public boolean isRaftTask() { + return false; + } + + @Override + public GeneratedMessageV3 getTaskMeta(PartitionHeartbeatResponse instruct) { + if (instruct.hasMovePartition()) { + return instruct.getMovePartition(); + } + return null; + } +} diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/processor/PartitionRangeChangeProcessor.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/processor/PartitionRangeChangeProcessor.java new file mode 100644 index 0000000000..c3b898861e --- /dev/null +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/processor/PartitionRangeChangeProcessor.java @@ -0,0 +1,104 @@ +/* + * 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.store.processor; + +import java.util.List; +import java.util.function.Consumer; + +import org.apache.hugegraph.pd.common.PDException; +import org.apache.hugegraph.pd.grpc.Metapb; +import org.apache.hugegraph.pd.grpc.pulse.PartitionHeartbeatResponse; +import org.apache.hugegraph.pd.grpc.pulse.PartitionKeyRange; +import org.apache.hugegraph.store.HgStoreEngine; +import org.apache.hugegraph.store.PartitionEngine; +import org.apache.hugegraph.store.meta.Partition; +import org.apache.hugegraph.store.raft.RaftOperation; + +import com.google.protobuf.GeneratedMessageV3; + +import lombok.extern.slf4j.Slf4j; + +/** + * @author zhangyingjie + * @date 2023/10/10 + **/ +@Slf4j +public class PartitionRangeChangeProcessor extends CommandProcessor { + + public PartitionRangeChangeProcessor(HgStoreEngine storeEngine) { + super(storeEngine); + } + + @Override + public void process(long taskId, Partition partition, GeneratedMessageV3 data, + Consumer raftCompleteCallback) { + PartitionEngine engine = storeEngine.getPartitionEngine(partition.getId()); + if (engine != null) { + PartitionKeyRange partitionKeyRange = (PartitionKeyRange) data; + var partitionManager = storeEngine.getPartitionManager(); + var localPartition = + partitionManager.getPartition(partition.getGraphName(), partition.getId()); + + if (localPartition == null) { + // 如果分区数据为空,本地不会存储 + localPartition = partitionManager.getPartitionFromPD(partition.getGraphName(), + partition.getId()); + log.info("onPartitionKeyRangeChanged, get from pd:{}-{} -> {}", + partition.getGraphName(), partition.getId(), localPartition); + if (localPartition == null) { + return; + } + } + + var newPartition = localPartition.getProtoObj().toBuilder() + .setStartKey(partitionKeyRange.getKeyStart()) + .setEndKey(partitionKeyRange.getKeyEnd()) + .setState(Metapb.PartitionState.PState_Normal) + .build(); + partitionManager.updatePartition(newPartition, true); + + try { + sendRaftTask(newPartition.getGraphName(), newPartition.getId(), + RaftOperation.SYNC_PARTITION, newPartition, + status -> { + log.info( + "onPartitionKeyRangeChanged, {}-{},key range: {}-{} " + + "status{}", + newPartition.getGraphName(), + newPartition.getId(), + partitionKeyRange.getKeyStart(), + partitionKeyRange.getKeyEnd(), + status); + raftCompleteCallback.accept(0); + }); + log.info("onPartitionKeyRangeChanged: {}, update to pd", newPartition); + partitionManager.updatePartitionToPD(List.of(newPartition)); + } catch (PDException e) { + throw new RuntimeException(e); + } + } + } + + @Override + public GeneratedMessageV3 getTaskMeta(PartitionHeartbeatResponse instruct) { + if (instruct.hasKeyRange()) { + return instruct.getKeyRange(); + } + return null; + } +} diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/processor/Processors.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/processor/Processors.java new file mode 100644 index 0000000000..0d7cc994a4 --- /dev/null +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/processor/Processors.java @@ -0,0 +1,79 @@ +/* + * 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.store.processor; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Consumer; + +import org.apache.hugegraph.pd.grpc.pulse.PartitionHeartbeatResponse; +import org.apache.hugegraph.store.HgStoreEngine; +import org.apache.hugegraph.store.PartitionEngine; + +import lombok.extern.slf4j.Slf4j; + +/** + * @author zhangyingjie + * @date 2023/10/10 + **/ +@Slf4j +public class Processors { + + private final HgStoreEngine engine; + private Map processors = new ConcurrentHashMap<>(16); + + public Processors(HgStoreEngine engine) { + register(new BuildIndexProcessor(engine)); + register(new ChangeShardProcessor(engine)); + register(new CleanPartitionProcessor(engine)); + register(new DbCompactionProcessor(engine)); + register(new MovePartitionProcessor(engine)); + register(new PartitionRangeChangeProcessor(engine)); + register(new SplitPartitionProcessor(engine)); + register(new TransferLeaderProcessor(engine)); + + this.engine = engine; + } + + public void register(CommandProcessor processor) { + processors.put(processor.getClass(), processor); + } + + public CommandProcessor get(Class clazz) { + return processors.get(clazz); + } + + public void process(PartitionHeartbeatResponse instruct, + Consumer consumer) { + int partitionId = instruct.getPartition().getId(); + PartitionEngine engine = this.engine.getPartitionEngine(partitionId); + if (engine == null || !engine.isLeader()) { + return; + } + + consumer.accept(0); + + for (var entry : this.processors.entrySet()) { + try { + entry.getValue().executeInstruct(instruct); + } catch (Exception e) { + log.error("execute instruct {} error: ", instruct, e); + } + } + } +} diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/processor/SplitPartitionProcessor.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/processor/SplitPartitionProcessor.java new file mode 100644 index 0000000000..553a31af93 --- /dev/null +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/processor/SplitPartitionProcessor.java @@ -0,0 +1,86 @@ +/* + * 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.store.processor; + +import java.util.function.Consumer; + +import org.apache.hugegraph.pd.grpc.MetaTask; +import org.apache.hugegraph.pd.grpc.pulse.PartitionHeartbeatResponse; +import org.apache.hugegraph.pd.grpc.pulse.SplitPartition; +import org.apache.hugegraph.store.HgStoreEngine; +import org.apache.hugegraph.store.PartitionEngine; +import org.apache.hugegraph.store.meta.Partition; + +import com.google.protobuf.GeneratedMessageV3; + +import lombok.extern.slf4j.Slf4j; + +/** + * @author zhangyingjie + * @date 2023/10/10 + **/ +@Slf4j +public class SplitPartitionProcessor extends CommandProcessor { + + public SplitPartitionProcessor(HgStoreEngine storeEngine) { + super(storeEngine); + } + + @Override + public void process(long taskId, Partition partition, GeneratedMessageV3 data, + Consumer consumer) { + if (preCheckTaskId(taskId, partition.getId())) { + return; + } + PartitionEngine engine = storeEngine.getPartitionEngine(partition.getId()); + + if (engine != null) { + // 先应答,避免超时造成pd重复发送 + String graphName = partition.getGraphName(); + int partitionId = partition.getId(); + SplitPartition splitPartition = (SplitPartition) data; + MetaTask.Task task = MetaTask.Task.newBuilder() + .setId(taskId) + .setPartition(partition.getProtoObj()) + .setType(MetaTask.TaskType.Split_Partition) + .setState(MetaTask.TaskState.Task_Ready) + .setSplitPartition(splitPartition) + .build(); + try { + engine.moveData(task); + } catch (Exception e) { + String msg = + String.format("Partition %s-%s split with error", graphName, partitionId); + log.error(msg, e); + } + } + } + + @Override + public boolean isRaftTask() { + return false; + } + + @Override + public GeneratedMessageV3 getTaskMeta(PartitionHeartbeatResponse instruct) { + if (instruct.hasSplitPartition()) { + return instruct.getSplitPartition(); + } + return null; + } +} diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/processor/TransferLeaderProcessor.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/processor/TransferLeaderProcessor.java new file mode 100644 index 0000000000..87d70dbbfb --- /dev/null +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/processor/TransferLeaderProcessor.java @@ -0,0 +1,67 @@ +/* + * 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.store.processor; + +import java.util.function.Consumer; + +import org.apache.hugegraph.pd.grpc.pulse.PartitionHeartbeatResponse; +import org.apache.hugegraph.pd.grpc.pulse.TransferLeader; +import org.apache.hugegraph.store.HgStoreEngine; +import org.apache.hugegraph.store.PartitionEngine; +import org.apache.hugegraph.store.meta.Partition; + +import com.google.protobuf.GeneratedMessageV3; + +import lombok.extern.slf4j.Slf4j; + +/** + * @author zhangyingjie + * @date 2023/10/10 + **/ +@Slf4j +public class TransferLeaderProcessor extends CommandProcessor { + + public TransferLeaderProcessor(HgStoreEngine storeEngine) { + super(storeEngine); + } + + @Override + public void process(long taskId, Partition partition, GeneratedMessageV3 data, + Consumer consumer) { + PartitionEngine engine = storeEngine.getPartitionEngine(partition.getId()); + if (engine != null && engine.isLeader()) { + TransferLeader transferLeader = (TransferLeader) data; + log.info("Partition {}-{} receive TransferLeader instruction, new leader is {}" + , partition.getGraphName(), partition.getId(), transferLeader.getShard()); + engine.transferLeader(partition.getGraphName(), transferLeader.getShard()); + } + } + + @Override + public boolean executeInBlockingMode() { + return false; + } + + @Override + public GeneratedMessageV3 getTaskMeta(PartitionHeartbeatResponse instruct) { + if (instruct.hasTransferLeader()) { + return instruct.getTransferLeader(); + } + return null; + } +} diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/raft/DefaultRaftClosure.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/raft/DefaultRaftClosure.java new file mode 100644 index 0000000000..5c8b65772c --- /dev/null +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/raft/DefaultRaftClosure.java @@ -0,0 +1,52 @@ +/* + * 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.store.raft; + +import com.alipay.sofa.jraft.Status; + +/** + * @author zhangyingjie + * @date 2023/9/8 + **/ +public class DefaultRaftClosure implements RaftClosure { + + private RaftOperation operation; + private RaftClosure closure; + + public DefaultRaftClosure(RaftOperation op, RaftClosure closure) { + this.operation = op; + this.closure = closure; + } + + @Override + public void run(Status status) { + closure.run(status); + } + + public RaftClosure getClosure() { + return closure; + } + + public void clear() { + operation = null; + } + + public RaftOperation getOperation() { + return operation; + } +} diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/raft/PartitionStateMachine.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/raft/PartitionStateMachine.java new file mode 100644 index 0000000000..a7fb6462d7 --- /dev/null +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/raft/PartitionStateMachine.java @@ -0,0 +1,240 @@ +/* + * 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.store.raft; + +import java.util.Base64; +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +import org.apache.hugegraph.store.HgStoreEngine; +import org.apache.hugegraph.store.snapshot.SnapshotHandler; +import org.apache.hugegraph.store.util.HgStoreException; + +import com.alipay.sofa.jraft.Closure; +import com.alipay.sofa.jraft.Iterator; +import com.alipay.sofa.jraft.Status; +import com.alipay.sofa.jraft.conf.Configuration; +import com.alipay.sofa.jraft.core.StateMachineAdapter; +import com.alipay.sofa.jraft.entity.LeaderChangeContext; +import com.alipay.sofa.jraft.entity.RaftOutter; +import com.alipay.sofa.jraft.error.RaftError; +import com.alipay.sofa.jraft.error.RaftException; +import com.alipay.sofa.jraft.storage.snapshot.SnapshotReader; +import com.alipay.sofa.jraft.storage.snapshot.SnapshotWriter; +import com.alipay.sofa.jraft.util.Utils; + +import lombok.extern.slf4j.Slf4j; + +/** + * Raft 状态机 + * + * @author Yanjinbing + */ +@Slf4j +public class PartitionStateMachine extends StateMachineAdapter { + + private final AtomicLong leaderTerm = new AtomicLong(-1); + private final SnapshotHandler snapshotHandler; + private final Integer groupId; + private List taskHandlers; + private List stateListeners; + + private Lock lock = new ReentrantLock(); + private long committedIndex; + + public PartitionStateMachine(Integer groupId, SnapshotHandler snapshotHandler) { + this.groupId = groupId; + this.snapshotHandler = snapshotHandler; + this.stateListeners = new CopyOnWriteArrayList<>(); + this.taskHandlers = new CopyOnWriteArrayList<>(); + } + + public void addTaskHandler(RaftTaskHandler handler) { + taskHandlers.add(handler); + } + + public void addStateListener(RaftStateListener listener) { + stateListeners.add(listener); + } + + public boolean isLeader() { + return this.leaderTerm.get() > 0; + } + + @Override + public void onApply(Iterator iter) { + while (iter.hasNext()) { + final DefaultRaftClosure done = (DefaultRaftClosure) iter.done(); + try { + for (RaftTaskHandler handler : taskHandlers) { + if (done != null) { + // Leader分支,本地调用 + RaftOperation operation = done.getOperation(); + if (handler.invoke(groupId, operation.getOp(), operation.getReq(), + done.getClosure())) { + done.run(Status.OK()); + break; + } + } else { + if (handler.invoke(groupId, iter.getData().array(), null)) { + break; + } + } + } + } catch (Throwable t) { + log.info("{}", Base64.getEncoder().encode(iter.getData().array())); + log.error(String.format("StateMachine %s meet critical error:", groupId), t); + if (done != null) { + log.error("StateMachine meet critical error: op = {} {}.", + done.getOperation().getOp(), + done.getOperation().getReq()); + } + } + committedIndex = iter.getIndex(); + stateListeners.forEach(listener -> listener.onDataCommitted(committedIndex)); + // 清理数据 + if (done != null) { + done.clear(); + } + // 遍历下一条 + iter.next(); + } + } + + public long getCommittedIndex() { + return committedIndex; + } + + public long getLeaderTerm() { + return leaderTerm.get(); + } + + @Override + public void onError(final RaftException e) { + log.error(String.format("Raft %s StateMachine on error {}", groupId), e); + Utils.runInThread(() -> { + stateListeners.forEach(listener -> listener.onError(e)); + }); + } + + @Override + public void onShutdown() { + super.onShutdown(); + } + + @Override + public void onLeaderStart(final long term) { + this.leaderTerm.set(term); + super.onLeaderStart(term); + Utils.runInThread(() -> stateListeners.forEach(l -> l.onLeaderStart(term))); + log.info("Raft {} becomes leader ", groupId); + } + + @Override + public void onLeaderStop(final Status status) { + Utils.runInThread(() -> stateListeners.forEach(l -> l.onLeaderStop(this.leaderTerm.get()))); + this.leaderTerm.set(-1); + super.onLeaderStop(status); + log.info("Raft {} lost leader ", groupId); + } + + @Override + public void onStartFollowing(final LeaderChangeContext ctx) { + super.onStartFollowing(ctx); + Utils.runInThread( + () -> stateListeners.forEach( + l -> l.onStartFollowing(ctx.getLeaderId(), ctx.getTerm()))); + log.info("Raft {} start following: {}.", groupId, ctx); + } + + @Override + public void onStopFollowing(final LeaderChangeContext ctx) { + super.onStopFollowing(ctx); + Utils.runInThread( + () -> stateListeners.forEach( + l -> l.onStopFollowing(ctx.getLeaderId(), ctx.getTerm()))); + if (!ctx.getStatus().getRaftError().equals(RaftError.ESHUTDOWN)) { + log.info("Raft {} stop following: {}.", groupId, ctx); + } + } + + @Override + public void onConfigurationCommitted(final Configuration conf) { + stateListeners.forEach(listener -> { + Utils.runInThread(() -> { + try { + listener.onConfigurationCommitted(conf); + } catch (Exception e) { + log.error("Raft {} onConfigurationCommitted {}", groupId, e); + } + }); + }); + log.info("Raft {} onConfigurationCommitted {}", groupId, conf); + } + + @Override + public void onSnapshotSave(final SnapshotWriter writer, final Closure done) { + HgStoreEngine.getUninterruptibleJobs().execute(() -> { + try { + lock.lock(); + snapshotHandler.onSnapshotSave(writer); + log.info("Raft {} onSnapshotSave success", groupId); + done.run(Status.OK()); + } catch (HgStoreException e) { + log.error(String.format("Raft %s onSnapshotSave failed. {}", groupId), e); + done.run(new Status(RaftError.EIO, e.toString())); + } finally { + lock.unlock(); + } + }); + } + + @Override + public boolean onSnapshotLoad(final SnapshotReader reader) { + try { + RaftOutter.SnapshotMeta meta = reader.load(); + if (meta != null) { + this.committedIndex = meta.getLastIncludedIndex(); + log.info("onSnapshotLoad committedIndex = {}", this.committedIndex); + } else { + log.error("onSnapshotLoad failed to get SnapshotMeta"); + return false; + } + } catch (Exception e) { + log.error("onSnapshotLoad failed to get SnapshotMeta.", e); + return false; + } + + if (isLeader()) { + log.warn("Leader is not supposed to load snapshot"); + return false; + } + try { + snapshotHandler.onSnapshotLoad(reader, this.committedIndex); + log.info("Raft {} onSnapshotLoad success", groupId); + return true; + } catch (HgStoreException e) { + log.error(String.format("Raft %s onSnapshotLoad failed. ", groupId), e); + return false; + } + } + +} diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/raft/RaftOperation.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/raft/RaftOperation.java index 9ed26b92cb..5001d9b332 100644 --- a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/raft/RaftOperation.java +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/raft/RaftOperation.java @@ -44,6 +44,9 @@ public class RaftOperation { public static final byte IN_CLEAN_OP = 0x65; public static final byte RAFT_UPDATE_PARTITION = 0x66; public static final byte DB_COMPACTION = 0x67; + public static final byte DO_SYNC_SNAPSHOT = 0x68; + public static final byte SYNC_BLANK_TASK = 0x69; + final static byte[] EMPTY_Bytes = new byte[0]; private static final Logger LOG = LoggerFactory.getLogger(RaftOperation.class); private byte[] values; // req serialized result, used for transmitting to other raft nodes diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/snapshot/HgSnapshotHandler.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/snapshot/HgSnapshotHandler.java index eb80b64b4f..96f10af42b 100644 --- a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/snapshot/HgSnapshotHandler.java +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/snapshot/HgSnapshotHandler.java @@ -41,6 +41,7 @@ import lombok.extern.slf4j.Slf4j; +@Deprecated @Slf4j public class HgSnapshotHandler { diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/snapshot/SnapshotHandler.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/snapshot/SnapshotHandler.java new file mode 100644 index 0000000000..31eaa43338 --- /dev/null +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/snapshot/SnapshotHandler.java @@ -0,0 +1,230 @@ +/* + * 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.store.snapshot; + +import java.io.File; +import java.io.IOException; +import java.io.RandomAccessFile; +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.zip.Checksum; + +import org.apache.commons.io.FileUtils; +import org.apache.hugegraph.pd.grpc.Metapb; +import org.apache.hugegraph.store.PartitionEngine; +import org.apache.hugegraph.store.business.BusinessHandler; +import org.apache.hugegraph.store.meta.Partition; +import org.apache.hugegraph.store.util.HgStoreException; + +import com.alipay.sofa.jraft.entity.LocalFileMetaOutter; +import com.alipay.sofa.jraft.storage.snapshot.Snapshot; +import com.alipay.sofa.jraft.storage.snapshot.SnapshotReader; +import com.alipay.sofa.jraft.storage.snapshot.SnapshotWriter; +import com.alipay.sofa.jraft.util.CRC64; + +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class SnapshotHandler { + + private static final String SHOULD_NOT_LOAD = "should_not_load"; + private static final String SNAPSHOT_DATA_PATH = "data"; + + private final PartitionEngine partitionEngine; + private final BusinessHandler businessHandler; + + public SnapshotHandler(PartitionEngine partitionEngine) { + this.partitionEngine = partitionEngine; + this.businessHandler = partitionEngine.getStoreEngine().getBusinessHandler(); + } + + public static String trimStartPath(String str, String prefix) { + if (!prefix.endsWith(File.separator)) { + prefix = prefix + File.separator; + } + if (str.startsWith(prefix)) { + return (str.substring(prefix.length())); + } + return str; + } + + public static void findFileList(File dir, File rootDir, List files) { + if (!dir.exists() || !dir.isDirectory()) { + return; + } + File[] fs = dir.listFiles(); + if (fs != null) { + for (File f : fs) { + if (f.isFile()) { + files.add(trimStartPath(dir.getPath(), rootDir.getPath()) + File.separator + + f.getName()); + } else { + findFileList(f, rootDir, files); + } + } + } + } + + public Map getPartitions() { + return partitionEngine.getPartitions(); + } + + /** + * create rocksdb checkpoint + */ + public void onSnapshotSave(final SnapshotWriter writer) throws HgStoreException { + final String snapshotDir = writer.getPath(); + if (partitionEngine != null) { + Integer groupId = partitionEngine.getGroupId(); + AtomicInteger state = businessHandler.getState(groupId); + if (state != null && state.get() == BusinessHandler.doing) { + return; + } + // rocks db snapshot + final String graphSnapshotDir = snapshotDir + File.separator + SNAPSHOT_DATA_PATH; + businessHandler.saveSnapshot(graphSnapshotDir, "", groupId); + + List files = new ArrayList<>(); + File dir = new File(graphSnapshotDir); + File rootDirFile = new File(writer.getPath()); + // add all files in data dir + findFileList(dir, rootDirFile, files); + + // load snapshot by learner ?? + for (String file : files) { + String checksum = calculateChecksum(writer.getPath() + File.separator + file); + if (checksum.length() != 0) { + LocalFileMetaOutter.LocalFileMeta meta = + LocalFileMetaOutter.LocalFileMeta.newBuilder() + .setChecksum(checksum) + .build(); + writer.addFile(file, meta); + } else { + writer.addFile(file); + } + } + // should_not_load wound not sync to learner + markShouldNotLoad(writer, true); + } + } + + private String calculateChecksum(String path) { + // only calculate .sst and .log(wal file) file + final String emptyString = ""; + if (path.endsWith(".sst") || path.endsWith(".log")) { + final int maxFullCheckLength = 8192; + final int checkLength = 4096; + try { + File file = new File(path); + long length = file.length(); + Checksum checksum = new CRC64(); + try (final RandomAccessFile raf = new RandomAccessFile(file, "r")) { + byte[] buf = new byte[checkLength]; + if (length <= maxFullCheckLength) { + int totalReadLen = 0; + while (totalReadLen < length) { + int readLen = raf.read(buf); + checksum.update(buf, 0, readLen); + totalReadLen += readLen; + } + } else { + // head + int readLen = raf.read(buf); + checksum.update(buf, 0, readLen); + // tail + raf.seek(length - checkLength); + readLen = raf.read(buf); + checksum.update(buf, 0, readLen); + } + } + // final checksum = crc checksum + file length + return Long.toHexString(checksum.getValue()) + "_" + Long.toHexString(length); + } catch (IOException e) { + log.error("Failed to calculateChecksum for file {}. {}", path, e); + return emptyString; + } + } else { + return emptyString; + } + } + + public void onSnapshotLoad(final SnapshotReader reader, long committedIndex) throws + HgStoreException { + final String snapshotDir = reader.getPath(); + + // 本地保存的快照没必要加载 + if (shouldNotLoad(reader)) { + log.info("skip to load snapshot because of should_not_load flag"); + return; + } + + // 直接使用 snapshot + final String graphSnapshotDir = snapshotDir + File.separator + SNAPSHOT_DATA_PATH; + log.info("Raft {} begin loadSnapshot, {}", partitionEngine.getGroupId(), graphSnapshotDir); + businessHandler.loadSnapshot(graphSnapshotDir, "", partitionEngine.getGroupId(), + committedIndex); + log.info("Raft {} end loadSnapshot.", partitionEngine.getGroupId()); + + for (Metapb.Partition snapPartition : partitionEngine.loadPartitionsFromLocalDb()) { + log.info("onSnapshotLoad loaded partition from local db. Partition: {}", snapPartition); + partitionEngine.loadPartitionFromSnapshot(new Partition(snapPartition)); + + Partition partition = partitionEngine.getPartition(snapPartition.getGraphName()); + if (partition == null) { + log.warn("skip to load snapshot for {}-{}, it is not belong to this node", + snapPartition.getGraphName(), snapPartition.getId()); + continue; + } + + var taskManager = partitionEngine.getTaskManager(); + // async tasks + for (var task : taskManager.scanAsyncTasks(partitionEngine.getGroupId(), + snapPartition.getGraphName())) { + task.handleTask(); + } + } + + // mark snapshot has been loaded + markShouldNotLoad(reader, false); + } + + private boolean shouldNotLoad(final Snapshot snapshot) { + String shouldNotLoadPath = getShouldNotLoadPath(snapshot); + return new File(shouldNotLoadPath).exists(); + } + + private void markShouldNotLoad(final Snapshot snapshot, boolean saveSnapshot) { + String shouldNotLoadPath = getShouldNotLoadPath(snapshot); + try { + FileUtils.writeStringToFile(new File(shouldNotLoadPath), + saveSnapshot ? "saved snapshot" : "loaded snapshot", + Charset.defaultCharset()); + } catch (IOException e) { + log.error("Failed to create snapshot should not load flag file {}. {}", + shouldNotLoadPath, e); + } + } + + private String getShouldNotLoadPath(final Snapshot snapshot) { + return snapshot.getPath() + File.separator + SHOULD_NOT_LOAD; + } + +} diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/util/CopyOnWriteCache.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/util/CopyOnWriteCache.java index 59dd7c2d82..c85b96bed6 100644 --- a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/util/CopyOnWriteCache.java +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/util/CopyOnWriteCache.java @@ -29,17 +29,6 @@ import org.jetbrains.annotations.NotNull; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.ConcurrentMap; -import java.util.concurrent.Executors; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.TimeUnit; -import org.jetbrains.annotations.NotNull; - public class CopyOnWriteCache implements ConcurrentMap { // Scheduled executor service for periodically clearing the cache. @@ -59,8 +48,8 @@ public CopyOnWriteCache(long effectiveTime) { // Create a single-threaded scheduled executor to manage cache clearing. scheduledExecutor = Executors.newScheduledThreadPool(1); // Schedule the clear task to run at fixed intervals defined by effectiveTime. - scheduledExecutor.scheduleWithFixedDelay( - this::clear, effectiveTime, effectiveTime, TimeUnit.MILLISECONDS); + scheduledExecutor.scheduleWithFixedDelay(this::clear, effectiveTime, effectiveTime, + TimeUnit.MILLISECONDS); } /** @@ -163,7 +152,8 @@ public synchronized void clear() { * * @param k The key with which the specified value is to be associated. * @param v The value to be associated with the specified key. - * @return the previous value associated with the key, or null if there was no mapping for the key. + * @return the previous value associated with the key, or null if there was no mapping for + * the key. */ @Override public synchronized V put(K k, V v) { @@ -189,7 +179,8 @@ public synchronized void putAll(@NotNull Map entries) * Removes the mapping for the specified key from this cache if present. * * @param key The key whose mapping is to be removed from the cache. - * @return the previous value associated with the key, or null if there was no mapping for the key. + * @return the previous value associated with the key, or null if there was no mapping for + * the key. */ @Override public synchronized V remove(Object key) { @@ -200,12 +191,14 @@ public synchronized V remove(Object key) { } /** - * If the specified key is not already associated with a value, associates it with the given value. + * If the specified key is not already associated with a value, associates it with the given + * value. * Otherwise, returns the current value associated with the key. * * @param k The key with which the specified value is to be associated. * @param v The value to be associated with the specified key. - * @return the previous value associated with the key, or null if there was no mapping for the key. + * @return the previous value associated with the key, or null if there was no mapping for + * the key. */ @Override public synchronized V putIfAbsent(K k, V v) { @@ -217,7 +210,8 @@ public synchronized V putIfAbsent(K k, V v) { } /** - * Removes the entry for the specified key only if it is currently mapped to the specified value. + * Removes the entry for the specified key only if it is currently mapped to the specified + * value. * * @param k The key whose mapping is to be removed. * @param v The value expected to be associated with the key. @@ -234,10 +228,11 @@ public synchronized boolean remove(Object k, Object v) { } /** - * Replaces the entry for the specified key only if it is currently mapped to the specified original value. + * Replaces the entry for the specified key only if it is currently mapped to the specified + * original value. * - * @param k The key whose mapping is to be replaced. - * @param original The expected value to be associated with the key. + * @param k The key whose mapping is to be replaced. + * @param original The expected value to be associated with the key. * @param replacement The value to be associated with the key if the original value is present. * @return true if the mapping was replaced; otherwise, false. */ @@ -256,7 +251,8 @@ public synchronized boolean replace(@NotNull K k, @NotNull V original, @NotNull * * @param k The key whose mapping is to be replaced. * @param v The new value to be associated with the key. - * @return the previous value associated with the key, or null if there was no mapping for the key. + * @return the previous value associated with the key, or null if there was no mapping for + * the key. */ @Override public synchronized V replace(K k, V v) { diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/util/HgStoreException.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/util/HgStoreException.java index b5cef3b353..9284361395 100644 --- a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/util/HgStoreException.java +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/util/HgStoreException.java @@ -19,24 +19,25 @@ public class HgStoreException extends RuntimeException { - public final static int EC_NOEXCEPT = 0; - public final static int EC_FAIL = 1000; - // The data format stored is not supported. - public final static int EC_DATAFMT_NOT_SUPPORTED = 1001; - public final static int EC_RKDB_CREATE_FAIL = 1201; - public final static int EC_RKDB_DOPUT_FAIL = 1202; - public final static int EC_RKDB_DODEL_FAIL = 1203; - public final static int EC_RDKDB_DOSINGLEDEL_FAIL = 1204; - public final static int EC_RKDB_DODELPREFIX_FAIL = 1205; - public final static int EC_RKDB_DODELRANGE_FAIL = 1206; - public final static int EC_RKDB_DOMERGE_FAIL = 1207; - public final static int EC_RKDB_DOGET_FAIL = 1208; - public final static int EC_RKDB_PD_FAIL = 1209; - public final static int EC_RKDB_TRUNCATE_FAIL = 1212; - public final static int EC_RKDB_EXPORT_SNAPSHOT_FAIL = 1214; - public final static int EC_RKDB_IMPORT_SNAPSHOT_FAIL = 1215; - public final static int EC_RKDB_TRANSFER_SNAPSHOT_FAIL = 1216; - public final static int EC_METRIC_FAIL = 1401; + public static final int EC_NOEXCEPT = 0; + public static final int EC_FAIL = 1000; + // data format not support + public static final int EC_DATAFMT_NOT_SUPPORTED = 1001; + public static final int EC_CLOSE = 1002; + public static final int EC_RKDB_CREATE_FAIL = 1201; + public static final int EC_RKDB_DOPUT_FAIL = 1202; + public static final int EC_RKDB_DODEL_FAIL = 1203; + public static final int EC_RDKDB_DOSINGLEDEL_FAIL = 1204; + public static final int EC_RKDB_DODELPREFIX_FAIL = 1205; + public static final int EC_RKDB_DODELRANGE_FAIL = 1206; + public static final int EC_RKDB_DOMERGE_FAIL = 1207; + public static final int EC_RKDB_DOGET_FAIL = 1208; + public static final int EC_RKDB_PD_FAIL = 1209; + public static final int EC_RKDB_TRUNCATE_FAIL = 1212; + public static final int EC_RKDB_EXPORT_SNAPSHOT_FAIL = 1214; + public static final int EC_RKDB_IMPORT_SNAPSHOT_FAIL = 1215; + public static final int EC_RKDB_TRANSFER_SNAPSHOT_FAIL = 1216; + public static final int EC_METRIC_FAIL = 1401; private static final long serialVersionUID = 5193624480997934335L; private final int code; diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/util/MultiKv.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/util/MultiKv.java new file mode 100644 index 0000000000..dd4628c0e3 --- /dev/null +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/util/MultiKv.java @@ -0,0 +1,71 @@ +/* + * 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.store.util; + +import java.io.Serializable; +import java.util.List; + +import lombok.Data; + +@Data +public class MultiKv implements Comparable, Serializable { + + private List keys; + private List values; + + private List compareIndex; + + public MultiKv(List keys, List values) { + this.keys = keys; + this.values = values; + } + + public static MultiKv of(List keys, List values) { + return new MultiKv(keys, values); + } + + @Override + public int compareTo(MultiKv o) { + if (keys == null && o == null) { + return 0; + } + if (keys == null) { + return -1; + } else if (o.keys == null) { + return 1; + } else { + int l1 = keys.size(); + int l2 = o.getKeys().size(); + for (int i = 0; i < Math.min(l1, l2); i++) { + if (keys.get(i) instanceof Comparable && o.getKeys().get(i) instanceof Comparable) { + var ret = ((Comparable) keys.get(i)).compareTo(o.getKeys().get(i)); + if (ret != 0) { + return ret; + } + } else { + return 1; + } + } + + if (l1 != l2) { + return l1 > l2 ? 1 : -1; + } + } + return 0; + } +} diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/util/PartitionMetaStoreWrapper.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/util/PartitionMetaStoreWrapper.java index 918664a077..6576011d13 100644 --- a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/util/PartitionMetaStoreWrapper.java +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/util/PartitionMetaStoreWrapper.java @@ -52,6 +52,10 @@ public List scan(int partitionId, com.google.protobuf.Parser parser, b return store.scan(parser, prefix); } + public void close(int partitionId) { + HgStoreEngine.getInstance().getBusinessHandler().getSession(partitionId).close(); + } + private static class InnerMetaStore extends MetaStoreBase { private int partitionId; diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/util/SortShuffle.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/util/SortShuffle.java new file mode 100644 index 0000000000..62a7e28016 --- /dev/null +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/util/SortShuffle.java @@ -0,0 +1,218 @@ +/* + * 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.store.util; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.Serializable; +import java.util.ArrayDeque; +import java.util.Comparator; +import java.util.Deque; +import java.util.Iterator; +import java.util.Queue; +import java.util.concurrent.ConcurrentLinkedDeque; + +import org.apache.hugegraph.store.business.itrv2.FileObjectIterator; +import org.apache.hugegraph.store.business.itrv2.io.SortShuffleSerializer; + +public class SortShuffle { + + private static final int BATCH_SIZE = 1000000; + + private static final int FILE_SIZE = 3; + + private static int fileSeq = 0; + + private static String basePath = "/tmp/"; + + private String path; + + private Queue queue = new ConcurrentLinkedDeque<>(); + + private Comparator comparator; + + private SortShuffleSerializer serializer; + + private Deque files = new ArrayDeque<>(); + + public SortShuffle(Comparator comparator, SortShuffleSerializer serializer) { + this.comparator = comparator; + path = basePath + Thread.currentThread().getId() + "-" + + System.currentTimeMillis() % 10000 + "/"; + new File(path).mkdirs(); + this.serializer = serializer; + } + + public static String getBasePath() { + return basePath; + } + + public static void setBasePath(String path) { + basePath = path; + } + + /** + * 将对象t追加到文件中。如果文件中的记录数已达到BATCH_SIZE,则将其写入文件并清空队列。 + * + * @param t 要追加的对象 + * @throws IOException 如果写入时出错 + */ + public void append(T t) throws IOException { + if (queue.size() >= BATCH_SIZE) { + synchronized (this) { + if (queue.size() >= BATCH_SIZE) { + writeToFile(); + queue.clear(); + } + } + } + queue.add(t); + } + + public void finish() throws IOException { + finalMerge(); + } + + /** + * 删除文件及其目录,并清空资源。 + */ + public void close() { + if (this.files.size() > 0) { + while (this.files.size() > 0) { + new File(files.pop()).delete(); + } + new File(path).delete(); + } + this.files.clear(); + this.queue.clear(); + } + + /** + * 将数据写入文件 + * + * @throws IOException 如果创建文件夹失败或写文件失败时抛出 + */ + private void writeToFile() throws IOException { + if (!new File(path).exists()) { + new File(path).mkdirs(); + } + + if (files.size() >= FILE_SIZE) { + minorMerge(files.pop(), files.pop()); + } + + var fn = getFileName(); + OutputStream fos = new FileOutputStream(fn); + queue.stream().sorted(this.comparator).forEach(t -> { + try { + serializer.write(fos, t); + } catch (IOException e) { + throw new RuntimeException(e); + } + }); + this.files.add(fn); + fos.close(); + } + + private synchronized String getFileName() { + fileSeq += 1; + return path + fileSeq; + } + + /** + * merge with file data when spill files exceed FILE_SIZE + */ + private void minorMerge(String f1, String f2) throws IOException { + String fn = getFileName(); + OutputStream fos = new FileOutputStream(fn); + + InputStream fis1 = new FileInputStream(f1); + InputStream fis2 = new FileInputStream(f2); + + T o1 = serializer.read(fis1); + T o2 = serializer.read(fis2); + + // read sorted fn1 and f2, write to new file + while (o1 != null && o2 != null) { + if (comparator.compare(o1, o2) < 0) { + serializer.write(fos, o1); + o1 = serializer.read(fis1); + } else { + serializer.write(fos, o2); + o2 = serializer.read(fis2); + } + } + + if (o1 != null) { + serializer.write(fos, o1); + while ((o1 = serializer.read(fis1)) != null) { + serializer.write(fos, o1); + } + } + + if (o2 != null) { + serializer.write(fos, o2); + while ((o2 = serializer.read(fis2)) != null) { + serializer.write(fos, o2); + } + } + + fis1.close(); + fis2.close(); + fos.close(); + + new File(f1).delete(); + new File(f2).delete(); + files.add(fn); + } + + /** + * merge all split files + */ + private void finalMerge() throws IOException { + + if (this.files.size() == 0) { + return; + } + + writeToFile(); + queue.clear(); + + while (this.files.size() > 1) { + minorMerge(this.files.pop(), this.files.pop()); + } + } + + /** + * read all sorted element + * + * @return iterator + */ + public Iterator getIterator() throws IOException { + if (files.size() == 0) { + return queue.iterator(); + } + + return new FileObjectIterator<>(files.getFirst(), this.serializer); + } + +} diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/util/ZipUtils.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/util/ZipUtils.java new file mode 100644 index 0000000000..331fd3c577 --- /dev/null +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/util/ZipUtils.java @@ -0,0 +1,93 @@ +/* + * 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.store.util; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.nio.file.Paths; +import java.util.zip.CheckedInputStream; +import java.util.zip.CheckedOutputStream; +import java.util.zip.Checksum; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; +import java.util.zip.ZipOutputStream; + +import org.apache.commons.io.FileUtils; +import org.apache.commons.io.IOUtils; +import org.apache.commons.io.output.NullOutputStream; + +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public final class ZipUtils { + + public static void compress(final String rootDir, final String sourceDir, + final String outputFile, final Checksum checksum) throws + IOException { + try (final FileOutputStream fos = new FileOutputStream(outputFile); + final CheckedOutputStream cos = new CheckedOutputStream(fos, checksum); + final ZipOutputStream zos = new ZipOutputStream(new BufferedOutputStream(cos))) { + ZipUtils.compressDirectoryToZipFile(rootDir, sourceDir, zos); + zos.flush(); + fos.getFD().sync(); + } + } + + private static void compressDirectoryToZipFile(final String rootDir, final String sourceDir, + final ZipOutputStream zos) throws IOException { + final String dir = Paths.get(rootDir, sourceDir).toString(); + final File[] files = new File(dir).listFiles(); + for (final File file : files) { + final String child = Paths.get(sourceDir, file.getName()).toString(); + if (file.isDirectory()) { + compressDirectoryToZipFile(rootDir, child, zos); + } else { + zos.putNextEntry(new ZipEntry(child)); + try (final FileInputStream fis = new FileInputStream(file); + final BufferedInputStream bis = new BufferedInputStream(fis)) { + IOUtils.copy(bis, zos); + } + } + } + } + + public static void decompress(final String sourceFile, final String outputDir, + final Checksum checksum) throws IOException { + try (final FileInputStream fis = new FileInputStream(sourceFile); + final CheckedInputStream cis = new CheckedInputStream(fis, checksum); + final ZipInputStream zis = new ZipInputStream(new BufferedInputStream(cis))) { + ZipEntry entry; + while ((entry = zis.getNextEntry()) != null) { + final String fileName = entry.getName(); + final File entryFile = new File(Paths.get(outputDir, fileName).toString()); + FileUtils.forceMkdir(entryFile.getParentFile()); + try (final FileOutputStream fos = new FileOutputStream(entryFile); + final BufferedOutputStream bos = new BufferedOutputStream(fos)) { + IOUtils.copy(zis, bos); + bos.flush(); + fos.getFD().sync(); + } + } + IOUtils.copy(cis, NullOutputStream.NULL_OUTPUT_STREAM); + } + } +} diff --git a/hugegraph-store/hg-store-rocksdb/src/main/java/org/apache/hugegraph/rocksdb/access/RocksDBSession.java b/hugegraph-store/hg-store-rocksdb/src/main/java/org/apache/hugegraph/rocksdb/access/RocksDBSession.java index a8827abfc0..7ce17f399b 100644 --- a/hugegraph-store/hg-store-rocksdb/src/main/java/org/apache/hugegraph/rocksdb/access/RocksDBSession.java +++ b/hugegraph-store/hg-store-rocksdb/src/main/java/org/apache/hugegraph/rocksdb/access/RocksDBSession.java @@ -20,6 +20,7 @@ import java.io.Closeable; import java.io.File; import java.io.IOException; +import java.io.Serializable; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Paths; @@ -89,7 +90,8 @@ public class RocksDBSession implements AutoCloseable, Cloneable { @Getter private Map iteratorMap; - public RocksDBSession(HugeConfig hugeConfig, String dbDataPath, String graphName, long version) { + public RocksDBSession(HugeConfig hugeConfig, String dbDataPath, String graphName, + long version) { this.hugeConfig = hugeConfig; this.graphName = graphName; this.cfHandleLock = new ReentrantReadWriteLock(); @@ -868,9 +870,10 @@ public long getApproximateDataSize(byte[] start, byte[] end) { /** * 根据表名获取 size + * * @param table table * @param start key start - * @param end key end + * @param end key end * @return size */ public long getApproximateDataSize(String table, byte[] start, byte[] end) { @@ -886,8 +889,9 @@ public long getApproximateDataSize(String table, byte[] start, byte[] end) { var h = this.tables.get(table); long[] sizes = - this.rocksDB.getApproximateSizes( - h, Arrays.asList(r1), SizeApproximationFlag.INCLUDE_FILES, SizeApproximationFlag.INCLUDE_MEMTABLES); + this.rocksDB.getApproximateSizes( + h, Arrays.asList(r1), SizeApproximationFlag.INCLUDE_FILES, + SizeApproximationFlag.INCLUDE_MEMTABLES); bytesSize += sizes[0]; kbSize += bytesSize / 1024; @@ -1043,7 +1047,7 @@ public ColumnFamilyHandle get() { * A wrapper for RocksIterator that convert RocksDB results to std Iterator */ - public static class BackendColumn implements Comparable { + public static class BackendColumn implements Comparable, Serializable { public byte[] name; public byte[] value; From b359131069f27c54db5d57e06e46b2fee76cc91e Mon Sep 17 00:00:00 2001 From: JisoLya <523420504@qq.com> Date: Sun, 3 Aug 2025 20:05:32 +0800 Subject: [PATCH 14/35] update: add todo tag --- .../java/org/apache/hugegraph/store/PartitionEngine.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/PartitionEngine.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/PartitionEngine.java index 808149fb22..6f6cdb2184 100644 --- a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/PartitionEngine.java +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/PartitionEngine.java @@ -119,10 +119,8 @@ public class PartitionEngine implements Lifecycle, RaftS private PartitionStateMachine stateMachine; @Getter private RaftGroupService raftGroupService; - @Getter private TaskManager taskManager; private SnapshotHandler snapshotHandler; - @Getter private Node raftNode; private volatile boolean started; @@ -1096,13 +1094,15 @@ public void doSnapshotSync(Closure done) { } log.info("Partition {},path:{} begin to doSnapshotSync", this.getGroupId(), lockPath); - raftNode.getRaftOptions().setTruncateLog(true); + //todo soya may have problem + //raftNode.getRaftOptions().setTruncateLog(true); CountDownLatch latch = new CountDownLatch(1); AtomicReference result = new AtomicReference<>(); raftNode.snapshot(status -> { result.set(status); try { - raftNode.getRaftOptions().setTruncateLog(false); + //todo soya may have problem + //raftNode.getRaftOptions().setTruncateLog(false); latch.countDown(); log.info("Partition {},path: {} doSnapshotSync result : {}. ", groupId, lockPath, status); From edec16d8a315ba6b7157d1c5340049b90bbf5eeb Mon Sep 17 00:00:00 2001 From: JisoLya <523420504@qq.com> Date: Wed, 13 Aug 2025 08:42:02 +0800 Subject: [PATCH 15/35] refactor: update part of node module --- .../node/controller/FixGraphIdController.java | 667 ++++++++++++++++++ .../controller/HgStoreMetricsController.java | 5 +- .../controller/HgStoreStatusController.java | 35 +- .../node/controller/HgTestController.java | 83 ++- .../store/node/controller/PartitionAPI.java | 14 + .../store/node/controller/RaftAPI.java | 63 ++ .../store/node/entry/PartitionRequest.java | 26 + .../store/node/grpc/BatchGrpcClosure.java | 34 +- .../store/node/grpc/GRpcServerConfig.java | 5 +- .../store/node/grpc/HgStoreNodeService.java | 29 +- .../store/node/grpc/HgStoreSessionImpl.java | 63 +- .../store/node/grpc/HgStoreStateService.java | 30 + .../store/node/grpc/HgStoreStreamImpl.java | 3 +- .../store/node/grpc/HgStoreWrapperEx.java | 7 + .../store/node/grpc/ScanBatchResponse.java | 2 - .../store/node/grpc/ScanBatchResponse3.java | 4 +- .../grpc/query/AggregativeQueryObserver.java | 398 +++++++++++ .../grpc/query/AggregativeQueryService.java | 145 ++++ .../node/grpc/query/MultiKeyComparator.java | 70 ++ .../store/node/grpc/query/QueryStage.java | 47 ++ .../store/node/grpc/query/QueryStages.java | 91 +++ .../store/node/grpc/query/QueryUtil.java | 374 ++++++++++ .../node/grpc/query/model/PipelineResult.java | 75 ++ .../grpc/query/model/PipelineResultType.java | 26 + .../node/grpc/query/model/QueryPlan.java | 130 ++++ .../node/grpc/query/stages/AggStage.java | 210 ++++++ .../query/stages/DeserializationStage.java | 74 ++ .../grpc/query/stages/EarlyStopException.java | 21 + .../query/stages/ExtractAggFieldStage.java | 101 +++ .../node/grpc/query/stages/FilterStage.java | 58 ++ .../node/grpc/query/stages/LimitStage.java | 52 ++ .../node/grpc/query/stages/OlapStage.java | 123 ++++ .../node/grpc/query/stages/OrderByStage.java | 146 ++++ .../grpc/query/stages/ProjectionStage.java | 76 ++ .../node/grpc/query/stages/SampleStage.java | 54 ++ .../grpc/query/stages/SimpleCountStage.java | 63 ++ .../node/grpc/query/stages/StopStage.java | 40 ++ .../node/grpc/query/stages/TopStage.java | 110 +++ .../node/grpc/query/stages/TtlCheckStage.java | 65 ++ 39 files changed, 3543 insertions(+), 76 deletions(-) create mode 100644 hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/controller/FixGraphIdController.java create mode 100644 hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/controller/RaftAPI.java create mode 100644 hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/entry/PartitionRequest.java create mode 100644 hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/query/AggregativeQueryObserver.java create mode 100644 hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/query/AggregativeQueryService.java create mode 100644 hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/query/MultiKeyComparator.java create mode 100644 hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/query/QueryStage.java create mode 100644 hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/query/QueryStages.java create mode 100644 hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/query/QueryUtil.java create mode 100644 hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/query/model/PipelineResult.java create mode 100644 hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/query/model/PipelineResultType.java create mode 100644 hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/query/model/QueryPlan.java create mode 100644 hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/query/stages/AggStage.java create mode 100644 hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/query/stages/DeserializationStage.java create mode 100644 hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/query/stages/EarlyStopException.java create mode 100644 hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/query/stages/ExtractAggFieldStage.java create mode 100644 hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/query/stages/FilterStage.java create mode 100644 hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/query/stages/LimitStage.java create mode 100644 hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/query/stages/OlapStage.java create mode 100644 hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/query/stages/OrderByStage.java create mode 100644 hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/query/stages/ProjectionStage.java create mode 100644 hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/query/stages/SampleStage.java create mode 100644 hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/query/stages/SimpleCountStage.java create mode 100644 hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/query/stages/StopStage.java create mode 100644 hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/query/stages/TopStage.java create mode 100644 hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/query/stages/TtlCheckStage.java diff --git a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/controller/FixGraphIdController.java b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/controller/FixGraphIdController.java new file mode 100644 index 0000000000..468fc606da --- /dev/null +++ b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/controller/FixGraphIdController.java @@ -0,0 +1,667 @@ +/* + * 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.store.node.controller; + +import static org.apache.hugegraph.rocksdb.access.SessionOperatorImpl.increaseOne; + +import java.io.IOException; +import java.util.AbstractMap; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Random; +import java.util.Set; +import java.util.stream.Collectors; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import org.apache.hugegraph.backend.BackendColumn; + +import org.apache.hugegraph.rocksdb.access.RocksDBSession; +import org.apache.hugegraph.rocksdb.access.ScanIterator; +import org.apache.hugegraph.rocksdb.access.SessionOperator; +import org.apache.hugegraph.serializer.BinaryElementSerializer; +import org.apache.hugegraph.store.business.BusinessHandlerImpl; +import org.apache.hugegraph.store.business.InnerKeyCreator; +import org.apache.hugegraph.store.meta.GraphIdManager; +import org.apache.hugegraph.store.meta.MetadataKeyHelper; +import org.apache.hugegraph.store.node.grpc.HgStoreNodeService; +import org.apache.hugegraph.store.node.grpc.query.QueryUtil; +import org.apache.hugegraph.structure.BaseElement; +import org.apache.hugegraph.structure.BaseProperty; +import com.google.protobuf.Int64Value; +import com.google.protobuf.InvalidProtocolBufferException; + +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@RestController +@RequestMapping(value = "/fix") +public class FixGraphIdController { + + private static final String GRAPH_ID_PREFIX = "@GRAPH_ID@"; + + @Autowired + private HgStoreNodeService nodeService; + + private final BinaryElementSerializer serializer = BinaryElementSerializer.getInstance(); + + private static List graphs = new ArrayList<>(); + static { + String graphNames = "acgopgs/acg_opg/g\n" + + "acgopgs/acg/g\n" + + "acgopgs/example/g\n" + + "acgopgs/test/g\n" + + "acgraggs/acg_20241014/g\n" + + "acgraggs/acg_20241024/g\n" + + "acgraggs/acg_20241112/g\n" + + "acgraggs/example/g\n" + + "acgraggs/test_1/g\n" + + "acgraggs/test_inf_v2/g\n" + + "acgraggs/test_songbowei/g\n" + + "acgraggs/test2/g\n" + + "acgraggs/test3/g\n" + + "acgraggs/test4/g\n" + + "acgraggs/yangshengpu_test/g\n" + + "aiqichags/corpmarket_graph/g\n" + + "aiqichags/online_1/g\n" + + "aiqichags/online_2/g\n" + + "aiqichags/sub_graph/g\n" + + "appbuildergs/ab_20241121/g\n" + + "appbuildergs/ab_20250103/g\n" + + "assetgs/assetgraph_all/g\n" + + "assetgs/assetgraph_backup/g\n" + + "assetgs/assetgraph/g\n" + + "assetgs/test_schema/g\n" + + "assetgs/test/g\n" + + "astrolabegs/example/g\n" + + "astrolabegs/gxp_load_test/g\n" + + "astrolabegs/indexpro_test\n" + + "astrolabegs/indexpro_test/g\n" + + "astrolabegs/load_test/g\n" + + "astrolabegs/my_rag_test/g\n" + + "astrolabegs/test/g\n" + + "b2bgs/b2b_test/g\n" + + "baikegs/lemma_test/g\n" + + "baikegs/test/g\n" + + "bdpanriskctrlgs/example/g\n" + + "bdpanriskctrlgs/porn_kids_v1_1/g\n" + + "bdpanriskctrlgs/porn_kids/g\n" + + "bdpanriskctrlgs/test/g\n" + + "bjh_project/bcp_chat_detail_2410/g\n" + + "bjh_project/bcp_chat_detail/g\n" + + "bjh_project/bcp_social_2410/g\n" + + "bjh_project/bcp_social/g\n" + + "bjh_project/bcp_test/g\n" + + "bjh_project/bjh_follow/g\n" + + "bjh_project/bjh_project_240828/g\n" + + "bjh_project/bjh_project_test/g\n" + + "bjh_project/bjh_project_ttl30/g\n" + + "bjh_project/bjh_project/g\n" + + "bjh_project/bjh_register/g\n" + + "bjh_project/bjh_relation/g\n" + + "bjh_project/bjh_social_relation/g\n" + + "bjh_project/bjh_social_ttl30/g\n" + + "bjh_project/bjh_social/g\n" + + "bjh_project/common_data/g\n" + + "comategs/comate_demo/g\n" + + "comategs/example/g\n" + + "DEFAULT/~sys_graph/g\n" + + "fanheichangs/benefit_g/g\n" + + "fanheichangs/benefit_graph/g\n" + + "fanheichangs/cuid2uid_graph/g\n" + + "fanheichangs/example/g\n" + + "fanheichangs/id_graph_10w/g\n" + + "fanheichangs/id_graph_1w/g\n" + + "fanheichangs/id_graph/g\n" + + "fanheichangs/site_graph/g\n" + + "fanzhags/example/g\n" + + "fanzhags/fengchao_id/g\n" + + "fengchaogs/afs_test/g\n" + + "fengchaogs/antiservice_gcn_min_test/g\n" + + "fengchaogs/antiservice_gcn_test/g\n" + + "fengchaogs/black_test/g\n" + + "fengchaogs/cvt_test/g\n" + + "fengkonggs/d_200003_z_i/g\n" + + "fengkonggs/d_200003_z_p/g\n" + + "fengkonggs/d_200003/g\n" + + "fengkonggs/d_200012_z_i/g\n" + + "fengkonggs/d_200012_z_p/g\n" + + "fengkonggs/d_200012/g\n" + + "fengkonggs/d_others_z_p/g\n" + + "fengkonggs/d_others/g\n" + + "fengkonggs/d_sense/g\n" + + "fengkonggs/dianshang_all/g\n" + + "fengkonggs/dianshang_b_2/g\n" + + "fengkonggs/dianshang_b/g\n" + + "fengkonggs/dianshang_group/g\n" + + "fengkonggs/dianshang_strong_weak/g\n" + + "fengkonggs/dianshang_weak/g\n" + + "fengkonggs/tmp/g\n" + + "fengkonggs/universal_graph_big/g\n" + + "fengkonggs/universal_graph_test/g\n" + + "fengkonggs/universal_graph/g\n" + + "fengkonggs/unsupervised_ip_graph/g\n" + + "graphcloudgs/automobile_knowledge_graph/g\n" + + "hcghealthgs/basedata/g\n" + + "ip_info/ip_space_time_analyse_new/g\n" + + "ip_info/ip_space_time_analyse_public/g\n" + + "ipipe/g1/g\n" + + "ipipe/g2/g\n" + + "ipipe/vermeer/g\n" + + "itplatformgs/eop_authz_prod/g\n" + + "itplatformgs/eop_authz_sandbox/g\n" + + "itplatformgs/example/g\n" + + "judicialgraphgs/graph/g\n" + + "judicialgraphgs/test/g\n" + + "judicialgraphgs/verdict/g\n" + + "mappoigs/map_cuid_poi_20231227/g\n" + + "mappoigs/mappoi_hz_0404_0406/g\n" + + "mappoigs/mappoi_test/g\n" + + "mappoigs/mappoi240318/g\n" + + "megqegs/code_case/g\n" + + "megqegs/example/g\n" + + "neizhianli/covid19/g\n" + + "neizhianli/hlm/g\n" + + "politicsllmgs/llm_test/g\n" + + "politicsllmgs/politics_llm/g\n" + + "searchcontentgs/example/g\n" + + "secaitestgs/atcp_test_1/g\n" + + "secaitestgs/atcp_test_2/g\n" + + "secaitestgs/atcp_test_3/g\n" + + "secaitestgs/hugegraph_server/g\n" + + "secaitestgs/hugegraph/g\n" + + "secaitestgs/rag_test/g\n" + + "secaitestgs/risk_sync/g\n" + + "secaitestgs/sec/g\n" + + "secaitestgs/test1/g\n" + + "secaitestgs/things_box/g\n" + + "secaitestgs/things_shield/g\n" + + "tieba_ronghe/social_high_percision/g\n" + + "tieba_ronghe/social_multi_feature/g\n" + + "tieba_ronghe/social_ttl_7/g\n" + + "tieba_ronghe/tieba_daily_cls_20240603/g\n" + + "tieba_ronghe/tieba_daily_cls_recover/g\n" + + "tieba_ronghe/tieba_daily_cls_test/g\n" + + "tieba_ronghe/tieba_daily_cls/g\n" + + "tieba_ronghe/tieba_data_0228_0304/g\n" + + "tieba_ronghe/tieba_data_0308_0314/g\n" + + "tieba_ronghe/tieba_data_24_0123_trim/g\n" + + "tieba_ronghe/tieba_data_24_0123/g\n" + + "tieba_ronghe/tieba_data_machine_learning/g\n" + + "tieba_ronghe/tieba_data_ttl_31/g\n" + + "tieba_ronghe/tieba_data_ttl_7/g\n" + + "tieba_ronghe/tieba_qunliao_0626_0701/g\n" + + "tieba_ronghe/tieba_qunliao_0701/g\n" + + "tieba_ronghe/tieba_qunliao_test/g\n" + + "tieba_ronghe/tieba_test/g\n" + + "tieba_ronghe/tieba_yucui/g\n" + + "tieba/tieba_data_fanheichan_1/g\n" + + "tieba/tieba_data_fanheichan_tag/g\n" + + "tieba/tieba_data_haotianjing/g\n" + + "tieba/tieba_data_new/g\n" + + "tieba/tieba_data/g\n" + + "trafficllmgs/accident_report_test/g\n" + + "trafficllmgs/example/g\n" + + "trafficllmgs/llm_rag/g\n" + + "trafficllmgs/nnx_traffic_event/g\n" + + "trafficllmgs/traffic_graph_test/g\n" + + "trafficllmgs/traffic_rag/g\n" + + "upopgs/tu_id/g\n" + + "wenkuraggs/demo_241112/g\n" + + "wenkuraggs/test_241107/g\n" + + "wenkuraggs/wenku_test_241112/g\n" + + "secaitestgs/asdfg32435r4rwewet/g"; + + graphs.addAll(List.of(graphNames.split("\n"))); + } + + @GetMapping(value = "/update_next_id/{partition_id}/{graph_id}", produces = "application/json") + public String updateMaxGraphId(@PathVariable(value = "partition_id") int pid, @PathVariable( + "graph_id") long graphId) throws IOException { + var businessHandler = nodeService.getStoreEngine().getBusinessHandler(); + try(var manager = new GraphIdManager(businessHandler, pid)){ + var key = MetadataKeyHelper.getCidKey(GRAPH_ID_PREFIX); + log.info("update max graph id to {}, partition, {}", graphId, pid); + manager.put(key, Int64Value.of(graphId)); + manager.flush(); + } + return "OK"; + } + + @GetMapping(value = "/next_id/{partition_id}", produces = "application/json") + public String getNextId(@PathVariable(value = "partition_id") int pid) throws IOException { + var handler = (BusinessHandlerImpl)nodeService.getStoreEngine().getBusinessHandler(); + var op = handler.getSession(pid).sessionOp(); + var next = op.get(GraphIdManager.DEFAULT_CF_NAME, + MetadataKeyHelper.getCidKey(GRAPH_ID_PREFIX)); + if (next != null) { + return String.valueOf(Int64Value.parseFrom(next).getValue()); + } + return "NOT_FOUND"; + } + + @PostMapping(value = "/update_graph_id/{partition_id}", produces = "application/json") + public String updateGraphId(@PathVariable(value = "partition_id") int pid, + @RequestBody Map idMap) throws IOException { + var handler = (BusinessHandlerImpl)nodeService.getStoreEngine().getBusinessHandler(); + try(var manager = new GraphIdManager(handler, pid)) { + idMap.forEach((graphName, graphId) -> { + log.info("update graph id of {} to {}, partition, {}", graphName, graphId, pid); + var graphIdKey = MetadataKeyHelper.getGraphIDKey(graphName); + var slotKey = manager.genCIDSlotKey(GRAPH_ID_PREFIX, graphId); + var value = Int64Value.of(graphId); + manager.put(graphIdKey, value); + manager.put(slotKey, value); + }); + manager.flush(); + } + handler.getKeyCreator().clearCache(pid); + return "OK"; + } + + public static byte[] getShortBytes(int x) { + byte[] buf = new byte[2]; + buf[0] = (byte) (x >> 8); + buf[1] = (byte) (x); + return buf; + } + + /** + * 统计整个表中 graph id 对应对 count 以及随机抽样 100 条 (精确的数字) + * @param op op + * @param table table + * @return count map and sample map + */ + + private Map.Entry, Map>> + scanAndSample(SessionOperator op, String table) { + Map countMap = new HashMap<>(); + Map> sampleMap = new HashMap<>(); + Random random = new Random(); + + try (var iterator = op.scan(table)) { + while (iterator.hasNext()){ + var col = (RocksDBSession.BackendColumn)iterator.next(); + if (col.name.length > 2){ + int id = (col.name[0] << 8) + (col.name[1]); + if (!countMap.containsKey(id)){ + countMap.put(id, 0); + sampleMap.put(id, new ArrayList<>()); + } + var count = countMap.put(id, countMap.get(id) + 1); + if (count == null) { + count = 0; + } + if (count < 100) { + sampleMap.get(id).add(col); + } else { + int k = random.nextInt(count + 1); + if (k < 100) { + sampleMap.get(id).set(k, col); + } + } + } + } + } + return new AbstractMap.SimpleEntry<> (countMap, sampleMap); + } + + private long getLabelId(RocksDBSession.BackendColumn col, String table) { + BackendColumn newCol = BackendColumn.of( + Arrays.copyOfRange(col.name, Short.BYTES, col.name.length - Short.BYTES), + col.value); + var id = serializer.parseLabelFromCol(newCol, Objects.equals("g+v", table)); + return id.asLong(); + } + + /** + * 效率优化,只查前 10 万条 + * @param op + * @param table + * @param start + * @param end + * @return + */ + private Map scanAndSample(SessionOperator op, String table, byte[] start, + byte[] end) { + Random random = new Random(); + + Set labels = new HashSet<>(); + try(var iterator = op.scan(table, start, end, ScanIterator.Trait.SCAN_LT_END)) { + int count = 0; + List sample = new ArrayList<>(); + while (iterator.hasNext()) { + var col = (RocksDBSession.BackendColumn) iterator.next(); + if (col.name.length > 2) { + if (count < 10000 || random.nextInt(100) == 1) { + labels.add(getLabelId(col, table)); + } + + if (count < 100) { + sample.add(col); + } else { + int k = random.nextInt(count + 1); + if (k < 100) { + sample.set(k, col); + } + } + count += 1; + } + } + return Map.of("count", count, "sample", sample, + "labels", labels.stream().map(String::valueOf) + .collect(Collectors.joining(","))); + + } + } + + /** + * 性能优化版,按照 graph id 去扫描,根据预估文件大小,决定是否要扫这个分区 + * @param session + * @return + */ + + private Map> scanAndSample(RocksDBSession session) { + Map> result = new HashMap<>(); + var op = session.sessionOp(); + for (int i = 0 ; i < 65536; i ++) { + var start = getShortBytes(i); + var end = getShortBytes(i + 1); + long size = session.getApproximateDataSize(start, end); + if (size > 0) { + var vMap = scanAndSample(op, "g+v", start, end); + var eMap = scanAndSample(op, "g+ie", start, end); + + if ((int)vMap.get("count") + (int)eMap.get("count") > 0) { + result.put(i, Map.of("vCount", vMap.get("count"), + "eCount", eMap.get("count"), + "size", size, + "vLabels", vMap.get("labels"), + "eLabels", eMap.get("labels"), + "vSample", vMap.get("sample"), + "eSample", eMap.get("sample"))); + } + } + } + return result; + } + + + private String elementToString(BaseElement element) { + if (element == null) { + return ""; + } + StringBuilder builder = new StringBuilder(); + for (var property: element.getProperties().entrySet()) { + BaseProperty value = property.getValue(); + var v = property.getValue().value(); + if (v instanceof String) { + builder.append(value.propertyKey().name()); + builder.append(":").append(v).append(","); + } + } + return builder.toString(); + } + + private String runDeserialize(List list, boolean isVertex){ + if (list == null || list.isEmpty()) { + return "empty"; + } + + int total = list.size(); + StringBuilder buffer = new StringBuilder(); + for (String graph: graphs) { + int success = 0; + BaseElement element = null; + for (var column : list) { + BackendColumn newCol = BackendColumn.of(Arrays.copyOfRange(column.name, Short.BYTES, + column.name.length - Short.BYTES), column.value); + try { + element = QueryUtil.parseEntry(BusinessHandlerImpl.getGraphSupplier(graph), + newCol, isVertex); + success++; + } catch (Exception e) { + } + } + if (success > total * 0.8) { + buffer.append(String.format("%s: %f, %s\n", graph, success*1.0/total, + element == null ? "FAIL" : element.toString())); + } + } + return buffer.toString(); + } + + /** + * 要同时满足能够解析定点和边 + * @param list1 vertex list + * @param list2 edge list + * @return + */ + + private Map runDeserialize(List list1, + List list2){ + int total1 = list1.size(); + int total2 = list2.size(); + List passed = new ArrayList<>(); + BaseElement element = null; + BaseElement element2 = null; + + for (String graph: graphs) { + int success = 0; + int success2 = 0; + for (var column : list1) { + BackendColumn newCol = BackendColumn.of(Arrays.copyOfRange(column.name, Short.BYTES, + column.name.length - Short.BYTES), column.value); + try { + element = QueryUtil.parseEntry(BusinessHandlerImpl.getGraphSupplier(graph), + newCol, true); + success++; + } catch (Exception e) { + } + } + if (success < total1 * 0.9) { + continue; + } + + for (var column : list2) { + BackendColumn newCol = BackendColumn.of(Arrays.copyOfRange(column.name, Short.BYTES, + column.name.length - Short.BYTES), column.value); + try { + element2 = QueryUtil.parseEntry(BusinessHandlerImpl.getGraphSupplier(graph), + newCol, false); + success2++; + } catch (Exception e) { + } + } + + if (success2 >= total2 * 0.9) { + passed.add(String.format("%s:%f", graph, + (success + success2) * 1.0 / (total1 + total2))); + } + } + + return Map.of("graphs", String.join("\n", passed), "samples", + String.join("\n",List.of(elementToString(element), + elementToString(element2)))); + } + + private Map getGraphIds(RocksDBSession session) { + Map graphs = new HashMap<>(); + var op = session.sessionOp(); + var prefix = MetadataKeyHelper.getGraphIDKey(""); + try (var iterator = op.scan(GraphIdManager.DEFAULT_CF_NAME, prefix)){ + while (iterator.hasNext()){ + var col = (RocksDBSession.BackendColumn)iterator.next(); + try { + int graphId = (int) Int64Value.parseFrom(col.value).getValue(); + String graphName = new String(col.name).replace("HUGEGRAPH/GRAPH_ID/", ""); + graphs.put(graphId, graphName); + } catch (InvalidProtocolBufferException e) { + } + } + } + return graphs; + } + + private Set getSlotIds(RocksDBSession session) { + Set result = new HashSet<>(); + var op = session.sessionOp(); + var prefix = MetadataKeyHelper.getCidSlotKeyPrefix(GRAPH_ID_PREFIX); + try (var iterator = op.scan(GraphIdManager.DEFAULT_CF_NAME, prefix)){ + while (iterator.hasNext()){ + var col = (RocksDBSession.BackendColumn)iterator.next(); + try { + int graphId = (int) Int64Value.parseFrom(col.value).getValue(); + result.add(graphId); + } catch (InvalidProtocolBufferException e) { + } + } + } + + return result; + } + + @GetMapping(value = "/graph_ids/{id}", produces = "application/json") + public Map> allGraphIds(@PathVariable(value = "id") int id) { + var session = nodeService.getStoreEngine().getBusinessHandler().getSession(id); + var graphs = getGraphIds(session); + var slotIds = getSlotIds(session); + Map> result = new HashMap<>(); + for (int i = 0 ; i < 65536; i ++) { + var start = getShortBytes(i); + var end = getShortBytes(i + 1); + long size = session.getApproximateDataSize(start, end); + long count = 0; + if (size > 0 && size < 512){ + count = session.sessionOp().keyCount(start, end, "g+v"); + if (count == 0) { + continue; + } + } + if (size > 0 || graphs.containsKey(i)) { + Map tmp = new HashMap<>(); + tmp.put("size", String.valueOf(size)); + tmp.put("graph", graphs.getOrDefault(i, "not found")); + if (count > 0) { + tmp.put("count", String.valueOf(count)); + } + if (slotIds.contains(i)) { + tmp.put("has_slot_id", "true"); + } + result.put(i, tmp); + } + } + return result; + } + + + @GetMapping(value = "/check/{id}", produces = "application/json") + public Map> checkGraphId(@PathVariable(value = "id") int id) { + var businessHandler = nodeService.getStoreEngine().getBusinessHandler(); + var session = businessHandler.getSession(id); + Map graphs = getGraphIds(session); + + var result = new HashMap>(); + var samples = scanAndSample(session); + + for (var entry : samples.entrySet()){ + var graphId = entry.getKey(); + var value = entry.getValue(); + + Map map = new HashMap<>(); + map.put("size", String.valueOf(value.get("size"))); + map.put("vertex count", String.valueOf(value.get("vCount"))); + map.put("in edge count", String.valueOf(value.get("eCount"))); + map.put("graph id", graphs.getOrDefault(graphId, "not found")); + map.put("vLabels", String.valueOf(value.get("vLabels"))); + map.put("eLabels", String.valueOf(value.get("eLabels"))); + + var list1 = (List) value.get("vSample"); + var list2 = (List) value.get("eSample"); + + var parseResult = runDeserialize(list1, list2); + map.put("graphs", parseResult.getOrDefault("graphs", "")); + map.put("samples", parseResult.getOrDefault("samples", "")); + result.put(graphId, map); + } + return result; + } + + @GetMapping(value = "/delete_graph_id/{partition}/{graph_id}", produces = "application/json") + public String deleteGraphId(@PathVariable(value = "partition") int pid, + @PathVariable("graph_id") int gid) { + byte[] start = getShortBytes(gid); + byte[] end = Arrays.copyOf(start, start.length); + increaseOne(end); + var businessHandler = nodeService.getStoreEngine().getBusinessHandler(); + + var op = businessHandler.getSession(pid).sessionOp(); + var tables = List.of("g+v", "g+ie", "g+oe", "g+index", "g+olap"); + for (var table : tables) { + op.deleteRange(table, start,end); + } + return "OK"; + } + + @GetMapping(value = "/clean/{graph:.+}", produces = "application/json") + public String cleanGraph(@PathVariable(value = "graph") String graph) { + var businessHandler = nodeService.getStoreEngine().getBusinessHandler(); + var tables = List.of("g+v", "g+ie", "g+oe"); + + InnerKeyCreator keyCreator = new InnerKeyCreator(businessHandler); + var supplier = BusinessHandlerImpl.getGraphSupplier(graph); + + var partitions = businessHandler.getPartitionIds(graph); + for (var pid : partitions) { + var session = businessHandler.getSession(pid); + var op = session.sessionOp(); + + for (String table : tables) { + boolean isVertex = QueryUtil.isVertex(table); + try (var itr = op.scan(table, keyCreator.getStartKey(pid, graph), + keyCreator.getEndKey(pid, graph), 0)) { + while (itr.hasNext()) { + var col = (RocksDBSession.BackendColumn) itr.next(); + BackendColumn newCol = BackendColumn.of( + Arrays.copyOfRange(col.name, Short.BYTES, + col.name.length - Short.BYTES), col.value); + try { + QueryUtil.parseEntry(supplier, newCol, isVertex); + } catch (Exception e) { + op.delete(table, col.name); + } + } + } + } + op.commit(); + } + + return "OK"; + } +} diff --git a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/controller/HgStoreMetricsController.java b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/controller/HgStoreMetricsController.java index a7aea39b1d..a07d838868 100644 --- a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/controller/HgStoreMetricsController.java +++ b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/controller/HgStoreMetricsController.java @@ -38,11 +38,12 @@ @RequestMapping(value = "/metrics", method = RequestMethod.GET) public class HgStoreMetricsController { - private final SystemMetrics systemMetrics = new SystemMetrics(); - private final DriveMetrics driveMetrics = new DriveMetrics(); @Autowired HgStoreNodeService nodeService; + private final SystemMetrics systemMetrics = new SystemMetrics(); + private final DriveMetrics driveMetrics = new DriveMetrics(); + @GetMapping public Map index() { return new HashMap<>(); diff --git a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/controller/HgStoreStatusController.java b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/controller/HgStoreStatusController.java index e02315623c..fce51ab1b8 100644 --- a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/controller/HgStoreStatusController.java +++ b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/controller/HgStoreStatusController.java @@ -19,11 +19,6 @@ import java.io.Serializable; -import org.apache.hugegraph.store.grpc.state.ScanState; -import org.apache.hugegraph.store.node.entry.RestResult; -import org.apache.hugegraph.store.node.grpc.HgStoreNodeState; -import org.apache.hugegraph.store.node.grpc.HgStoreStreamImpl; -import org.apache.hugegraph.store.node.model.HgNodeStatus; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.GetMapping; @@ -32,6 +27,12 @@ import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.RestController; +import org.apache.hugegraph.store.grpc.state.ScanState; +import org.apache.hugegraph.store.node.entry.RestResult; +import org.apache.hugegraph.store.node.grpc.HgStoreNodeState; +import org.apache.hugegraph.store.node.grpc.HgStoreStreamImpl; +import org.apache.hugegraph.store.node.model.HgNodeStatus; +import org.apache.hugegraph.store.node.task.TTLCleaner; import com.google.protobuf.util.JsonFormat; /** @@ -42,6 +43,8 @@ public class HgStoreStatusController { @Autowired HgStoreStreamImpl streamImpl; + @Autowired + TTLCleaner cleaner; @GetMapping("/-/echo") public HgNodeStatus greeting( @@ -91,4 +94,26 @@ public Serializable getScanState() { } } + @GetMapping(value = "/-/cleaner", + produces = MediaType.APPLICATION_JSON_VALUE) + @ResponseBody + public Serializable ttlClean() { + RestResult result = new RestResult(); + try { + cleaner.submit(); + result.setState(RestResult.OK); + result.setMessage("" ); + return result; + } catch (Exception e) { + result.setState(RestResult.ERR); + result.setMessage(e.getMessage()); + return result; + } + } + + @GetMapping(value = "/v1/health", produces = MediaType.TEXT_PLAIN_VALUE) + public Serializable checkHealthy() { + return ""; + } + } diff --git a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/controller/HgTestController.java b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/controller/HgTestController.java index 157c7dfdaf..a13ecf1394 100644 --- a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/controller/HgTestController.java +++ b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/controller/HgTestController.java @@ -17,13 +17,13 @@ package org.apache.hugegraph.store.node.controller; -import java.util.ArrayList; -import java.util.List; - +import com.alipay.sofa.jraft.entity.PeerId; import org.apache.hugegraph.store.PartitionEngine; +import org.apache.hugegraph.store.node.grpc.HgStoreNodeService; import org.apache.hugegraph.store.meta.Partition; import org.apache.hugegraph.store.meta.Store; -import org.apache.hugegraph.store.node.grpc.HgStoreNodeService; + +import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.GetMapping; @@ -31,7 +31,8 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; -import lombok.extern.slf4j.Slf4j; +import java.util.ArrayList; +import java.util.List; /** * For testing only @@ -60,8 +61,12 @@ public Store testGetStoreInfo() { @GetMapping(value = "/raftRestart/{groupId}", produces = MediaType.APPLICATION_JSON_VALUE) public String restartRaftNode(@PathVariable(value = "groupId") int groupId) { PartitionEngine engine = nodeService.getStoreEngine().getPartitionEngine(groupId); + if (engine != null ) { engine.restartRaftNode(); return "OK"; + } else { + return "partition engine not found"; + } } @GetMapping(value = "/raftDelete/{groupId}", produces = MediaType.APPLICATION_JSON_VALUE) @@ -113,4 +118,72 @@ public String dbCompaction() { }); return "snapshot OK!"; } + + @GetMapping(value = "/pulse/reset", produces = MediaType.APPLICATION_JSON_VALUE) + public String resetPulse() { + try { + nodeService.getStoreEngine().getHeartbeatService().connectNewPulse(); + return "OK"; + } catch (Exception e) { + log.error("pulse reset error: ", e); + return e.getMessage(); + } + } + + @GetMapping(value = "/transferLeaders", produces = MediaType.APPLICATION_JSON_VALUE) + public String transferLeaders() { + try { + nodeService.getStoreEngine().getLeaderPartition().forEach(engine -> { + try { + engine.getRaftNode().transferLeadershipTo(PeerId.ANY_PEER); + } catch (Exception e) { + log.error("transfer leader error: ", e); + } + }); + return "OK"; + } catch (Exception e) { + log.error("pulse reset error: ", e); + return e.getMessage(); + } + } + + @GetMapping(value = "/no_vote", produces = MediaType.APPLICATION_JSON_VALUE) + public String noVote() { + try { + nodeService.getStoreEngine().getPartitionEngines().values().forEach(engine -> { + engine.getRaftNode().disableVote(); }); + return "OK"; + } catch (Exception e) { + log.error("pulse reset error: ", e); + return e.getMessage(); + } + } + + @GetMapping(value = "/restart_raft", produces = MediaType.APPLICATION_JSON_VALUE) + public String restartRaft() { + try { + nodeService.getStoreEngine().getPartitionEngines().values().forEach(PartitionEngine::restartRaftNode); + return "OK"; + } catch (Exception e) { + log.error("pulse reset error: ", e); + return e.getMessage(); + } + } + + + @GetMapping(value = "/all_raft_start", produces = MediaType.APPLICATION_JSON_VALUE) + public String isRaftAllStarted() { + try { + var engine = nodeService.getStoreEngine(); + var storeId = engine.getPartitionManager().getStore().getId(); + var flag = nodeService.getStoreEngine().getPdProvider().getPartitionsByStore(storeId).stream() + .mapToInt(Partition::getId) + .allMatch(i -> engine.getPartitionEngine(i) != null); + return flag ? "OK" : "NO" ; + } catch (Exception e) { + log.error("pulse reset error: ", e); + return e.getMessage(); + } + } + } diff --git a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/controller/PartitionAPI.java b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/controller/PartitionAPI.java index d55bcbf28a..8088bf137b 100644 --- a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/controller/PartitionAPI.java +++ b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/controller/PartitionAPI.java @@ -200,6 +200,20 @@ public Map arthasstart( return okMap("arthasstart", ret); } + public Map compact(@PathVariable(value = "id")int id){ + boolean submitted = nodeService.getStoreEngine().getBusinessHandler().blockingCompact("", id); + Map map = new HashMap<>(); + if (submitted) { + map.put("code", "OK"); + map.put("msg", "compaction was successfully submitted. See the log for more information"); + } else { + map.put("code", "Failed"); + map.put("msg", "compaction task fail to submit, and there could be another task in progress"); + } + return map; + } + + public Map okMap(String k, Object v) { Map map = new HashMap<>(); map.put("status", 0); diff --git a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/controller/RaftAPI.java b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/controller/RaftAPI.java new file mode 100644 index 0000000000..544338d687 --- /dev/null +++ b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/controller/RaftAPI.java @@ -0,0 +1,63 @@ +/* + * 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.store.node.controller; + +import javax.servlet.http.HttpServletRequest; + +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; + +import com.alipay.sofa.jraft.option.RpcOptions; +import org.apache.hugegraph.store.HgStoreEngine; +import org.apache.hugegraph.store.PartitionEngine; +import org.apache.hugegraph.store.node.entry.PartitionRequest; +import org.apache.hugegraph.store.node.entry.RestResult; + +import lombok.extern.slf4j.Slf4j; + +@RestController +@Slf4j +@RequestMapping("/raft") +public class RaftAPI { + + @PostMapping(value = "/options", consumes = MediaType.APPLICATION_JSON_VALUE, + produces = MediaType.APPLICATION_JSON_VALUE) + @ResponseBody + public RestResult options(@RequestBody PartitionRequest body, HttpServletRequest request) { + RestResult result = new RestResult(); + try { + if (body.getId() != null) { + PartitionEngine pe = HgStoreEngine.getInstance().getPartitionEngine(body.getId()); + if (pe != null) { + RpcOptions options = pe.getRaftGroupService().getNodeOptions(); + result.setData(options.toString()); + } + } + result.setState(RestResult.OK); + } catch (Exception e) { + result.setState(RestResult.ERR); + result.setMessage(e.getMessage()); + } + return result; + } +} + diff --git a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/entry/PartitionRequest.java b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/entry/PartitionRequest.java new file mode 100644 index 0000000000..ee278b7e1e --- /dev/null +++ b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/entry/PartitionRequest.java @@ -0,0 +1,26 @@ +/* + * 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.store.node.entry; + +import lombok.Data; + + +@Data +public class PartitionRequest { + private Integer id; +} diff --git a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/BatchGrpcClosure.java b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/BatchGrpcClosure.java index 14c0926787..7f1dbb682c 100644 --- a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/BatchGrpcClosure.java +++ b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/BatchGrpcClosure.java @@ -117,13 +117,12 @@ public PartitionFaultResponse getErrorResponse() { PartitionFaultResponse errorResponse; if (leaderMap.size() > 0) { - PartitionFaultResponse.Builder partitionFault = - PartitionFaultResponse.newBuilder().setFaultType( - PartitionFaultType.PARTITION_FAULT_TYPE_NOT_LEADER); + PartitionFaultResponse.Builder partitionFault = PartitionFaultResponse.newBuilder() + .setFaultType( + PartitionFaultType.PARTITION_FAULT_TYPE_NOT_LEADER); leaderMap.forEach((k, v) -> { - partitionFault.addPartitionLeaders(PartitionLeader.newBuilder() - .setPartitionId(k) - .setLeaderId(v).build()); + partitionFault.addPartitionLeaders( + PartitionLeader.newBuilder().setPartitionId(k).setLeaderId(v).build()); }); errorResponse = partitionFault.build(); } else { @@ -167,26 +166,23 @@ public void waitFinish(StreamObserver observer, Function, V> ok, long if (errorStatus.isEmpty()) { // No error, merge results observer.onNext(ok.apply(results)); } else { - observer.onNext((V) FeedbackRes.newBuilder() - .setStatus(ResStatus.newBuilder() - .setCode(ResCode.RES_CODE_FAIL) - .setMsg(getErrorMsg())) + observer.onNext((V) FeedbackRes.newBuilder().setStatus( + ResStatus.newBuilder().setCode(ResCode.RES_CODE_FAIL).setMsg(getErrorMsg())) .setPartitionFaultResponse(this.getErrorResponse()) .build()); } } catch (InterruptedException e) { log.error("waitFinish exception: ", e); - observer.onNext((V) FeedbackRes.newBuilder() - .setStatus(ResStatus.newBuilder() - .setCode(ResCode.RES_CODE_FAIL) - .setMsg(e.getLocalizedMessage()) - .build()).build()); + observer.onNext((V) FeedbackRes.newBuilder().setStatus( + ResStatus.newBuilder().setCode(ResCode.RES_CODE_FAIL) + .setMsg(e.getLocalizedMessage()).build()).build()); } observer.onCompleted(); } /** - * Select one incorrect result from multiple results, if there are no errors, return the first one. + * Select one incorrect result from multiple results, if there are no errors, return the + * first one. */ public FeedbackRes selectError(List results) { if (!CollectionUtils.isEmpty(results)) { @@ -202,10 +198,8 @@ public FeedbackRes selectError(List results) { }); return res.get(); } else { - return FeedbackRes.newBuilder() - .setStatus(ResStatus.newBuilder() - .setCode(ResCode.RES_CODE_OK).build()) - .build(); + return FeedbackRes.newBuilder().setStatus( + ResStatus.newBuilder().setCode(ResCode.RES_CODE_OK).build()).build(); } } diff --git a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/GRpcServerConfig.java b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/GRpcServerConfig.java index 27cb69a1de..df06b988a5 100644 --- a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/GRpcServerConfig.java +++ b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/GRpcServerConfig.java @@ -17,6 +17,7 @@ package org.apache.hugegraph.store.node.grpc; +import org.apache.hugegraph.store.consts.PoolNames; import org.apache.hugegraph.store.node.AppConfig; import org.apache.hugegraph.store.node.util.HgExecutorUtil; import org.lognet.springboot.grpc.GRpcServerBuilderConfigurer; @@ -30,8 +31,6 @@ */ @Component public class GRpcServerConfig extends GRpcServerBuilderConfigurer { - - public final static String EXECUTOR_NAME = "hg-grpc"; @Autowired private AppConfig appConfig; @@ -39,7 +38,7 @@ public class GRpcServerConfig extends GRpcServerBuilderConfigurer { public void configure(ServerBuilder serverBuilder) { AppConfig.ThreadPoolGrpc grpc = appConfig.getThreadPoolGrpc(); serverBuilder.executor( - HgExecutorUtil.createExecutor(EXECUTOR_NAME, grpc.getCore(), grpc.getMax(), + HgExecutorUtil.createExecutor(PoolNames.GRPC, grpc.getCore(), grpc.getMax(), grpc.getQueue()) ); } diff --git a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/HgStoreNodeService.java b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/HgStoreNodeService.java index 0305bd03c7..28682374a3 100644 --- a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/HgStoreNodeService.java +++ b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/HgStoreNodeService.java @@ -27,7 +27,8 @@ import javax.annotation.PreDestroy; import org.apache.hugegraph.store.HgStoreEngine; -import org.apache.hugegraph.store.business.DefaultDataMover; +import org.apache.hugegraph.store.business.DataManagerImpl; +import org.apache.hugegraph.store.grpc.common.TTLCleanRequest; import org.apache.hugegraph.store.grpc.session.BatchReq; import org.apache.hugegraph.store.grpc.session.CleanReq; import org.apache.hugegraph.store.grpc.session.GraphReq; @@ -45,6 +46,7 @@ import com.alipay.sofa.jraft.Status; import com.alipay.sofa.jraft.core.NodeMetrics; +import com.alipay.sofa.jraft.util.Utils; import com.google.protobuf.CodedInputStream; import com.google.protobuf.CodedOutputStream; @@ -61,7 +63,7 @@ public class HgStoreNodeService implements RaftTaskHandler { public static final byte TABLE_OP = 0x13; public static final byte GRAPH_OP = 0x14; public static final byte CLEAN_OP = 0x15; - + public static final byte TTL_CLEAN_OP = 0x16; public static final byte MAX_OP = 0x59; private final AppConfig appConfig; @Autowired @@ -100,8 +102,10 @@ public void init() { .isUseRocksDBSegmentLogStorage()); setMaxSegmentFileSize(appConfig.getRaft().getMaxSegmentFileSize()); setMaxReplicatorInflightMsgs(appConfig.getRaft().getMaxReplicatorInflightMsgs()); - setMaxEntriesSize(appConfig.getRaft().getMaxEntriesSize()); - setMaxBodySize(appConfig.getRaft().getMaxBodySize()); + if (appConfig.getRaft().getRpcPoolSizeByMultipleOfCPU() > 0){ + setRaftRpcThreadPoolSize(Utils.cpus() * raft.getRpcPoolSizeByMultipleOfCPU()); + } + setRaftRpcThreadPoolSizeOfBasic(raft.getRpcPoolSizeOfBasic()); }}); setFakePdOptions(new FakePdOptions() {{ setStoreList(appConfig.getFakePdConfig().getStoreList()); @@ -109,6 +113,15 @@ public void init() { setPartitionCount(appConfig.getFakePdConfig().getPartitionCount()); setShardCount(appConfig.getFakePdConfig().getShardCount()); }}); + + setQueryPushDownOption(new QueryPushDownOption(){{ + setThreadPoolSize(appConfig.getQueryPushDownConfig().getThreadPoolSize()); + setFetchBatchSize(appConfig.getQueryPushDownConfig().getFetchBatchSize()); + setFetchTimeout(appConfig.getQueryPushDownConfig().getFetchTimeOut()); + setMemoryLimitCount(appConfig.getQueryPushDownConfig().getMemoryLimitCount()); + setIndexSizeLimitCount(appConfig.getQueryPushDownConfig().getIndexSizeLimitCount()); + }}); + setJobConfig(appConfig.getJobOptions()); }}; RaftRocksdbOptions.initRocksdbGlobalConfig(options.getRocksdbConfig()); @@ -116,7 +129,7 @@ public void init() { options.getLabels().put("rest.port", Integer.toString(appConfig.getRestPort())); log.info("HgStoreEngine init {}", options); options.setTaskHandler(this); - options.setDataTransfer(new DefaultDataMover()); + options.setDataTransfer(new DataManagerImpl()); storeEngine = HgStoreEngine.getInstance(); storeEngine.init(options); @@ -182,6 +195,9 @@ public boolean invoke(int partId, byte[] request, RaftClosure response) throws case HgStoreNodeService.CLEAN_OP: invoke(partId, methodId, CleanReq.parseFrom(input), response); break; + case HgStoreNodeService.TTL_CLEAN_OP: + invoke(partId, methodId, TTLCleanRequest.parseFrom(input), response); + break; default: return false; // Unhandled } @@ -213,6 +229,9 @@ public boolean invoke(int partId, byte methodId, Object req, RaftClosure respons case HgStoreNodeService.CLEAN_OP: hgStoreSession.doClean(partId, (CleanReq) req, response); break; + case HgStoreNodeService.TTL_CLEAN_OP: + hgStoreSession.cleanTtl(partId, (TTLCleanRequest) req, response); + break; default: return false; // Unhandled } diff --git a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/HgStoreSessionImpl.java b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/HgStoreSessionImpl.java index 373de6ed67..d6182f093a 100644 --- a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/HgStoreSessionImpl.java +++ b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/HgStoreSessionImpl.java @@ -32,6 +32,7 @@ import org.apache.hugegraph.store.grpc.common.Kv; import org.apache.hugegraph.store.grpc.common.ResCode; import org.apache.hugegraph.store.grpc.common.ResStatus; +import org.apache.hugegraph.store.grpc.common.TTLCleanRequest; import org.apache.hugegraph.store.grpc.session.Agg; import org.apache.hugegraph.store.grpc.session.BatchEntry; import org.apache.hugegraph.store.grpc.session.BatchGetReq; @@ -58,10 +59,12 @@ import org.springframework.beans.factory.annotation.Autowired; import com.google.protobuf.ByteString; +import com.google.protobuf.util.JsonFormat; import io.grpc.stub.StreamObserver; import lombok.extern.slf4j.Slf4j; +//todo refactor @Slf4j @GRpcService public class HgStoreSessionImpl extends HgStoreSessionGrpc.HgStoreSessionImplBase { @@ -157,6 +160,30 @@ public void doClean(int partId, CleanReq request, RaftClosure response) { GrpcClosure.setResult(response, builder.build()); } + public void cleanTtl(int partId, TTLCleanRequest request, RaftClosure response) { + String graph = request.getGraph(); + FeedbackRes.Builder builder = FeedbackRes.newBuilder(); + String table = request.getTable(); + List ids = request.getIdsList(); + try { + if (getWrapper().cleanTtl(graph, partId, table, ids)) { + builder.setStatus(HgGrpc.success()); + } else { + builder.setStatus(HgGrpc.not()); + } + } catch (Throwable t) { + String msg = "cleanTtl with error: "; + try { + msg += JsonFormat.printer().print(request); + } catch (Exception e) { + } finally { + log.error(msg, t); + builder.setStatus(HgGrpc.fail(msg)); + } + } + GrpcClosure.setResult(response, builder.build()); + } + @Override public void batchGet2(BatchGetReq request, StreamObserver responseObserver) { String graph = request.getHeader().getGraph(); @@ -292,16 +319,11 @@ public void batch(BatchReq request, StreamObserver observer) { BatchGrpcClosure closure = new BatchGrpcClosure<>(groups.size()); groups.forEach((partition, entries) -> { - storeService.addRaftTask(HgStoreNodeService.BATCH_OP, graph, - partition, - BatchReq.newBuilder() - .setHeader(request.getHeader()) - .setWriteReq( - BatchWriteReq.newBuilder() - .addAllEntry( - entries)) - .build(), - closure.newRaftClosure()); + storeService.addRaftTask(HgStoreNodeService.BATCH_OP, graph, partition, + BatchReq.newBuilder().setHeader(request.getHeader()) + .setWriteReq(BatchWriteReq.newBuilder() + .addAllEntry(entries)) + .build(), closure.newRaftClosure()); }); if (!graph.isEmpty()) { @@ -527,25 +549,4 @@ public void doGraph(int partId, GraphReq request, RaftClosure response) { } GrpcClosure.setResult(response, builder.build()); } - - @Override - public void count(ScanStreamReq request, StreamObserver observer) { - ScanIterator it = null; - try { - BusinessHandler handler = storeService.getStoreEngine().getBusinessHandler(); - long count = handler.count(request.getHeader().getGraph(), request.getTable()); - observer.onNext(Agg.newBuilder().setCount(count).build()); - observer.onCompleted(); - } catch (Exception e) { - observer.onError(e); - } finally { - if (it != null) { - try { - it.close(); - } catch (Exception e) { - - } - } - } - } } diff --git a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/HgStoreStateService.java b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/HgStoreStateService.java index a8dc1c2cac..0fddb86bef 100644 --- a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/HgStoreStateService.java +++ b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/HgStoreStateService.java @@ -17,13 +17,21 @@ package org.apache.hugegraph.store.node.grpc; +import java.util.List; + +import org.apache.hugegraph.store.PartitionEngine; import org.apache.hugegraph.store.grpc.state.HgStoreStateGrpc; import org.apache.hugegraph.store.grpc.state.NodeStateRes; +import org.apache.hugegraph.store.grpc.state.PartitionRequest; +import org.apache.hugegraph.store.grpc.state.PeersResponse; import org.apache.hugegraph.store.grpc.state.ScanState; import org.apache.hugegraph.store.grpc.state.SubStateReq; import org.lognet.springboot.grpc.GRpcService; import org.springframework.beans.factory.annotation.Autowired; +import com.alipay.sofa.jraft.Node; +import com.alipay.sofa.jraft.conf.Configuration; +import com.alipay.sofa.jraft.entity.PeerId; import com.google.protobuf.Empty; import io.grpc.stub.StreamObserver; @@ -38,6 +46,8 @@ public class HgStoreStateService extends HgStoreStateGrpc.HgStoreStateImplBase { @Autowired HgStoreStreamImpl impl; + @Autowired + private HgStoreNodeService storeService; @Override public void subState(SubStateReq request, StreamObserver observer) { @@ -55,4 +65,24 @@ public void getScanState(SubStateReq request, StreamObserver observer observer.onNext(state); observer.onCompleted(); } + + @Override + public void getPeers(PartitionRequest request, StreamObserver observer) { + PartitionEngine engine = storeService.getStoreEngine().getPartitionEngine(request.getId()); + StringBuilder result = new StringBuilder(); + if (engine != null) { + Node raftNode = engine.getRaftNode(); + Configuration conf = raftNode.getCurrentConf(); + List peers = conf.getPeers(); + for (PeerId id : peers) { + result.append(id.getEndpoint().toString()); + result.append(","); + } + if (result.length() > 0) { + result.deleteCharAt(result.length() - 1); + } + } + observer.onNext(PeersResponse.newBuilder().setPeers(result.toString()).build()); + observer.onCompleted(); + } } diff --git a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/HgStoreStreamImpl.java b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/HgStoreStreamImpl.java index 7d01fa3db4..41e6b6b891 100644 --- a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/HgStoreStreamImpl.java +++ b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/HgStoreStreamImpl.java @@ -20,6 +20,7 @@ import java.util.concurrent.BlockingQueue; import java.util.concurrent.ThreadPoolExecutor; +import org.apache.hugegraph.store.consts.PoolNames; import org.apache.hugegraph.store.grpc.state.ScanState; import org.apache.hugegraph.store.grpc.stream.HgStoreStreamGrpc; import org.apache.hugegraph.store.grpc.stream.KvPageRes; @@ -70,7 +71,7 @@ public ThreadPoolExecutor getExecutor() { if (this.executor == null) { AppConfig.ThreadPoolScan scan = this.appConfig.getThreadPoolScan(); this.executor = - HgExecutorUtil.createExecutor("hg-scan", scan.getCore(), scan.getMax(), + HgExecutorUtil.createExecutor(PoolNames.SCAN, scan.getCore(), scan.getMax(), scan.getQueue()); } } diff --git a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/HgStoreWrapperEx.java b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/HgStoreWrapperEx.java index 26e7f2357f..ed938403b0 100644 --- a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/HgStoreWrapperEx.java +++ b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/HgStoreWrapperEx.java @@ -31,6 +31,8 @@ import org.apache.hugegraph.store.grpc.session.BatchEntry; import org.apache.hugegraph.store.term.HgPair; +import com.google.protobuf.ByteString; + import lombok.extern.slf4j.Slf4j; @Slf4j @@ -120,4 +122,9 @@ public boolean doGraph(int partId, GraphMethod method, String graph) { } return flag; } + + public boolean cleanTtl(String graph, int partId, String table, + List ids) { + return this.handler.cleanTtl(graph, partId, table, ids); + } } diff --git a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/ScanBatchResponse.java b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/ScanBatchResponse.java index 99ce662fe7..a99587cf23 100644 --- a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/ScanBatchResponse.java +++ b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/ScanBatchResponse.java @@ -45,8 +45,6 @@ @Slf4j public class ScanBatchResponse implements StreamObserver { - static ByteBufferAllocator bfAllocator = - new ByteBufferAllocator(ParallelScanIterator.maxBodySize * 3 / 2, 1000); static ByteBufferAllocator alloc = new ByteBufferAllocator(ParallelScanIterator.maxBodySize * 3 / 2, 1000); private final int maxInFlightCount = PropertyUtil.getInt("app.scan.stream.inflight", 16); diff --git a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/ScanBatchResponse3.java b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/ScanBatchResponse3.java index 2369dffd95..fac1c35820 100644 --- a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/ScanBatchResponse3.java +++ b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/ScanBatchResponse3.java @@ -34,7 +34,7 @@ import org.apache.hugegraph.store.grpc.stream.ScanCondition; import org.apache.hugegraph.store.grpc.stream.ScanQueryRequest; import org.apache.hugegraph.store.grpc.stream.ScanStreamBatchReq; -import org.apache.hugegraph.store.util.Base58Encoder; +import org.apache.hugegraph.store.node.util.Base58; import org.apache.hugegraph.store.node.util.HgAssert; import org.apache.hugegraph.store.node.util.HgGrpc; import org.apache.hugegraph.store.node.util.HgStoreConst; @@ -133,7 +133,7 @@ private void makeADeal(ScanQueryRequest request) { if (conditions.size() > 0) { ScanCondition c = conditions.get(0); if (c.getPrefix() != null && c.getPrefix().size() > 0) { - deliverId = Base58Encoder.convertToBase58(c.getPrefix().toByteArray()); + deliverId = Base58.encode(c.getPrefix().toByteArray()); log.info("[ANALYSIS DEAL] [{}] prefixLength: {}", deliverId, conditions.size()); } diff --git a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/query/AggregativeQueryObserver.java b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/query/AggregativeQueryObserver.java new file mode 100644 index 0000000000..eb2840e190 --- /dev/null +++ b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/query/AggregativeQueryObserver.java @@ -0,0 +1,398 @@ +/* + * 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.store.node.grpc.query; + +import org.apache.hugegraph.backend.BackendColumn; +import org.apache.hugegraph.rocksdb.access.RocksDBSession; +import org.apache.hugegraph.rocksdb.access.ScanIterator; +import org.apache.hugegraph.serializer.BinaryElementSerializer; +import org.apache.hugegraph.store.business.MultiPartitionIterator; +import org.apache.hugegraph.store.grpc.common.Kv; +import org.apache.hugegraph.store.grpc.query.QueryRequest; +import org.apache.hugegraph.store.grpc.query.QueryResponse; +import org.apache.hugegraph.store.node.grpc.query.model.PipelineResult; +import org.apache.hugegraph.store.node.grpc.query.model.QueryPlan; +import org.apache.hugegraph.store.node.grpc.query.stages.EarlyStopException; +import org.apache.hugegraph.store.query.KvSerializer; +import org.apache.hugegraph.structure.BaseEdge; +import org.apache.hugegraph.structure.BaseElement; +import org.apache.hugegraph.structure.BaseVertex; +import com.google.protobuf.ByteString; +import io.grpc.stub.StreamObserver; +import lombok.extern.slf4j.Slf4j; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; + +import static org.apache.hugegraph.store.node.grpc.query.AggregativeQueryService.errorResponse; + + +@Slf4j +public class AggregativeQueryObserver implements StreamObserver { + + private static final int RESULT_COUNT = 16; + + private StreamObserver sender; + + private final ExecutorService threadPool; + + private final long timeout; + + private final int batchSize; + + private final AtomicInteger consumeCount = new AtomicInteger(0); + + private final AtomicInteger sendCount = new AtomicInteger(0); + + private final AtomicBoolean clientCanceled = new AtomicBoolean(false); + + private volatile ScanIterator iterator = null; + + private QueryPlan plan = null; + + private String queryId; + +// private final ThreadLocal localBuilder = ThreadLocal.withInitial(QueryResponse::newBuilder); +// private final ThreadLocal localKvBuilder = ThreadLocal.withInitial(Kv::newBuilder); + private final BinaryElementSerializer serializer = BinaryElementSerializer.getInstance(); + + public AggregativeQueryObserver(StreamObserver sender, ExecutorService threadPool, long timeout, + int batchSize) { + this.sender = sender; + this.threadPool = threadPool; + this.batchSize = batchSize; + this.timeout = timeout; + } + + @Override + public void onNext(QueryRequest request) { + if (this.queryId == null) { + log.debug("got request: {}", request); + this.queryId = request.getQueryId(); + } + + // the first request, start the sending thread + if (iterator == null) { + long current = System.nanoTime(); + iterator = QueryUtil.getIterator(request); + plan = QueryUtil.buildPlan(request); + threadPool.submit(this::sendData); + log.debug("query id: {}, init data cost: {} ms", queryId, (System.nanoTime() - current) * 1.0 / 1000000); + } else { + this.consumeCount.incrementAndGet(); + log.debug("query id: {}, send feedback of {}", queryId, this.consumeCount.get()); + } + } + + @Override + public void onError(Throwable t) { + // 通道有问题的时候,停止计算 + this.clientCanceled.set(true); + log.error("AggregativeQueryService, query id: {}, got error", this.queryId, t); + } + + @Override + public void onCompleted() { + // client my be cancelled earlier + this.clientCanceled.set(true); + } + + public void sendData() { + try { + long lastSend = System.currentTimeMillis(); + var responseBuilder = getBuilder(); + var kvBuilder = getKvBuilder(); + + while (! this.clientCanceled.get()) { + // produces more result than consumer, just waiting + if (sendCount.get() - consumeCount.get() >= RESULT_COUNT) { + // read timeout, takes long time not to read data + if (System.currentTimeMillis() - lastSend > timeout) { + this.sender.onNext(errorResponse(getBuilder(), queryId, + new RuntimeException("sending-timeout, server closed"))); + this.sender.onCompleted(); + return; + } + + try { + Thread.sleep(1000); + continue; + } catch (InterruptedException ignore) { + log.warn("send data is interrupted, {}", ignore.getMessage()); + } + } + + var builder = readBatchData(responseBuilder, kvBuilder); + if (builder == null || this.clientCanceled.get()) { + break; + } else { + try { + builder.setQueryId(queryId); + sender.onNext(builder.build()); + this.sendCount.incrementAndGet(); + lastSend = System.currentTimeMillis(); + } catch (Exception e) { + log.error("send data got error: ", e); + break; + } + } + + if (builder.getIsFinished() || ! builder.getIsOk()) { + break; + } + } + } finally { + this.plan.clear(); + this.iterator.close(); + this.sender.onCompleted(); + } + } + + /** + * 1.1: pipeline is empty: + * --> read data from iterator + * 1.2: pipeline is not empty + * 1.2.1: only stop stage: --> just finish + * 1.2.2: has Agg or top or sort --> multi thread + * 1.2.3: plain stage: --> read data from iterator through pipeline + * + * @return result builder + */ + private QueryResponse.Builder readBatchData(QueryResponse.Builder builder, Kv.Builder kvBuilder) { + ScanIterator itr = this.iterator; + boolean empty = plan.isEmpty(); + boolean finish = false; + boolean checkIterator = true; + + int count = 0; + long current = System.nanoTime(); + + try { + if (! empty) { + if (this.plan.onlyStopStage()) { + builder.setIsOk(true).setIsFinished(true); + return builder; + } else if (this.plan.hasIteratorResult()) { + checkIterator = false; + AtomicReference exception = new AtomicReference<>(); + if (this.iterator instanceof MultiPartitionIterator) { + var iterators = ((MultiPartitionIterator) this.iterator).getIterators(); + CountDownLatch latch = new CountDownLatch(iterators.size()); + for (var itr2 : iterators) { + threadPool.execute(() -> { + try { + execute(itr2); + } catch (Exception e) { + exception.set(e); + } finally { + // MultiPartitionIterator 的 close 不生效。 + itr2.close(); + latch.countDown(); + } + }); + } + latch.await(timeout, TimeUnit.MILLISECONDS); + if (exception.get() != null) { + throw exception.get(); + } + } else { + // can't be parallel, but has agg like stage + execute(this.iterator); + } + + try { + // last empty element + itr = (ScanIterator) plan.execute(PipelineResult.EMPTY); + } catch (EarlyStopException ignore) { + } + } else { + itr = executePlainPipeline(this.iterator); + } + } + + builder.clear(); + + List batchResult = new ArrayList<>(); + while (itr.hasNext() && ! this.clientCanceled.get()) { + if (count >= batchSize) { + break; + } + + if (empty) { + // reading from raw iterator + var column = (RocksDBSession.BackendColumn) iterator.next(); + if (column != null) { + batchResult.add(kvBuilder.clear().setKey(ByteString.copyFrom(column.name)) + .setValue(column.value == null ? ByteString.EMPTY : ByteString.copyFrom(column.value)) + .build()); + // builder.addData(kvBuilder.setKey(ByteString.copyFrom(column.name)) + // .setValue(column.value == null ? ByteString.EMPTY : ByteString.copyFrom(column.value)) + // .build()); + count++; + } + } else { + // pass through pipeline + PipelineResult result = itr.next(); + if (result == null) { + continue; + } + + if (result == PipelineResult.EMPTY) { + finish = true; + break; + } + count++; + batchResult.add(toKv(kvBuilder, result)); + // builder.addData(toKv(result)); + } + } + + builder.addAllData(batchResult); + } catch (Exception e) { + log.error("readBatchData got error: ", e); + return builder.setIsOk(false).setIsFinished(false).setMessage("Store Server Error: " + + Arrays.toString(e.getStackTrace())); + } + + if (checkIterator){ + // check the iterator + finish = ! itr.hasNext(); + } + log.debug("query id: {}, finished batch, with size :{}, finish:{}, cost: {} ms", queryId, count, + finish, (System.nanoTime() - current) * 1.0 / 1000000); + + return builder.setIsOk(true).setIsFinished(finish); + } + + public ScanIterator executePlainPipeline(ScanIterator itr) { + return new ScanIterator() { + private boolean limitFlag = false; + @Override + public boolean hasNext() { + return itr.hasNext() && ! limitFlag; + } + + @Override + public boolean isValid() { + return itr.isValid(); + } + + @Override + public T next() { + try { + return (T) executePipeline(itr.next()); + } catch (EarlyStopException ignore) { + limitFlag = true; + return (T) PipelineResult.EMPTY; + } + } + + @Override + public void close() { + } + }; + } + + + /** + * 用于并行化处理 + * @param itr input iterator + */ + private void execute(ScanIterator itr) { + long recordCount = 0; + long current = System.nanoTime(); + while (itr.hasNext() && ! this.clientCanceled.get()) { + try { + recordCount ++; + executePipeline(itr.next()); + if (System.currentTimeMillis() - current > timeout * 1000) { + throw new RuntimeException("execution timeout"); + } + } catch (EarlyStopException ignore) { + // limit stage 会抛一个异常,提前中止运行 + // log.warn("query id: {}, early stop: {}", this.queryId, e.getMessage()); + break; + } + } + log.debug("query id: {}, read records: {}", this.queryId, recordCount); + } + + private Object executePipeline(Object obj) throws EarlyStopException { + PipelineResult input; + if (obj instanceof RocksDBSession.BackendColumn) { + input = new PipelineResult((RocksDBSession.BackendColumn) obj); + } else if (obj instanceof BaseElement) { + input = new PipelineResult((BaseElement) obj); + } else { + return null; + } + + return plan.execute(input); + } + + private QueryResponse.Builder getBuilder() { + return QueryResponse.newBuilder(); + // return localBuilder.get().clear(); + } + + private Kv.Builder getKvBuilder() { + return Kv.newBuilder(); + // return localKvBuilder.get().clear(); + } + + private Kv toKv(Kv.Builder builder, PipelineResult result) { + builder.clear(); + switch (result.getResultType()) { + case BACKEND_COLUMN: + var column = result.getColumn(); + builder.setKey(ByteString.copyFrom(column.name)); + builder.setValue(column.value == null ? ByteString.EMPTY : ByteString.copyFrom(column.value)); + break; + case MKV: + var mkv = result.getKv(); + builder.setKey(ByteString.copyFrom(KvSerializer.toBytes(mkv.getKeys()))); + builder.setValue(ByteString.copyFrom(KvSerializer.toBytes(mkv.getValues()))); + break; + case HG_ELEMENT: + var element = result.getElement(); + // builder.setKey(ByteString.copyFrom(element.id().asBytes())); + BackendColumn backendColumn; + if (element instanceof BaseVertex) { + backendColumn = serializer.writeVertex((BaseVertex) element); + } else { // if (element instanceof BaseEdge) { + backendColumn = serializer.writeEdge((BaseEdge) element); + } + + builder.setKey(ByteString.copyFrom(backendColumn.name)); + builder.setValue(ByteString.copyFrom(backendColumn.value)); + + break; + default: + throw new RuntimeException("unsupported result type: " + result.getResultType()); + } + + return builder.build(); + } +} diff --git a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/query/AggregativeQueryService.java b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/query/AggregativeQueryService.java new file mode 100644 index 0000000000..4d91ba1527 --- /dev/null +++ b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/query/AggregativeQueryService.java @@ -0,0 +1,145 @@ +/* + * 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.store.node.grpc.query; + +import org.apache.hugegraph.rocksdb.access.RocksDBSession; +import org.apache.hugegraph.store.HgStoreEngine; +import org.apache.hugegraph.store.consts.PoolNames; +import org.apache.hugegraph.store.grpc.common.Kv; +import org.apache.hugegraph.store.grpc.query.QueryRequest; +import org.apache.hugegraph.store.grpc.query.QueryResponse; +import org.apache.hugegraph.store.grpc.query.QueryServiceGrpc; +import org.apache.hugegraph.store.query.KvSerializer; +import org.apache.hugegraph.store.util.ExecutorUtil; +import com.google.protobuf.ByteString; +import io.grpc.stub.StreamObserver; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +import org.lognet.springboot.grpc.GRpcService; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.atomic.AtomicLong; + +@Slf4j +@GRpcService +public class AggregativeQueryService extends QueryServiceGrpc.QueryServiceImplBase { + + private final int batchSize ; + + private final Long timeout; + + @Getter + private final ThreadPoolExecutor threadPool; + + public AggregativeQueryService() { + var queryPushDownOption = HgStoreEngine.getInstance().getOption().getQueryPushDownOption(); + + timeout = queryPushDownOption.getFetchTimeout(); + batchSize = queryPushDownOption.getFetchBatchSize(); + + this.threadPool = ExecutorUtil.createExecutor(PoolNames.SCAN_V2, + Runtime.getRuntime().availableProcessors(), + queryPushDownOption.getThreadPoolSize(), + 10000, true); + } + + @Override + public StreamObserver query(StreamObserver observer) { + return new AggregativeQueryObserver(observer, threadPool, timeout, batchSize); + } + + @Override + public void query0(QueryRequest request, StreamObserver observer) { + + var itr = QueryUtil.getIterator(request); + var builder = QueryResponse.newBuilder(); + var kvBuilder = Kv.newBuilder(); + + try { + while (itr.hasNext()) { + var column = (RocksDBSession.BackendColumn) itr.next(); + if (column != null) { + builder.addData(kvBuilder.setKey(ByteString.copyFrom(column.name)) + .setValue(column.value == null ? ByteString.EMPTY : ByteString.copyFrom(column.value)) + .build()); + } + } + builder.setQueryId(request.getQueryId()); + builder.setIsOk(true); + builder.setIsFinished(true); + observer.onNext(builder.build()); + } catch (Exception e) { + observer.onNext(errorResponse(builder, request.getQueryId(), e)); + } + observer.onCompleted(); + } + + /** + * 查询数据条数 + * + * @param request 查询请求对象 + * @param observer Observer 对象,用于接收查询响应结果 + */ + @Override + public void count(QueryRequest request, StreamObserver observer) { + + log.debug("query id : {}, simple count of table: {}", request.getQueryId(), request.getTable()); + var builder = QueryResponse.newBuilder(); + var kvBuilder = Kv.newBuilder(); + + try { + + var handler = new QueryUtil().getHandler(); + long start = System.currentTimeMillis(); + long count = handler.count(request.getGraph(), request.getTable()); + log.debug("query id: {}, count of cost: {} ms", request.getQueryId(), System.currentTimeMillis() - start); + List array = new ArrayList<>(); + for (int i = 0 ; i < request.getFunctionsList().size(); i ++) { + array.add(new AtomicLong(count)); + } + + kvBuilder.setKey(ByteString.copyFrom(KvSerializer.toBytes(List.of()))); + kvBuilder.setValue(ByteString.copyFrom(KvSerializer.toBytes(array))); + builder.addData(kvBuilder.build()); + builder.setQueryId(request.getQueryId()); + builder.setIsOk(true); + builder.setIsFinished(true); + observer.onNext(builder.build()); + } catch (Exception e) { + observer.onNext(errorResponse(builder, request.getQueryId(), e)); + } + observer.onCompleted(); + } + + /** + * 生成错误响应。 + * + * @param queryId 查询标识符 + * @param t 异常对象 + * @return 查询响应对象 + */ + public static QueryResponse errorResponse(QueryResponse.Builder builder, String queryId, Throwable t) { + return builder.setQueryId(queryId) + .setIsOk(false) + .setIsFinished(false) + .setMessage(t.getMessage() == null ? "" : t.getMessage()) + .build(); + } +} diff --git a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/query/MultiKeyComparator.java b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/query/MultiKeyComparator.java new file mode 100644 index 0000000000..8cd0a190a3 --- /dev/null +++ b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/query/MultiKeyComparator.java @@ -0,0 +1,70 @@ +/* + * 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.store.node.grpc.query; + + +import org.apache.hugegraph.store.util.MultiKv; + +import java.util.Comparator; +import java.util.List; + +public class MultiKeyComparator implements Comparator{ + + private List orders; + + public MultiKeyComparator(List orders){ + this.orders = orders; + } + + @Override + public int compare(MultiKv o1, MultiKv o2) { + var key1 = o1 == null ? null : o1.getKeys(); + var key2 = o2 == null ? null : o2.getKeys(); + + if (key1 == null || key2 == null) { + if (key1 == null && key2 == null) { + return 0; + } + return key1 == null ? -1 : 1; + } + + for (int i = 0 ; i < this.orders.size(); i ++) { + var index = this.orders.get(i); + var v1 = key1.size() > index ? key1.get(index) : null; + var v2 = key2.size() > index ? key2.get(index) : null; + int ret = compareV((Comparable) v1, (Comparable) v2); + if (ret != 0) { + return ret; + } + } + + return key1.size() - key2.size(); + } + + private int compareV(Comparable a, Comparable b) { + if (a == null || b == null) { + if (a == null && b == null) { + return 0; + } + + return a == null ? -1 : 1; + } + + return a.compareTo(b); + } +} diff --git a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/query/QueryStage.java b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/query/QueryStage.java new file mode 100644 index 0000000000..51a1afd0f8 --- /dev/null +++ b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/query/QueryStage.java @@ -0,0 +1,47 @@ +/* + * 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.store.node.grpc.query; + +import org.apache.hugegraph.store.node.grpc.query.model.PipelineResult; +import org.apache.hugegraph.store.node.grpc.query.stages.EarlyStopException; + +import java.util.Iterator; + +public interface QueryStage { + + /** + * init params + * @param objects params + */ + default void init(Object... objects) {} + + default PipelineResult handle (PipelineResult result) throws EarlyStopException { + return null; + } + + default boolean isIterator() { + return false; + } + + default Iterator handleIterator(PipelineResult result) { + return null; + } + String getName(); + + default void close() {}; +} diff --git a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/query/QueryStages.java b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/query/QueryStages.java new file mode 100644 index 0000000000..c6addc0980 --- /dev/null +++ b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/query/QueryStages.java @@ -0,0 +1,91 @@ +/* + * 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.store.node.grpc.query; + + +import org.apache.hugegraph.store.node.grpc.query.stages.AggStage; +import org.apache.hugegraph.store.node.grpc.query.stages.DeserializationStage; +import org.apache.hugegraph.store.node.grpc.query.stages.ExtractAggFieldStage; +import org.apache.hugegraph.store.node.grpc.query.stages.FilterStage; +import org.apache.hugegraph.store.node.grpc.query.stages.LimitStage; +import org.apache.hugegraph.store.node.grpc.query.stages.OlapStage; +import org.apache.hugegraph.store.node.grpc.query.stages.OrderByStage; +import org.apache.hugegraph.store.node.grpc.query.stages.ProjectionStage; +import org.apache.hugegraph.store.node.grpc.query.stages.SampleStage; +import org.apache.hugegraph.store.node.grpc.query.stages.SimpleCountStage; +import org.apache.hugegraph.store.node.grpc.query.stages.StopStage; +import org.apache.hugegraph.store.node.grpc.query.stages.TopStage; +import org.apache.hugegraph.store.node.grpc.query.stages.TtlCheckStage; + +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class QueryStages { + + public static QueryStage ofFilterStage() { + return new FilterStage(); + } + + public static QueryStage ofProjectionStage() { + return new ProjectionStage(); + } + + public static QueryStage ofDeserializationStage() { + return new DeserializationStage(); + } + + public static QueryStage ofOlapStage() { + return new OlapStage(); + } + + public static QueryStage ofExtractAggFieldStage() { + return new ExtractAggFieldStage(); + } + + public static QueryStage ofAggStage() { + return new AggStage(); + } + + public static QueryStage ofOrderByStage() { + return new OrderByStage(); + } + + public static QueryStage ofLimitStage() { + return new LimitStage(); + } + + public static QueryStage ofSampleStage() { + return new SampleStage(); + } + + public static QueryStage ofSimpleCountStage() { + return new SimpleCountStage(); + } + + public static QueryStage ofStopStage() { + return new StopStage(); + } + + public static QueryStage ofTopStage() { + return new TopStage(); + } + + public static QueryStage ofTtlCheckStage() { + return new TtlCheckStage(); + } +} diff --git a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/query/QueryUtil.java b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/query/QueryUtil.java new file mode 100644 index 0000000000..2768ad14e9 --- /dev/null +++ b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/query/QueryUtil.java @@ -0,0 +1,374 @@ +/* + * 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.store.node.grpc.query; + +import org.apache.hugegraph.HugeGraphSupplier; +import org.apache.hugegraph.backend.BackendColumn; +import org.apache.hugegraph.id.Id; +import org.apache.hugegraph.id.IdUtil; +import org.apache.hugegraph.rocksdb.access.RocksDBSession; +import org.apache.hugegraph.rocksdb.access.ScanIterator; +import org.apache.hugegraph.serializer.BinaryElementSerializer; +import org.apache.hugegraph.store.HgStoreEngine; +import org.apache.hugegraph.store.business.BusinessHandler; +import org.apache.hugegraph.store.grpc.query.AggregationType; +import org.apache.hugegraph.store.grpc.query.DeDupOption; +import org.apache.hugegraph.store.grpc.query.ScanType; +import org.apache.hugegraph.store.grpc.query.ScanTypeParam; +import org.apache.hugegraph.store.grpc.query.QueryRequest; +import org.apache.hugegraph.store.node.grpc.EmptyIterator; +import org.apache.hugegraph.store.node.grpc.query.model.QueryPlan; +import org.apache.hugegraph.store.query.QueryTypeParam; +import org.apache.hugegraph.store.query.Tuple2; +import org.apache.hugegraph.store.query.func.AggregationFunction; +import org.apache.hugegraph.store.query.func.AggregationFunctions; +import org.apache.hugegraph.structure.BaseElement; +import org.apache.hugegraph.structure.BaseVertex; +import com.google.protobuf.ByteString; +import lombok.extern.slf4j.Slf4j; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.function.Supplier; +import java.util.stream.Collectors; + +import static org.apache.hugegraph.store.business.BusinessHandlerImpl.getGraphSupplier; +import static org.apache.hugegraph.store.constant.HugeServerTables.OLAP_TABLE; +import static org.apache.hugegraph.store.constant.HugeServerTables.TASK_TABLE; +import static org.apache.hugegraph.store.constant.HugeServerTables.VERTEX_TABLE; + +@Slf4j +public class QueryUtil { + + public static final List EMPTY_AGG_KEY = new ArrayList<>(); + + private static final Integer TOP_LIMIT = 10000; + + private static BusinessHandler handler; + + private static BinaryElementSerializer serializer = new BinaryElementSerializer(); + + private static Set vertexTables = new HashSet<>(List.of(VERTEX_TABLE, OLAP_TABLE, TASK_TABLE)); + + public BusinessHandler getHandler() { + if (this.handler == null) { + synchronized (this) { + if (this.handler == null) { + this.handler = HgStoreEngine.getInstance().getBusinessHandler(); + } + } + } + return handler; + } + + /** + * 要求有语意和顺序关系 + * implementation + * @param request query request + * @return query plan + */ + public static QueryPlan buildPlan(QueryRequest request) { + QueryPlan plan = new QueryPlan(); + + if (request.getSampleFactor() == 0.0) { + // 全不抽样 + plan.addStage(QueryStages.ofStopStage()); + return plan; + } + + if (request.getSampleFactor() < 1.0) { + var sampleStage= QueryStages.ofSampleStage(); + sampleStage.init(request.getSampleFactor()); + plan.addStage(sampleStage); + } + + // only count agg. fast-forward + if (isOnlyCountAggregationFunction(request)){ + var simple = QueryStages.ofSimpleCountStage(); + simple.init(request.getFunctionsList().size()); + plan.addStage(simple); + } else { + if (request.getCheckTtl()) { + var ttl = QueryStages.ofTtlCheckStage(); + ttl.init(isVertex(request.getTable())); + plan.addStage(ttl); + } + + // when to de-serialization ? + if (needDeserialize(request)) { + var deserializeStage = QueryStages.ofDeserializationStage(); + deserializeStage.init(request.getTable(), + getGraphSupplier(request.getGraph())); + plan.addStage(deserializeStage); + } + + if (!isEmpty(request.getOlapPropertyList())) { + var olap = QueryStages.ofOlapStage(); + olap.init(request.getGraph(), request.getTable(), request.getOlapPropertyList()); + plan.addStage(olap); + } + + if (! request.getCondition().isEmpty()) { + var filterStage = QueryStages.ofFilterStage(); + filterStage.init(request.getCondition().toByteArray()); + plan.addStage(filterStage); + } + + if (!isEmpty(request.getFunctionsList())) { + var extractAggField = QueryStages.ofExtractAggFieldStage(); + List fields = new ArrayList<>(); + for (var func : request.getFunctionsList()) { + if (func.getFuncType() == AggregationType.COUNT) { + fields.add(null); + } else { + fields.add(func.getField()); + } + } + + extractAggField.init(request.getGroupByList(), fields, + request.getGroupBySchemaLabel(), isVertex(request.getTable())); + plan.addStage(extractAggField); + } + } + + // aggregation + if (!isEmpty(request.getFunctionsList())) { + var agg = QueryStages.ofAggStage(); + List> funcMetas = new ArrayList<>(); + for (var func : request.getFunctionsList()) { + funcMetas.add(new Tuple2<>(func.getFuncType(), func.getType())); + } + agg.init(funcMetas); + plan.addStage(agg); + } + + if (!isEmpty(request.getPropertyList()) || request.getNullProperty()){ + var selector = QueryStages.ofProjectionStage(); + selector.init(request.getPropertyList(), request.getNullProperty()); + plan.addStage(selector); + } + + // sort + limit -> top operation + if (canOptimiseToTop(request)) { + var topStage = QueryStages.ofTopStage(); + topStage.init(request.getLimit(), request.getOrderByList(), request.getSortOrder()); + plan.addStage(topStage); + } else { + if (!isEmpty(request.getOrderByList())) { + var order = QueryStages.ofOrderByStage(); + order.init(request.getOrderByList(), request.getGroupByList(), + !isEmpty(request.getFunctionsList()), + request.getSortOrder()); + plan.addStage(order); + } + + if (request.getLimit() > 0) { + var limit = QueryStages.ofLimitStage(); + limit.init(request.getLimit()); + plan.addStage(limit); + } + } + + log.debug("query id: {} ,build plan result: {}", request.getQueryId(), plan); + return plan; + } + + private static boolean isOnlyCountAggregationFunction(QueryRequest request){ + return !isEmpty(request.getFunctionsList()) && + request.getFunctionsList().stream().allMatch(f -> f.getFuncType() == AggregationType.COUNT) && + isEmpty(request.getGroupByList()) && request.getCondition().isEmpty() + && !request.getGroupBySchemaLabel(); + } + + private static boolean canOptimiseToTop(QueryRequest request) { + return !isEmpty(request.getOrderByList()) && request.getLimit() < TOP_LIMIT && request.getLimit() > 0; + } + + /** + * 判断是否需要反序列化。 + * + * @param request 查询请求对象。 + * @return 如果需要反序列化则返回 true,否则返回 false。 + */ + private static boolean needDeserialize(QueryRequest request) { + return !isEmpty(request.getOrderByList()) || !isEmpty(request.getPropertyList()) + || !request.getCondition().isEmpty() || !isEmpty(request.getFunctionsList()) + && !request.getGroupBySchemaLabel(); + } + + /** + * 获取一个扫描迭代器。 + * + * @param request 查询请求对象。 + * @return 查询迭代器。 + */ + public static ScanIterator getIterator(QueryRequest request) { + + var handler = new QueryUtil().getHandler(); + + switch (request.getScanType()) { + case TABLE_SCAN: + return handler.scanAll(request.getGraph(), request.getTable()); + + case PRIMARY_SCAN: + // id scan + // todo: 多个主键查询 + 精确去重+limit 的情况,考虑使用 map 做一部分的精确 + return handler.scan(request.getGraph(), request.getTable(), toQTP(request.getScanTypeParamList()), + request.getDedupOption()); + + case NO_SCAN: + // no scan 不需要反查: + // 1. 能够直接解析,不需要反查。2. 不需要消重,直接取 count + return handler.scanIndex(request.getGraph(), + request.getIndexesList().stream() + .map(x -> toQTP(x.getParamsList())) + .collect(Collectors.toList()), + request.getDedupOption(), + request.getLoadPropertyFromIndex(), + request.getCheckTtl()); + + case INDEX_SCAN: + return handler.scanIndex(request.getGraph(), + request.getTable(), + request.getIndexesList().stream() + .map(x -> toQTP(x.getParamsList())) + .collect(Collectors.toList()), + request.getDedupOption(), + true, + needIndexTransKey(request), + request.getCheckTtl(), + request.getLimit()); + default: + break; + } + + return new EmptyIterator(); + } + + /** + * 1. no scan/ 不需要回表 + * 2. 只有一个索引, + * @param request + * @return + */ + private static boolean needIndexTransKey(QueryRequest request) { + if (request.getScanType() == ScanType.NO_SCAN ) { + return ! isOnlyCountAggregationFunction(request) && request.getDedupOption() == DeDupOption.NONE; + } + return true; + } + + private static List toQTP(List range) { + return range.stream().map(QueryUtil::fromScanTypeParam).collect(Collectors.toList()); + } + + private static QueryTypeParam fromScanTypeParam(ScanTypeParam param) { + return new QueryTypeParam(param.getKeyStart().toByteArray(), + param.getKeyEnd().toByteArray(), + param.getScanBoundary(), + param.getIsPrefix(), + param.getIsSecondaryIndex(), + param.getCode(), + param.getIdPrefix().toByteArray()); + } + + public static boolean isEmpty(Collection c) { + return c == null || c.size() == 0; + } + + + public static BaseElement parseEntry(HugeGraphSupplier graph, + BackendColumn column, + boolean isVertex) { + if (isVertex) { + return serializer.parseVertex(graph, column, null); + } else { + return serializer.parseEdge(graph, column, null, true); + } + } + + public static BaseElement parseOlap(BackendColumn column, BaseVertex vertex) { + return serializer.parseVertexOlap(null, column, vertex); + } + + /** + * 一次的顶点序列化 - 反序列化 + * @param vertexColumn vertex + * @param olap olap vertex + * @return new vertex + */ + public static BackendColumn combineColumn(BackendColumn vertexColumn, List olap) { + return serializer.mergeCols(vertexColumn, olap.toArray(new BackendColumn[0])); + } + + + public static AggregationFunction createFunc(AggregationType funcType, String genericType) { + AggregationFunction func = null; + switch (funcType) { + case AVG: + func = new AggregationFunctions.AvgFunction(getAggregationBufferSupplier(genericType)); + break; + case SUM: + func = new AggregationFunctions.SumFunction(getAggregationBufferSupplier(genericType)); + break; + case MAX: + func = new AggregationFunctions.MaxFunction(getAggregationBufferSupplier(genericType)); + break; + case MIN: + func = new AggregationFunctions.MinFunction(getAggregationBufferSupplier(genericType)); + break; + case COUNT: + func = new AggregationFunctions.CountFunction(); + break; + default: + break; + } + return func; + } + + public static Supplier getAggregationBufferSupplier(String genericType) { + return AggregationFunctions.getAggregationBufferSupplier(genericType); + } + + + public static List fromStringBytes(List list) { + return list.stream() + .map(id -> id == null ? null : IdUtil.fromBytes(id.toByteArray())) + .collect(Collectors.toList()); + } + + /** + * 判断表是否为顶点表 + * + * @param table 待判断的表名 + * @return 如果是顶点表,返回 true;否则返回 false。 + */ + public static boolean isVertex(String table) { + return vertexTables.contains(table); + } + + public static Long getLabelId(RocksDBSession.BackendColumn column, boolean isVertex) { + var id = serializer.parseLabelFromCol(BackendColumn.of(column.name, column.value), + isVertex); + return id.asLong(); + } + +} diff --git a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/query/model/PipelineResult.java b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/query/model/PipelineResult.java new file mode 100644 index 0000000000..0043236949 --- /dev/null +++ b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/query/model/PipelineResult.java @@ -0,0 +1,75 @@ +/* + * 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.store.node.grpc.query.model; + +import org.apache.hugegraph.rocksdb.access.RocksDBSession; +import org.apache.hugegraph.store.util.MultiKv; +import org.apache.hugegraph.structure.BaseElement; +import lombok.Data; + +@Data +public class PipelineResult { + + public static final PipelineResult EMPTY = nullResult(); + + private PipelineResultType resultType; + private RocksDBSession.BackendColumn column; + private BaseElement element; + private MultiKv kv; + private String message; + + public PipelineResult(RocksDBSession.BackendColumn column) { + this.resultType = PipelineResultType.BACKEND_COLUMN; + this.column = column; + } + + public PipelineResult(BaseElement element) { + this.resultType = PipelineResultType.HG_ELEMENT; + this.element = element; + } + + public PipelineResult(MultiKv kv){ + this.resultType = PipelineResultType.MKV; + this.kv = kv; + } + + private PipelineResult(){ + this.resultType = PipelineResultType.NULL; + } + + private PipelineResult(String message){ + this.resultType = PipelineResultType.ERROR; + this.message = message; + } + + public static PipelineResult nullResult() { + return new PipelineResult(); + } + + public static PipelineResult ofError(String message) { + return new PipelineResult(message); + } + + public boolean isEmpty() { + return resultType == PipelineResultType.NULL; + } + + public boolean isError() { + return resultType == PipelineResultType.ERROR; + } +} diff --git a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/query/model/PipelineResultType.java b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/query/model/PipelineResultType.java new file mode 100644 index 0000000000..007b5b67f9 --- /dev/null +++ b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/query/model/PipelineResultType.java @@ -0,0 +1,26 @@ +/* + * 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.store.node.grpc.query.model; + +public enum PipelineResultType { + MKV, + BACKEND_COLUMN, + HG_ELEMENT, + NULL, + ERROR; +} diff --git a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/query/model/QueryPlan.java b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/query/model/QueryPlan.java new file mode 100644 index 0000000000..bd412f89c3 --- /dev/null +++ b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/query/model/QueryPlan.java @@ -0,0 +1,130 @@ +/* + * 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.store.node.grpc.query.model; + +import org.apache.hugegraph.store.node.grpc.query.QueryStage; +import org.apache.hugegraph.store.node.grpc.query.stages.EarlyStopException; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.stream.Collectors; + +public class QueryPlan { + private List stages; + + public QueryPlan() { + stages = new LinkedList<>(); + } + + public void addStage(QueryStage pipeline) { + this.stages.add(pipeline); + } + + public boolean onlyStopStage(){ + return stages.size() == 1 && "STOP_STAGE".equals(stages.get(0).getName()); + } + + /** + * 判断聚合阶段是否存在。 + * + * @return 如果存在聚合阶段则返回true,否则返回false。 + */ + public boolean containsAggStage() { + return stages.stream().anyMatch(stage -> stage.getName().equals("AGG_STAGE")); + } + + /** + * execute pipeline + * + * @param data the input data + * @return null when filtered or limited, iterator when encounter an iterator stage, or element when plain pipeline + * @throws EarlyStopException throws early stop exception when reach the limit of limit stage + */ + public Object execute(PipelineResult data) throws EarlyStopException { + if (data == null || this.stages.isEmpty()) { + return data; + } + + List current = new ArrayList<>(); + List next = new ArrayList<>(); + + next.add(data); + + for (QueryStage stage : stages) { + current.clear(); + current.addAll(next); + next.clear(); + for (var item : current) { + if (item instanceof Iterator) { + var itr = (Iterator) item; + while (itr.hasNext()) { + callStage(stage, next, itr.next()); + } + } else { + callStage(stage, next, (PipelineResult) item); + } + } + } + + if (next.isEmpty()) { + return null; + } + + if (next.get(0) instanceof Iterator || next.size() == 1) { + return next.get(0); + } + + return next.iterator(); + } + + private void callStage(QueryStage stage, List list, PipelineResult pre) throws EarlyStopException { + Object ret ; + if (stage.isIterator()) { + ret = stage.handleIterator(pre); + } else { + ret = stage.handle(pre); + } + + if (ret != null) { + list.add(ret); + } + } + + @Override + public String toString() { + var names = String.join(", ", stages.stream().map(QueryStage::getName).collect(Collectors.toList())); + return "QueryPlan{" + "stages=[" + names + "]}"; + } + + public void clear() { + for (var stage: stages) { + stage.close(); + } + this.stages.clear(); + } + + public boolean isEmpty() { + return this.stages.isEmpty(); + } + + public boolean hasIteratorResult() { + return this.stages.stream().anyMatch(QueryStage::isIterator); + } +} diff --git a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/query/stages/AggStage.java b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/query/stages/AggStage.java new file mode 100644 index 0000000000..0292cc58d2 --- /dev/null +++ b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/query/stages/AggStage.java @@ -0,0 +1,210 @@ +/* + * 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.store.node.grpc.query.stages; + +import org.apache.hugegraph.store.business.itrv2.FileObjectIterator; +import org.apache.hugegraph.store.business.itrv2.TypeTransIterator; +import org.apache.hugegraph.store.business.itrv2.io.SortShuffleSerializer; +import org.apache.hugegraph.store.grpc.query.AggregationType; +import org.apache.hugegraph.store.node.grpc.query.QueryStage; +import org.apache.hugegraph.store.node.grpc.query.QueryUtil; +import org.apache.hugegraph.store.node.grpc.query.model.PipelineResult; +import org.apache.hugegraph.store.node.grpc.query.model.PipelineResultType; +import org.apache.hugegraph.store.query.Tuple2; +import org.apache.hugegraph.store.query.func.AggregationFunction; +import org.apache.hugegraph.store.query.func.AggregationFunctions; +import org.apache.hugegraph.store.util.MultiKv; +import org.apache.hugegraph.store.util.SortShuffle; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.ObjectOutputStream; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.Collectors; + +/** + * 聚合计算 + */ +public class AggStage implements QueryStage { + + private static final Integer MAP_SIZE = 10000; + + private final Map, List> maps = new ConcurrentHashMap<>(); + + private List> funcMetas = new ArrayList<>(); + + private Integer functionSize; + + private String file; + + private String path; + + @Override + public boolean isIterator() { + return true; + } + + /** + * 初始化方法,用于初始化聚合函数元数据列表和路径。 + * + * @param objects 参数数组,第一个参数为聚合函数元数据的列表。 + */ + @Override + public void init(Object... objects) { + this.funcMetas = (List>) objects[0]; + functionSize = funcMetas.size(); + path = SortShuffle.getBasePath() + "agg_tmp_" + Thread.currentThread().getId() + "/"; + new File(path).mkdirs(); + } + + /** + * 将迭代器中的数据进行处理,并返回结果的迭代器 + * + * @param result 数据结果对象 + * @return 返回处理后的迭代器 + */ + @Override + public Iterator handleIterator(PipelineResult result) { + if (result.getResultType() == PipelineResultType.MKV) { + var kv = result.getKv(); + if (! maps.containsKey(kv.getKeys())) { + maps.putIfAbsent(kv.getKeys(), generateFunctions()); + } + + for (int i = 0 ; i < functionSize; i ++) { + var function = maps.get(kv.getKeys()).get(i); + Object value = kv.getValues().get(i); + if (function instanceof AggregationFunctions.AvgFunction) { + var avgFunction = (AggregationFunctions.AvgFunction) function; + value = transValue(avgFunction.getFiledClassType(), value); + } + function.iterate(value); + } + } + + if (maps.size() > MAP_SIZE) { + // write to local buffer + synchronized (this.maps) { + if (maps.size() > MAP_SIZE) { + writeToFile(changeToList()); + } + } + } + + if (result.isEmpty()) { + var list = changeToList(); + if (this.file == null) { + return new TypeTransIterator<>(list.iterator(), PipelineResult::new, + () -> PipelineResult.EMPTY).toIterator(); + } else { + writeToFile(list); + return new TypeTransIterator<>( + new FileObjectIterator<>(this.file, SortShuffleSerializer.ofBackendColumnSerializer()), + PipelineResult::new, () -> PipelineResult.EMPTY + ).toIterator(); + } + } + + return null; + } + + /** + * avg 函数的隐式转换 + * @param clz the class type of the value + * @param value value + * @return Double value + */ + private Double transValue(Class clz, Object value) { + Double retValue = null; + + if (clz.equals(Integer.class)) { + retValue = (double) (int) value; + } else if (clz.equals(Long.class)) { + retValue = (double) (long) value; + } else if (clz.equals(Double.class)) { + retValue = (double) value; + } else if (clz.equals(Float.class)) { + retValue = (double) (float) value; + } else if (clz.equals(String.class)) { + retValue = Double.valueOf((String) value); + } + + return retValue; + } + + @Override + public String getName() { + return "AGG_STAGE"; + } + + /** + * 生成函数列表。 + * + * @return 聚合函数列表。 + */ + private List generateFunctions() { + + List result = new ArrayList<>(); + + for (var funcMeta : funcMetas) { + result.add(QueryUtil.createFunc(funcMeta.getV1(), funcMeta.getV2())); + } + return result; + } + + private List changeToList() { + List result = new ArrayList<>(); + for (var entry: this.maps.entrySet()) { + result.add(new MultiKv(entry.getKey(), + entry.getValue().stream() + .map(x -> x.getBuffer()) + .collect(Collectors.toList()))); + } + + result.sort(MultiKv::compareTo); + this.maps.clear(); + return result; + } + + private void writeToFile(List list) { + if (this.file == null) { + file = path + System.currentTimeMillis() % 10000 + ".dat"; + } + + try { + ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(this.file, true)); + for (var item : list) { + oos.writeObject(item); + } + this.maps.clear(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + @Override + public void close() { + this.maps.clear(); + this.funcMetas.clear(); + } +} diff --git a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/query/stages/DeserializationStage.java b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/query/stages/DeserializationStage.java new file mode 100644 index 0000000000..4b229926f1 --- /dev/null +++ b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/query/stages/DeserializationStage.java @@ -0,0 +1,74 @@ +/* + * 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.store.node.grpc.query.stages; + +import org.apache.hugegraph.HugeGraphSupplier; +import org.apache.hugegraph.backend.BackendColumn; +import org.apache.hugegraph.store.node.grpc.query.QueryStage; +import org.apache.hugegraph.store.node.grpc.query.QueryUtil; +import org.apache.hugegraph.store.node.grpc.query.model.PipelineResult; + +import lombok.extern.slf4j.Slf4j; + +/** + * 反序列化 + */ +@Slf4j +public class DeserializationStage implements QueryStage { + + private HugeGraphSupplier graph; + private String table; + + @Override + public void init(Object... objects) { + this.table = (String) objects[0]; + this.graph = (HugeGraphSupplier) objects[1]; + } + + /** + * 将 PipelineResult 处理为 PipelineResult,将查询结果转换为图元素。 + * + * @param result 查询结果 + * @return 转换后的 PipelineResult,如果查询结果为空则返回 null。 + */ + @Override + public PipelineResult handle(PipelineResult result) { + if (result.isEmpty()) { + return result; + } + var column = result.getColumn(); + if (column.value == null) { + return null; + } + try { + var element = QueryUtil.parseEntry(this.graph, + BackendColumn.of(column.name, column.value), + QueryUtil.isVertex(this.table)); + return new PipelineResult(element); + } catch (Exception e) { + log.error("Deserialization error: {}", graph, e); + return null; + } + } + + @Override + public String getName() { + return "DESERIALIZATION_STAGE"; + } + +} diff --git a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/query/stages/EarlyStopException.java b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/query/stages/EarlyStopException.java new file mode 100644 index 0000000000..70ad64a460 --- /dev/null +++ b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/query/stages/EarlyStopException.java @@ -0,0 +1,21 @@ +/* + * 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.store.node.grpc.query.stages; + +public class EarlyStopException extends Exception{ +} diff --git a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/query/stages/ExtractAggFieldStage.java b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/query/stages/ExtractAggFieldStage.java new file mode 100644 index 0000000000..490f267606 --- /dev/null +++ b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/query/stages/ExtractAggFieldStage.java @@ -0,0 +1,101 @@ +/* + * 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.store.node.grpc.query.stages; + +import org.apache.hugegraph.id.Id; +import org.apache.hugegraph.store.node.grpc.query.QueryStage; +import org.apache.hugegraph.store.node.grpc.query.QueryUtil; +import org.apache.hugegraph.store.node.grpc.query.model.PipelineResult; +import org.apache.hugegraph.store.node.grpc.query.model.PipelineResultType; +import org.apache.hugegraph.store.util.MultiKv; +import org.apache.hugegraph.structure.BaseElement; +import com.google.protobuf.ByteString; + +import java.util.List; +import java.util.stream.Collectors; + +/** + * 提取聚合函数所需字段 + */ +public class ExtractAggFieldStage implements QueryStage { + private List groupBys; + + private List fields; + + private boolean groupByElementSchemaId; + private boolean isVertex; + + /** + * 初始化函数,用于初始化对象 + * + * @param objects 对象数组 + */ + @Override + public void init(Object... objects) { + // group by 按照 property 的顺序,方便后面的剪裁 + this.groupBys = QueryUtil.fromStringBytes((List) objects[0]); + this.fields = QueryUtil.fromStringBytes((List) objects[1]); + this.groupByElementSchemaId = (boolean) objects[2]; + this.isVertex = (boolean) objects[3]; + } + + /** + * 重写父类方法 handle,用于处理 PipelineResult 结果 + * + * @param result PipelineResult 结果对象 + * @return 返回处理后的 PipelineResult 结果对象 + */ + @Override + public PipelineResult handle(PipelineResult result) { + if (result == null) { + return null; + } + + if (this.groupByElementSchemaId && ! result.isEmpty()) { + return new PipelineResult(MultiKv.of(List.of(QueryUtil.getLabelId(result.getColumn(), + this.isVertex)), + List.of(1L))); + } else if (result.getResultType() == PipelineResultType.HG_ELEMENT) { + var element = result.getElement(); + return new PipelineResult(MultiKv.of(getFields(this.groupBys, element), + getFields(this.fields, element))); + } + return result; + } + + private List getFields(List ids, BaseElement element) { + return ids.stream() + .map(id -> id == null ? null : element.getPropertyValue(id)) + .collect(Collectors.toList()); + } + + private List getSchemaId(BaseElement element) { + return List.of(element.schemaLabel().id().asLong()); + } + + @Override + public String getName() { + return "EXTRACT_AGG_FIELD_STAGE"; + } + + @Override + public void close() { + this.fields.clear(); + this.groupBys.clear(); + } +} diff --git a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/query/stages/FilterStage.java b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/query/stages/FilterStage.java new file mode 100644 index 0000000000..0e6f03aaa9 --- /dev/null +++ b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/query/stages/FilterStage.java @@ -0,0 +1,58 @@ +/* + * 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.store.node.grpc.query.stages; + +import org.apache.hugegraph.query.ConditionQuery; +import org.apache.hugegraph.store.node.grpc.query.QueryStage; +import org.apache.hugegraph.store.node.grpc.query.model.PipelineResult; + +/** + * 过滤 + */ +public class FilterStage implements QueryStage { + + private ConditionQuery conditionQUery; + + @Override + public void init(Object... objects) { + this.conditionQUery = ConditionQuery.fromBytes((byte[]) objects[0]); + } + + @Override + public PipelineResult handle(PipelineResult result) { + if (result == null || result.isEmpty()){ + return result; + } + + if (result.getElement() == null) { + return null; + } + + if (conditionQUery.resultType().isVertex() || conditionQUery.resultType().isEdge()) { + if (! conditionQUery.test(result.getElement())){ + return null; + } + } + return result; + } + + @Override + public String getName() { + return "FILTER_STAGE"; + } +} diff --git a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/query/stages/LimitStage.java b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/query/stages/LimitStage.java new file mode 100644 index 0000000000..0445611f8a --- /dev/null +++ b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/query/stages/LimitStage.java @@ -0,0 +1,52 @@ +/* + * 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.store.node.grpc.query.stages; + +import org.apache.hugegraph.store.node.grpc.query.QueryStage; +import org.apache.hugegraph.store.node.grpc.query.model.PipelineResult; + +import java.util.Objects; +import java.util.concurrent.atomic.AtomicLong; + +/** + * 限制N + */ +public class LimitStage implements QueryStage { + + private final AtomicLong counter = new AtomicLong(0); + + private volatile Long limit; + + @Override + public void init(Object... objects) { + limit = (long) (int) objects[0]; + } + + @Override + public PipelineResult handle(PipelineResult result) throws EarlyStopException { + if (Objects.equals(result, PipelineResult.EMPTY) || counter.getAndIncrement() < this.limit) { + return result; + } + throw new EarlyStopException(); + } + + @Override + public String getName() { + return "LIMIT_STAGE"; + } +} diff --git a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/query/stages/OlapStage.java b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/query/stages/OlapStage.java new file mode 100644 index 0000000000..467976ce80 --- /dev/null +++ b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/query/stages/OlapStage.java @@ -0,0 +1,123 @@ +/* + * 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.store.node.grpc.query.stages; + +import org.apache.hugegraph.backend.BackendColumn; +import org.apache.hugegraph.id.Id; +import org.apache.hugegraph.pd.common.PartitionUtils; +import org.apache.hugegraph.rocksdb.access.RocksDBSession; +import org.apache.hugegraph.serializer.BinaryElementSerializer; +import org.apache.hugegraph.serializer.BytesBuffer; +import org.apache.hugegraph.store.business.BusinessHandler; +import org.apache.hugegraph.store.node.grpc.query.QueryStage; +import org.apache.hugegraph.store.node.grpc.query.QueryUtil; +import org.apache.hugegraph.store.node.grpc.query.model.PipelineResult; +import org.apache.hugegraph.store.node.grpc.query.model.PipelineResultType; +import org.apache.hugegraph.structure.BaseVertex; +import com.google.protobuf.ByteString; + +import java.util.ArrayList; +import java.util.List; + +import static org.apache.hugegraph.store.constant.HugeServerTables.OLAP_TABLE; + +import lombok.extern.slf4j.Slf4j; + +/** + * OLAP 查询 + */ +@Slf4j +public class OlapStage implements QueryStage { + private String graph; + + private String table; + + private List properties; + + private final BusinessHandler handler = new QueryUtil().getHandler(); + private final BinaryElementSerializer serializer = new BinaryElementSerializer(); + + @Override + public void init(Object... objects) { + this.graph = (String) objects[0]; + this.table = (String) objects[1]; + this.properties = QueryUtil.fromStringBytes((List) objects[2]); + } + + @Override + public PipelineResult handle(PipelineResult result) { + if (result == null){ + return null; + } + + if (result.getResultType() == PipelineResultType.HG_ELEMENT) { + var element = result.getElement(); + var code = PartitionUtils.calcHashcode(BinaryElementSerializer.ownerId(element).asBytes()); + + for (Id property : properties) { + // 构建 key + var key = getOlapKey(property, element.id()); + var values = handler.doGet(this.graph, code, OLAP_TABLE, key); + if (values != null) { + var column = BackendColumn.of(key, values); + QueryUtil.parseOlap(column, (BaseVertex) element); + } + } + } else if (result.getResultType() == PipelineResultType.BACKEND_COLUMN) { + var column = result.getColumn(); + try { + var vertexOnlyId = + serializer.parseVertex(null, BackendColumn.of(column.name, null), null); + var code = PartitionUtils.calcHashcode( + BinaryElementSerializer.ownerId(vertexOnlyId).asBytes()); + // todo: 等 structure 改成 byte[] 操作的 + var list = new ArrayList(); + for (Id property : properties) { + var key = getOlapKey(property, vertexOnlyId.id()); + var values = handler.doGet(this.graph, code, OLAP_TABLE, key); + if (values != null) { + list.add(BackendColumn.of(key, values)); + } + } + var vertex = + QueryUtil.combineColumn(BackendColumn.of(column.name, column.value), list); + result.setColumn(RocksDBSession.BackendColumn.of(vertex.name, vertex.value)); + } catch (Exception e) { + log.error("parse olap error, graph: {}, table : {}", graph, table, e); + return null; + } + } + return result; + } + + private byte[] getOlapKey(Id propertyId, Id vertexId) { + BytesBuffer bufferName = BytesBuffer.allocate(1 + propertyId.length() + 1 + vertexId.length()); + bufferName.writeId(propertyId); + return bufferName.writeId(vertexId).bytes(); + } + + @Override + public String getName() { + return "OLAP_STAGE"; + } + + @Override + public void close() { + this.properties.clear(); + } +} diff --git a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/query/stages/OrderByStage.java b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/query/stages/OrderByStage.java new file mode 100644 index 0000000000..56ddb2c582 --- /dev/null +++ b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/query/stages/OrderByStage.java @@ -0,0 +1,146 @@ +/* + * 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.store.node.grpc.query.stages; + +import org.apache.hugegraph.id.Id; +import org.apache.hugegraph.store.business.itrv2.TypeTransIterator; +import org.apache.hugegraph.store.business.itrv2.io.SortShuffleSerializer; +import org.apache.hugegraph.store.node.grpc.query.MultiKeyComparator; +import org.apache.hugegraph.store.node.grpc.query.QueryStage; +import org.apache.hugegraph.store.node.grpc.query.QueryUtil; +import org.apache.hugegraph.store.node.grpc.query.model.PipelineResult; +import org.apache.hugegraph.store.node.grpc.query.model.PipelineResultType; +import org.apache.hugegraph.store.query.BaseElementComparator; +import org.apache.hugegraph.store.util.MultiKv; +import org.apache.hugegraph.store.util.SortShuffle; +import org.apache.hugegraph.structure.BaseElement; +import com.google.protobuf.ByteString; +import lombok.extern.slf4j.Slf4j; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +/** + * 排序 + */ +@Slf4j +public class OrderByStage implements QueryStage { + private SortShuffle sortShuffle; + + private Iterator iterator; + + private boolean isAsc; + + private PipelineResultType resultType = PipelineResultType.HG_ELEMENT; + + @Override + public void init(Object... objects) { + var orderBys = QueryUtil.fromStringBytes((List) objects[0]); + var groupBys = QueryUtil.fromStringBytes((List) objects[1]); + this.isAsc = (boolean) objects[3]; + + // agg + if ((Boolean) objects[2]) { + if (orderBys == null) { + sortShuffle = new SortShuffle<>(MultiKv::compareTo, SortShuffleSerializer.ofMultiKvSerializer()); + } else { + List orders = new ArrayList<>(); + for (Id id : orderBys) { + orders.add(groupBys.indexOf(id)); + } + sortShuffle = new SortShuffle<>(new MultiKeyComparator(orders), + SortShuffleSerializer.ofMultiKvSerializer()); + } + resultType = PipelineResultType.MKV; + } else { + sortShuffle = new SortShuffle<>(new BaseElementComparator(orderBys, this.isAsc), + SortShuffleSerializer.ofBaseElementSerializer()); + resultType = PipelineResultType.HG_ELEMENT; + } + + } + + @Override + public boolean isIterator() { + return true; + } + + @Override + public Iterator handleIterator(PipelineResult result) { + if (result == null) { + return null; + } + if (! result.isEmpty()) { + try { + if (result.getResultType() == PipelineResultType.MKV) { + sortShuffle.append(result.getKv()); + } else if (result.getResultType() == PipelineResultType.HG_ELEMENT) { + sortShuffle.append(result.getElement()); + } + return null; + } catch (Exception e) { + log.info("GROUP_BY_STAGE, append: ", e); + } + } else { + // last empty flag + try { + sortShuffle.finish(); + iterator = sortShuffle.getIterator(); + } catch (Exception e) { + log.error("GROUP_BY_STAGE:", e); + } + } + + return new TypeTransIterator(new Iterator<>() { + + private boolean closeFlag = false; + @Override + public boolean hasNext() { + var ret = iterator.hasNext(); + if (!ret) { + sortShuffle.close(); + // sort shuffle close,会 clear list,造成 size 和 cursor 不一致返回 true + // 仅仅针对 小数据量不使用 file 的情况 + closeFlag = true; + } + return ret && !closeFlag; + } + + @Override + public PipelineResult next() { + if (resultType == PipelineResultType.HG_ELEMENT) { + return new PipelineResult((BaseElement) iterator.next()); + } else { + return new PipelineResult((MultiKv) iterator.next()); + } + } + }, r -> r, () -> PipelineResult.EMPTY).toIterator(); + + } + + @Override + public String getName() { + return "ORDER_BY_STAGE"; + } + + @Override + public void close() { + this.sortShuffle.close(); + } +} diff --git a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/query/stages/ProjectionStage.java b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/query/stages/ProjectionStage.java new file mode 100644 index 0000000000..d1f057ad80 --- /dev/null +++ b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/query/stages/ProjectionStage.java @@ -0,0 +1,76 @@ +/* + * 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.store.node.grpc.query.stages; + +import org.apache.hugegraph.id.Id; +import org.apache.hugegraph.store.node.grpc.query.QueryStage; +import org.apache.hugegraph.store.node.grpc.query.QueryUtil; +import org.apache.hugegraph.store.node.grpc.query.model.PipelineResult; +import org.apache.hugegraph.store.node.grpc.query.model.PipelineResultType; +import com.google.protobuf.ByteString; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; + + +/** + * 剪裁 + */ +public class ProjectionStage implements QueryStage { + private Set propertySet; + + private boolean removeAllProperty; + + @Override + public void init(Object... objects) { + this.propertySet = new HashSet<>(QueryUtil.fromStringBytes((List) objects[0])); + this.removeAllProperty = (Boolean) objects[1]; + } + + @Override + public PipelineResult handle(PipelineResult result) { + if (result == null) { + return null; + } + + if (result.getResultType() == PipelineResultType.HG_ELEMENT) { + var element = result.getElement(); + for (var id : element.getProperties().entrySet()){ + if (! this.propertySet.contains(id.getKey()) || this.removeAllProperty) { + element.removeProperty(id.getKey()); + } + } + return result; + } else if (result.getResultType() == PipelineResultType.BACKEND_COLUMN && this.removeAllProperty){ + var column = result.getColumn(); + column.value = new byte[0]; + } + return result; + } + + @Override + public String getName() { + return "PROJECTION_STAGE"; + } + + @Override + public void close() { + this.propertySet.clear(); + } +} diff --git a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/query/stages/SampleStage.java b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/query/stages/SampleStage.java new file mode 100644 index 0000000000..d391efe3e9 --- /dev/null +++ b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/query/stages/SampleStage.java @@ -0,0 +1,54 @@ +/* + * 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.store.node.grpc.query.stages; + +import org.apache.hugegraph.store.node.grpc.query.QueryStage; +import org.apache.hugegraph.store.node.grpc.query.model.PipelineResult; + +import java.util.Objects; +import java.util.Random; + +/** + * 抽样 + */ +public class SampleStage implements QueryStage { + + private double factor; + + private Random rand; + + @Override + public void init(Object... objects) { + factor = (double) objects[0]; + rand = new Random(System.currentTimeMillis()); + } + + @Override + public PipelineResult handle(PipelineResult result) { + if (Objects.equals(result, PipelineResult.EMPTY) || rand.nextDouble() <= this.factor) { + return result; + } + + return null; + } + + @Override + public String getName() { + return "SAMPLE_STAGE"; + } +} diff --git a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/query/stages/SimpleCountStage.java b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/query/stages/SimpleCountStage.java new file mode 100644 index 0000000000..e7956c9451 --- /dev/null +++ b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/query/stages/SimpleCountStage.java @@ -0,0 +1,63 @@ +/* + * 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.store.node.grpc.query.stages; + +import org.apache.hugegraph.store.node.grpc.query.QueryStage; +import org.apache.hugegraph.store.node.grpc.query.model.PipelineResult; +import org.apache.hugegraph.store.util.MultiKv; + +import java.util.ArrayList; +import java.util.List; + +import static org.apache.hugegraph.store.node.grpc.query.QueryUtil.EMPTY_AGG_KEY; + +/** + * 简单的count计数 + */ +public class SimpleCountStage implements QueryStage { + + private int aggCount = 0; + + @Override + public void init(Object... objects) { + this.aggCount = (int) objects[0]; + } + + @Override + public PipelineResult handle(PipelineResult result) { + if (result.isEmpty()) { + return result; + } + + MultiKv multiKv = new MultiKv(EMPTY_AGG_KEY, createArray(aggCount)); + return new PipelineResult(multiKv); + } + + @Override + public String getName() { + return "SIMPLE_COUNT_STAGE"; + } + + public List createArray(int count) { + List list = new ArrayList<>(); + for (int i = 0; i < count; i++) { + list.add(0L); + } + return list; + } +} diff --git a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/query/stages/StopStage.java b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/query/stages/StopStage.java new file mode 100644 index 0000000000..f64ad0efde --- /dev/null +++ b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/query/stages/StopStage.java @@ -0,0 +1,40 @@ +/* + * 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.store.node.grpc.query.stages; + +import org.apache.hugegraph.store.node.grpc.query.QueryStage; +import org.apache.hugegraph.store.node.grpc.query.model.PipelineResult; + +/** + * sample = 0 的特殊阶段 + */ +public class StopStage implements QueryStage { + + @Override + public PipelineResult handle(PipelineResult result) { + if (result.isEmpty()) { + return result; + } + return null; + } + + @Override + public String getName() { + return "STOP_STAGE"; + } +} diff --git a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/query/stages/TopStage.java b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/query/stages/TopStage.java new file mode 100644 index 0000000000..b25c54b995 --- /dev/null +++ b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/query/stages/TopStage.java @@ -0,0 +1,110 @@ +/* + * 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.store.node.grpc.query.stages; + +import org.apache.hugegraph.store.business.itrv2.TypeTransIterator; +import org.apache.hugegraph.store.node.grpc.query.QueryStage; +import org.apache.hugegraph.store.node.grpc.query.QueryUtil; +import org.apache.hugegraph.store.node.grpc.query.model.PipelineResult; +import org.apache.hugegraph.store.node.grpc.query.model.PipelineResultType; +import org.apache.hugegraph.store.query.BaseElementComparator; +import org.apache.hugegraph.structure.BaseElement; +import com.google.protobuf.ByteString; + +import java.util.Iterator; +import java.util.List; +import java.util.PriorityQueue; +import java.util.concurrent.PriorityBlockingQueue; + +public class TopStage implements QueryStage { + private PriorityBlockingQueue queue; + + private BaseElementComparator comparator; + private boolean isAsc; + + private int limit; + + // todo: check concurrency + @Override + public void init(Object... objects) { + this.limit = (int) objects[0]; + this.isAsc = (boolean) objects[2]; + + // 需要构建一个相反的堆 + this.comparator = new BaseElementComparator(QueryUtil.fromStringBytes((List) objects[1]), + !isAsc); + this.queue = new PriorityBlockingQueue<>(limit, this.comparator); + } + + @Override + public boolean isIterator() { + return true; + } + + @Override + public Iterator handleIterator(PipelineResult result) { + if (result == null) { + return null; + } + + if (result.isEmpty()) { + + this.comparator.reverseOrder(); + var reverseQueue = new PriorityQueue<>(this.comparator); + reverseQueue.addAll(this.queue); + queue.clear(); + + return new TypeTransIterator<>(new Iterator() { + @Override + public boolean hasNext() { + return reverseQueue.size() > 0; + } + + @Override + public BaseElement next() { + return reverseQueue.poll(); + } + }, PipelineResult::new, () -> PipelineResult.EMPTY).toIterator(); + } + + if (result.getResultType() == PipelineResultType.HG_ELEMENT) { + if (this.queue.size() < this.limit) { + this.queue.add(result.getElement()); + } else { + var top = this.queue.peek(); + var element = result.getElement(); + if (this.comparator.compare(element, top) > 0) { + this.queue.poll(); + this.queue.add(result.getElement()); + } + } + } + + return null; + } + + @Override + public String getName() { + return "TOP_STAGE"; + } + + @Override + public void close() { + this.queue.clear(); + } +} diff --git a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/query/stages/TtlCheckStage.java b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/query/stages/TtlCheckStage.java new file mode 100644 index 0000000000..6888337cf9 --- /dev/null +++ b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/query/stages/TtlCheckStage.java @@ -0,0 +1,65 @@ +/* + * 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.store.node.grpc.query.stages; + +import org.apache.hugegraph.serializer.DirectBinarySerializer; +import org.apache.hugegraph.store.node.grpc.query.QueryStage; +import org.apache.hugegraph.store.node.grpc.query.model.PipelineResult; +import org.apache.hugegraph.store.node.grpc.query.model.PipelineResultType; + +import lombok.extern.slf4j.Slf4j; + +/** + * check element ttl + */ +@Slf4j +public class TtlCheckStage implements QueryStage { + private boolean isVertex; + + private DirectBinarySerializer serializer = new DirectBinarySerializer(); + private long now ; + + @Override + public void init(Object... objects) { + this.isVertex = (boolean) objects[0]; + now = System.currentTimeMillis(); + } + + @Override + public PipelineResult handle(PipelineResult result) { + if (result.getResultType() == PipelineResultType.BACKEND_COLUMN) { + var col = result.getColumn(); + try { + var element = isVertex ? serializer.parseVertex(col.name, col.value) : + serializer.parseEdge(col.name, col.value); + if (element.expiredTime() > 0 && element.expiredTime() < now) { + return null; + } + } catch (Exception e) { + log.error("parse element error", e); + return null; + } + } + return result; + } + + @Override + public String getName() { + return "TTL_CHECK_STAGE"; + } +} From 68aae40a81e3acc1d3bb8c58792dd7fd1e4b4259 Mon Sep 17 00:00:00 2001 From: JisoLya <523420504@qq.com> Date: Wed, 13 Aug 2025 10:11:46 +0800 Subject: [PATCH 16/35] fix: add missing field in store-grpc --- hugegraph-store/hg-store-grpc/src/main/proto/graphpb.proto | 1 + 1 file changed, 1 insertion(+) diff --git a/hugegraph-store/hg-store-grpc/src/main/proto/graphpb.proto b/hugegraph-store/hg-store-grpc/src/main/proto/graphpb.proto index 6e9d16d2eb..db2bb82bbb 100644 --- a/hugegraph-store/hg-store-grpc/src/main/proto/graphpb.proto +++ b/hugegraph-store/hg-store-grpc/src/main/proto/graphpb.proto @@ -45,6 +45,7 @@ message ScanPartitionRequest{ bytes position = 10; // Return condition repeated int64 properties = 11; + int32 batchSize = 12; } From 788ba358c5c37b3c6e54c20de140d9218d7327a1 Mon Sep 17 00:00:00 2001 From: JisoLya <523420504@qq.com> Date: Wed, 13 Aug 2025 10:59:18 +0800 Subject: [PATCH 17/35] refactor: update listener folder --- .../node/listener/ContextClosedListener.java | 84 +++++++++++++++++-- .../node/listener/PlaceHolderListener.java | 69 +++++++++++++++ 2 files changed, 145 insertions(+), 8 deletions(-) create mode 100644 hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/listener/PlaceHolderListener.java diff --git a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/listener/ContextClosedListener.java b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/listener/ContextClosedListener.java index e990acfe6f..133d8894d6 100644 --- a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/listener/ContextClosedListener.java +++ b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/listener/ContextClosedListener.java @@ -17,13 +17,20 @@ package org.apache.hugegraph.store.node.listener; +import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ThreadPoolExecutor; -import org.apache.hugegraph.store.node.grpc.HgStoreStreamImpl; +import com.alipay.sofa.jraft.Status; +import com.alipay.sofa.jraft.entity.PeerId; + +import org.apache.hugegraph.store.HgStoreEngine; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationListener; import org.springframework.context.event.ContextClosedEvent; +import org.apache.hugegraph.store.node.grpc.HgStoreStreamImpl; +import org.apache.hugegraph.store.node.task.TTLCleaner; + import lombok.extern.slf4j.Slf4j; @Slf4j @@ -31,23 +38,84 @@ public class ContextClosedListener implements ApplicationListener { + try { + Status status = + leader.getRaftNode().transferLeadershipTo(PeerId.ANY_PEER); + log.info("partition {} transfer leader status: {}", + leader.getGroupId(), status); + } catch (Exception e) { + log.info("partition {} transfer leader error: ", + leader.getGroupId(), e); + } + }); + + HgStoreEngine.getInstance().getPartitionEngines() + .forEach((integer, partitionEngine) -> partitionEngine.getRaftNode() + .disableVote()); + } catch (Exception e) { + log.error("transfer leader failed: " + e.getMessage()); + } + } } diff --git a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/listener/PlaceHolderListener.java b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/listener/PlaceHolderListener.java new file mode 100644 index 0000000000..1ddbf69a2e --- /dev/null +++ b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/listener/PlaceHolderListener.java @@ -0,0 +1,69 @@ +/* + * 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.store.node.listener; + +import java.io.File; +import java.io.RandomAccessFile; +import java.util.Arrays; + +import org.apache.commons.io.FileUtils; +import org.apache.commons.lang.StringUtils; +import org.springframework.boot.context.event.ApplicationReadyEvent; +import org.springframework.context.ApplicationListener; + +import org.apache.hugegraph.store.node.AppConfig; +import org.apache.hugegraph.store.options.HgStoreEngineOptions; + +import lombok.extern.slf4j.Slf4j; + +/** + * @date 2023/7/17 + **/ +@Slf4j +public class PlaceHolderListener implements ApplicationListener { + + @Override + public void onApplicationEvent(ApplicationReadyEvent event) { + try { + AppConfig config = event.getApplicationContext().getBean(AppConfig.class); + String dataPath = config.getDataPath(); + String[] paths = dataPath.split(","); + Integer size = config.getPlaceholderSize(); + Arrays.stream(paths).parallel().forEach(path->{ + if (!StringUtils.isEmpty(path)) { + File ph = new File(path + "/" + HgStoreEngineOptions.PLACE_HOLDER_PREFIX); + if (!ph.exists() && size > 0) { + try { + FileUtils.touch(ph); + byte[] tmp = new byte[(int) FileUtils.ONE_GB]; + for (int j = 0; j < size; j++) { + FileUtils.writeByteArrayToFile(ph, tmp, true); + } + RandomAccessFile raf = new RandomAccessFile(ph, "rw"); + raf.setLength(size * FileUtils.ONE_GB); + } catch (Exception e) { + log.info("creating placeholder file got exception:", e); + } + } + } + }); + } catch (Exception e) { + log.error("create placeholder file with error:", e); + } + } +} From 971aa9c435d2c1171f6d8914220f7e024d9649fa Mon Sep 17 00:00:00 2001 From: JisoLya <523420504@qq.com> Date: Wed, 13 Aug 2025 10:59:30 +0800 Subject: [PATCH 18/35] refactor: update scan folder --- .../node/grpc/scan/ScanResponseObserver.java | 61 ++++++++++++++++--- 1 file changed, 54 insertions(+), 7 deletions(-) diff --git a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/scan/ScanResponseObserver.java b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/scan/ScanResponseObserver.java index dc57dae368..69b44844f7 100644 --- a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/scan/ScanResponseObserver.java +++ b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/scan/ScanResponseObserver.java @@ -38,6 +38,7 @@ import com.google.protobuf.Descriptors; +import io.grpc.Status; import io.grpc.stub.StreamObserver; import lombok.extern.slf4j.Slf4j; @@ -45,7 +46,7 @@ public class ScanResponseObserver implements StreamObserver { - private static final int BATCH_SIZE = 100000; + private static int batchSize = 100000; private static final int MAX_PAGE = 8; // private static final Error ok = Error.newBuilder().setType(ErrorType.OK).build(); private static final ResponseHeader okHeader = @@ -92,6 +93,23 @@ public ScanResponseObserver(StreamObserver sender, this.executor = executor; } + private void sendException(Exception e) { + if (sendTask != null) { + sendTask.cancel(true); + } + try { + packages.clear(); + } catch (Exception e1) { + + } + try { + iter.close(); + } catch (Exception e1) { + + } + sender.onError(Status.NOT_FOUND.withDescription(e.getMessage()).asException()); + } + private boolean readCondition() { return packages.remainingCapacity() != 0 && !readOver.get(); } @@ -148,12 +166,17 @@ public void onNext(ScanPartitionRequest scanReq) { if (scanReq.hasScanRequest() && !scanReq.hasReplyRequest()) { this.scanReq = scanReq; Request request = scanReq.getScanRequest(); + batchSize = request.getBatchSize() > 0 ? request.getBatchSize() : 100000; long rl = request.getLimit(); leftCount = rl > 0 ? rl : Long.MAX_VALUE; iter = handler.scan(scanReq); if (!iter.hasNext()) { - close(); - sender.onCompleted(); + if (iter.getStopCause() != null) { + sendException(iter.getStopCause()); + } else { + close(); + sender.onCompleted(); + } } else { readTask = executor.submit(rr); } @@ -184,9 +207,20 @@ private void close() { readTask.cancel(true); } readOver.set(true); - iter.close(); + try { + packages.clear(); + } catch (Exception e) { + + } + try { + iter.close(); + } catch (Exception e) { + + } } catch (Exception e) { log.warn("on Complete with error:", e); + } finally { + } } @@ -200,22 +234,25 @@ public void run() { Request r = scanReq.getScanRequest(); ScanType t = r.getScanType(); boolean isVertex = t.equals(ScanType.SCAN_VERTEX); - ArrayList data = new ArrayList<>(BATCH_SIZE); + ArrayList data = new ArrayList<>(batchSize); int count = 0; while (iter.hasNext() && leftCount > -1) { count++; leftCount--; T next = (T) iter.next(); data.add(next); - if (count >= BATCH_SIZE) { + if (count >= batchSize) { offer(data, isVertex); // data.clear(); break; } } + if (iter.getStopCause() != null){ + throw iter.getStopCause(); + } if (!(iter.hasNext() && leftCount > -1)) { if (data.size() > 0 && - data.size() < BATCH_SIZE) { + data.size() < batchSize) { offer(data, isVertex); } readOver.set(true); @@ -239,6 +276,16 @@ public void run() { ScanResponse response; try { if (readOver.get()) { + if (iter.getStopCause() != null) { + try { + packages.clear(); + iter.close(); + } catch (Exception e) { + + } + sender.onError(iter.getStopCause()); + return; + } if ((response = packages.poll()) == null) { sender.onCompleted(); } else { From 1ed56913c7f1055d91087313e4311f93772905af Mon Sep 17 00:00:00 2001 From: JisoLya <523420504@qq.com> Date: Wed, 13 Aug 2025 10:59:46 +0800 Subject: [PATCH 19/35] refactor: update metrics folder --- .../store/node/metrics/GRpcExMetrics.java | 4 +- .../store/node/metrics/JRaftMetrics.java | 93 ++++++----- .../store/node/metrics/MetricsConfig.java | 17 ++ .../store/node/metrics/ProcFileHandler.java | 1 + .../store/node/metrics/ProcfsEntry.java | 39 ++--- .../store/node/metrics/ProcfsMetrics.java | 19 +-- .../store/node/metrics/ProcfsReader.java | 142 ++++++++++++++++ .../store/node/metrics/ProcfsSmaps.java | 110 +++++++++++++ .../store/node/metrics/SystemMemoryStats.java | 155 +++++++++--------- 9 files changed, 428 insertions(+), 152 deletions(-) create mode 100644 hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/metrics/ProcfsReader.java create mode 100644 hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/metrics/ProcfsSmaps.java diff --git a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/metrics/GRpcExMetrics.java b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/metrics/GRpcExMetrics.java index 828b324b8c..e7818994a1 100644 --- a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/metrics/GRpcExMetrics.java +++ b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/metrics/GRpcExMetrics.java @@ -19,7 +19,7 @@ import java.util.concurrent.ThreadPoolExecutor; -import org.apache.hugegraph.store.node.grpc.GRpcServerConfig; +import org.apache.hugegraph.store.consts.PoolNames; import org.apache.hugegraph.store.node.util.HgExecutorUtil; import io.micrometer.core.instrument.Gauge; @@ -72,7 +72,7 @@ private static class ExecutorWrapper { void init() { if (this.pool == null) { - pool = HgExecutorUtil.getThreadPoolExecutor(GRpcServerConfig.EXECUTOR_NAME); + pool = HgExecutorUtil.getThreadPoolExecutor(PoolNames.GRPC); } } diff --git a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/metrics/JRaftMetrics.java b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/metrics/JRaftMetrics.java index d5ca11b3f5..2dc151f6f4 100644 --- a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/metrics/JRaftMetrics.java +++ b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/metrics/JRaftMetrics.java @@ -127,6 +127,31 @@ private static void registerNodeMetrics() { } + private static class HistogramWrapper { + + private com.codahale.metrics.Histogram histogram; + + private Snapshot snapshot; + private long ts = System.currentTimeMillis(); + + HistogramWrapper(com.codahale.metrics.Histogram histogram) { + this.histogram = histogram; + this.snapshot = this.histogram.getSnapshot(); + } + + Snapshot getSnapshot() { + if (System.currentTimeMillis() - this.ts > 30_000) { + this.snapshot = this.histogram.getSnapshot(); + this.ts = System.currentTimeMillis(); + } + return this.snapshot; + } + } + + private static HistogramWrapper toWrapper(com.codahale.metrics.Histogram histogram) { + return new HistogramWrapper(histogram); + } + private static String refineMetrics(String name, List tags) { if (name == null || name.isEmpty()) { return name; @@ -168,33 +193,35 @@ private static void registerHistogram(String group, String name, String baseName = PREFIX + "." + name.toLowerCase(); - Gauge.builder(baseName + ".median", histogram, h -> h.getSnapshot().getMedian()) + HistogramWrapper wrapper = toWrapper(histogram); + + Gauge.builder(baseName + ".median", wrapper, (d) -> d.getSnapshot().getMedian()) .tags(tags).register(registry); - Gauge.builder(baseName + ".min", histogram, h -> h.getSnapshot().getMin()) + Gauge.builder(baseName + ".min", wrapper, (d) -> d.getSnapshot().getMin()) .tags(tags).register(registry); - Gauge.builder(baseName + ".max", histogram, h -> h.getSnapshot().getMax()) + Gauge.builder(baseName + ".max", wrapper, (d) -> d.getSnapshot().getMax()) .tags(tags).register(registry); - Gauge.builder(baseName + ".mean", histogram, h -> h.getSnapshot().getMean()) + Gauge.builder(baseName + ".mean", wrapper, (d) -> d.getSnapshot().getMean()) .tags(tags).register(registry); baseName = baseName + ".summary"; - Gauge.builder(baseName, histogram, h -> h.getSnapshot().getMedian()) + Gauge.builder(baseName, wrapper, (d) -> d.getSnapshot().getMedian()) .tags(tags).tag(LABELS, LABEL_50).register(registry); - Gauge.builder(baseName, histogram, h -> h.getSnapshot().get75thPercentile()) + Gauge.builder(baseName, wrapper, (d) -> d.getSnapshot().get75thPercentile()) .tags(tags).tag(LABELS, LABEL_75).register(registry); - Gauge.builder(baseName, histogram, h -> h.getSnapshot().get95thPercentile()) + Gauge.builder(baseName, wrapper, (d) -> d.getSnapshot().get95thPercentile()) .tags(tags).tag(LABELS, LABEL_95).register(registry); - Gauge.builder(baseName, histogram, h -> h.getSnapshot().get98thPercentile()) + Gauge.builder(baseName, wrapper, (d) -> d.getSnapshot().get98thPercentile()) .tags(tags).tag(LABELS, LABEL_98).register(registry); - Gauge.builder(baseName, histogram, h -> h.getSnapshot().get99thPercentile()) + Gauge.builder(baseName, wrapper, (d) -> d.getSnapshot().get99thPercentile()) .tags(tags).tag(LABELS, LABEL_99).register(registry); - Gauge.builder(baseName, histogram, h -> h.getSnapshot().get999thPercentile()) + Gauge.builder(baseName, wrapper, (d) -> d.getSnapshot().get999thPercentile()) .tags(tags).tag(LABELS, LABEL_999).register(registry); - Gauge.builder(baseName + ".sum", histogram, - h -> Arrays.stream(h.getSnapshot().getValues()).sum()) + Gauge.builder(baseName + ".sum", wrapper, + (d) -> Arrays.stream(d.getSnapshot().getValues()).sum()) .tags(tags).register(registry); - Gauge.builder(baseName + ".count", histogram, h -> h.getSnapshot().size()) + Gauge.builder(baseName + ".count", wrapper, (d) -> d.getSnapshot().size()) .tags(tags).register(registry); } @@ -208,36 +235,18 @@ private static void registerTimer(String group, String name, com.codahale.metric String baseName = PREFIX + "." + name.toLowerCase(); - Gauge.builder(baseName + ".count", timer, t -> t.getCount()) - .tags(tags).register(registry); - Gauge.builder(baseName + ".min", timer, t -> t.getSnapshot().getMin()) - .tags(tags).register(registry); - Gauge.builder(baseName + ".max", timer, t -> t.getSnapshot().getMax()) - .tags(tags).register(registry); - Gauge.builder(baseName + ".mean", timer, t -> t.getSnapshot().getMean()) - .tags(tags).register(registry); - Gauge.builder(baseName + ".stddev", timer, t -> t.getSnapshot().getStdDev()) - .tags(tags).register(registry); - Gauge.builder(baseName + ".p50", timer, t -> t.getSnapshot().getMedian()) - .tags(tags).register(registry); - Gauge.builder(baseName + ".p75", timer, t -> t.getSnapshot().get75thPercentile()) - .tags(tags).register(registry); - Gauge.builder(baseName + ".p95", timer, t -> t.getSnapshot().get95thPercentile()) - .tags(tags).register(registry); - Gauge.builder(baseName + ".p98", timer, t -> t.getSnapshot().get98thPercentile()) - .tags(tags).register(registry); - Gauge.builder(baseName + ".p99", timer, t -> t.getSnapshot().get99thPercentile()) - .tags(tags).register(registry); - Gauge.builder(baseName + ".p999", timer, t -> t.getSnapshot().get999thPercentile()) - .tags(tags).register(registry); - Gauge.builder(baseName + ".m1_rate", timer, t -> t.getOneMinuteRate()) - .tags(tags).register(registry); - Gauge.builder(baseName + ".m5_rate", timer, t -> t.getFiveMinuteRate()) - .tags(tags).register(registry); - Gauge.builder(baseName + ".m15_rate", timer, t -> t.getFifteenMinuteRate()) - .tags(tags).register(registry); - Gauge.builder(baseName + ".mean_rate", timer, t -> t.getMeanRate()) + Gauge.builder(baseName + ".count", timer, Timer::getCount) .tags(tags).register(registry); + + Gauge.builder(baseName + ".timer", timer, Timer::getCount) + .tags(tags).tag("rate", "1m").register(registry); + Gauge.builder(baseName + ".timer", timer, Timer::getCount) + .tags(tags).tag("rate", "5m").register(registry); + Gauge.builder(baseName + ".timer", timer, Timer::getCount) + .tags(tags).tag("rate", "15m").register(registry); + Gauge.builder(baseName + ".timer", timer, Timer::getCount) + .tags(tags).tag("rate", "mean").register(registry); + } private static void registerMeter(String group, String name, com.codahale.metrics.Meter meter) { diff --git a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/metrics/MetricsConfig.java b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/metrics/MetricsConfig.java index 7062ee7eb8..dd08cb5e58 100644 --- a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/metrics/MetricsConfig.java +++ b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/metrics/MetricsConfig.java @@ -17,11 +17,17 @@ package org.apache.hugegraph.store.node.metrics; +import java.util.concurrent.ExecutorService; + +import org.apache.hugegraph.store.node.grpc.HgStoreStreamImpl; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.actuate.autoconfigure.metrics.MeterRegistryCustomizer; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import io.micrometer.core.instrument.MeterRegistry; +import io.micrometer.core.instrument.Tags; +import io.micrometer.core.instrument.binder.jvm.ExecutorServiceMetrics; /** * 2021/11/24 @@ -29,6 +35,9 @@ @Configuration public class MetricsConfig { + @Autowired + HgStoreStreamImpl stream; + @Bean public MeterRegistryCustomizer metricsCommonTags() { return (registry) -> registry.config().commonTags("hg", "store"); @@ -37,6 +46,7 @@ public MeterRegistryCustomizer metricsCommonTags() { @Bean public MeterRegistryCustomizer registerMeters() { return (registry) -> { + scanService(registry); StoreMetrics.init(registry); RocksDBMetrics.init(registry); JRaftMetrics.init(registry); @@ -45,4 +55,11 @@ public MeterRegistryCustomizer registerMeters() { }; } + @Bean + public ExecutorService scanService(MeterRegistry registry) { + ExecutorService service = + ExecutorServiceMetrics.monitor(registry, stream.getExecutor(), "scan", + Tags.of("hg", "store")); + return service; + } } diff --git a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/metrics/ProcFileHandler.java b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/metrics/ProcFileHandler.java index 1b0e6e22c5..bb8abdcfc4 100644 --- a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/metrics/ProcFileHandler.java +++ b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/metrics/ProcFileHandler.java @@ -30,6 +30,7 @@ import lombok.Getter; +@Deprecated class ProcFileHandler { // Cache duration in milliseconds diff --git a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/metrics/ProcfsEntry.java b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/metrics/ProcfsEntry.java index 7d67ab0022..6990d9032c 100644 --- a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/metrics/ProcfsEntry.java +++ b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/metrics/ProcfsEntry.java @@ -16,8 +16,6 @@ */ package org.apache.hugegraph.store.node.metrics; -import static org.apache.hugegraph.store.node.metrics.ProcFileHandler.ReadResult; - import java.io.IOException; import java.util.Collection; import java.util.Objects; @@ -25,39 +23,38 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -abstract class ProcfsRecord { +abstract class ProcfsEntry { - private static final Logger logger = LoggerFactory.getLogger(ProcfsRecord.class); + private static final Logger logger = LoggerFactory.getLogger(ProcfsEntry.class); - private final Object syncLock = new Object(); + private final Object lock = new Object(); - private final ProcFileHandler fileReader; + private final ProcfsReader reader; - private long lastProcessedTime = -1; + private long lastHandle = -1; - protected ProcfsRecord(ProcFileHandler fileReader) { - this.fileReader = Objects.requireNonNull(fileReader); + protected ProcfsEntry(ProcfsReader reader) { + this.reader = Objects.requireNonNull(reader); } - protected final void gatherData() { - synchronized (syncLock) { + protected final void collect() { + synchronized (lock) { try { - final ReadResult readResult = fileReader.readFile(); - if (readResult != null && - (lastProcessedTime == -1 || lastProcessedTime != readResult.getReadTime())) { - clear(); - process(readResult.getLines()); - lastProcessedTime = readResult.getReadTime(); + final ProcfsReader.ReadResult result = reader.read(); + if (result != null && (lastHandle == -1 || lastHandle != result.getReadTime())) { + reset(); + handle(result.getLines()); + lastHandle = result.getReadTime(); } } catch (IOException e) { - clear(); - logger.warn("Failed reading '" + fileReader.getFilePath() + "'!", e); + reset(); + logger.warn("Failed reading '" + reader.getEntryPath() + "'!", e); } } } - protected abstract void clear(); + protected abstract void reset(); - protected abstract void process(Collection lines); + protected abstract void handle(Collection lines); } diff --git a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/metrics/ProcfsMetrics.java b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/metrics/ProcfsMetrics.java index c5a649e62b..2e53df5804 100644 --- a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/metrics/ProcfsMetrics.java +++ b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/metrics/ProcfsMetrics.java @@ -23,7 +23,7 @@ public class ProcfsMetrics { public final static String PREFIX = "process_memory"; - private final static SystemMemoryStats smaps = new SystemMemoryStats(); + private final static ProcfsSmaps smaps = new ProcfsSmaps(); private static MeterRegistry registry; private ProcfsMetrics() { @@ -43,19 +43,18 @@ private static void registerMeters() { private static void registerProcessGauge() { Gauge.builder(PREFIX + ".rss.bytes", - () -> smaps.getMetric(SystemMemoryStats.MetricKey.RSS)).register(registry); + () -> smaps.get(ProcfsSmaps.KEY.RSS)).register(registry); - Gauge.builder(PREFIX + ".pss.bytes", - () -> smaps.getMetric(SystemMemoryStats.MetricKey.PSS)).register(registry); + Gauge.builder(PREFIX + ".pss.bytes", () -> smaps.get(ProcfsSmaps.KEY.PSS)) + .register(registry); - Gauge.builder(PREFIX + ".vss.bytes", - () -> smaps.getMetric(SystemMemoryStats.MetricKey.VSS)).register(registry); + Gauge.builder(PREFIX + ".vss.bytes", () -> smaps.get(ProcfsSmaps.KEY.VSS)) + .register(registry); - Gauge.builder(PREFIX + ".swap.bytes", - () -> smaps.getMetric(SystemMemoryStats.MetricKey.SWAP)).register(registry); + Gauge.builder(PREFIX + ".swap.bytes", () -> smaps.get(ProcfsSmaps.KEY.SWAP)) + .register(registry); - Gauge.builder(PREFIX + ".swappss.bytes", - () -> smaps.getMetric(SystemMemoryStats.MetricKey.SWAPPSS)) + Gauge.builder(PREFIX + ".swappss.bytes", () -> smaps.get(ProcfsSmaps.KEY.SWAPPSS)) .register(registry); } diff --git a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/metrics/ProcfsReader.java b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/metrics/ProcfsReader.java new file mode 100644 index 0000000000..c1891dcefc --- /dev/null +++ b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/metrics/ProcfsReader.java @@ -0,0 +1,142 @@ +/* + * 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.store.node.metrics; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.*; + +class ProcfsReader { + + private static final Map instances = new HashMap<>(); + + private static final Object instancesLock = new Object(); + + private static final Map> data = new HashMap<>(); + + private static final Object dataLock = new Object(); + + private static final Path BASE = Paths.get("/proc", "self"); + + /* default */ static final long CACHE_DURATION_MS = 100; + + /* default */ long lastReadTime = -1; + + private final Path entryPath; + + private final boolean osSupport; + + private ProcfsReader(String entry) { + this(BASE, entry, false); + } + + /* default */ ProcfsReader(Path base, String entry) { + this(base, entry, true); + } + + private ProcfsReader(Path base, String entry, boolean forceOSSupport) { + Objects.requireNonNull(base); + Objects.requireNonNull(entry); + + this.entryPath = base.resolve(entry); + + this.osSupport = forceOSSupport + || System.getProperty("os.name").toLowerCase(Locale.ENGLISH).startsWith("linux"); + } + + /* default */ Path getEntryPath() { + return entryPath; + } + + /* default */ ReadResult read() throws IOException { + return read(currentTime()); + } + + /* default */ ReadResult read(long currentTimeMillis) throws IOException { + synchronized (dataLock) { + final Path key = getEntryPath().getFileName(); + + final ReadResult readResult; + if (lastReadTime == -1 || lastReadTime + CACHE_DURATION_MS < currentTimeMillis) { + final List lines = readPath(entryPath); + cacheResult(key, lines); + lastReadTime = currentTime(); + readResult = new ReadResult(lines, lastReadTime); + } else { + readResult = new ReadResult(data.get(key), lastReadTime); + } + return readResult; + } + } + + /* default */ List readPath(Path path) throws IOException { + Objects.requireNonNull(path); + + if (!osSupport) { + return Collections.emptyList(); + } + return Files.readAllLines(path); + } + + /* default */ void cacheResult(Path key, List lines) { + Objects.requireNonNull(key); + Objects.requireNonNull(lines); + + data.put(key, lines); + } + + /* default */ long currentTime() { + return System.currentTimeMillis(); + } + + /* default */ static ProcfsReader getInstance(String entry) { + Objects.requireNonNull(entry); + + synchronized (instancesLock) { + ProcfsReader reader = instances.get(entry); + if (reader == null) { + reader = new ProcfsReader(entry); + instances.put(entry, reader); + } + return reader; + } + } + + /* default */ static class ReadResult { + + private final List lines; + + private final long readTime; + + /* default */ ReadResult(List lines, long readTime) { + this.lines = Objects.requireNonNull(lines); + this.readTime = readTime; + } + + public long getReadTime() { + return readTime; + } + + public List getLines() { + return lines; + } + + } + +} diff --git a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/metrics/ProcfsSmaps.java b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/metrics/ProcfsSmaps.java new file mode 100644 index 0000000000..bf205a1830 --- /dev/null +++ b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/metrics/ProcfsSmaps.java @@ -0,0 +1,110 @@ +/* + * 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.store.node.metrics; + +import java.util.*; +import java.util.concurrent.atomic.AtomicLong; +import java.util.function.LongUnaryOperator; + +public class ProcfsSmaps extends ProcfsEntry { + + public enum KEY { + /** + * Virtual set size + */ + VSS, + /** + * Resident set size + */ + RSS, + /** + * Proportional set size + */ + PSS, + /** + * Paged out memory + */ + SWAP, + /** + * Paged out memory accounting shared pages. Since Linux 4.3. + */ + SWAPPSS + } + + private static final int KILOBYTE = 1024; + + private final Map values = new HashMap<>(); + + public ProcfsSmaps() { + super(ProcfsReader.getInstance("smaps")); + } + + /* default */ ProcfsSmaps(ProcfsReader reader) { + super(reader); + } + + @Override + protected void reset() { + EnumSet.allOf(KEY.class).forEach(key -> values.put(key, new AtomicLong(-1))); + } + + @Override + protected void handle(Collection lines) { + Objects.requireNonNull(lines); + + for (final String line : lines) { + if (line.startsWith("Size:")) { + inc(KEY.VSS, parseKiloBytes(line) * KILOBYTE); + } else if (line.startsWith("Rss:")) { + inc(KEY.RSS, parseKiloBytes(line) * KILOBYTE); + } else if (line.startsWith("Pss:")) { + inc(KEY.PSS, parseKiloBytes(line) * KILOBYTE); + } else if (line.startsWith("Swap:")) { + inc(KEY.SWAP, parseKiloBytes(line) * KILOBYTE); + } else if (line.startsWith("SwapPss:")) { + inc(KEY.SWAPPSS, parseKiloBytes(line) * KILOBYTE); + } + } + } + + public Long get(KEY key) { + Objects.requireNonNull(key); + + collect(); + return Long.valueOf(values.get(key).longValue()); + } + + private void inc(KEY key, long increment) { + Objects.requireNonNull(key); + + values.get(key).getAndUpdate(new LongUnaryOperator() { + + @Override + public long applyAsLong(long currentValue) { + return currentValue + increment + (currentValue == -1 ? 1 : 0); + } + + }); + } + + private static long parseKiloBytes(String line) { + Objects.requireNonNull(line); + + return Long.parseLong(line.split("\\s+")[1]); + } + +} diff --git a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/metrics/SystemMemoryStats.java b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/metrics/SystemMemoryStats.java index f008e99259..4f315f1ace 100644 --- a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/metrics/SystemMemoryStats.java +++ b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/metrics/SystemMemoryStats.java @@ -23,80 +23,81 @@ import java.util.Objects; import java.util.concurrent.atomic.AtomicLong; -public class SystemMemoryStats extends ProcfsRecord { - - private static final int KB = 1024; - private final Map metrics = new HashMap<>(); - - public SystemMemoryStats() { - super(ProcFileHandler.getInstance("smaps")); - } - - /* default */ SystemMemoryStats(ProcFileHandler reader) { - super(reader); - } - - private static long parseKilobytes(String line) { - Objects.requireNonNull(line); - return Long.parseLong(line.split("\\s+")[1]); - } - - @Override - protected void clear() { - EnumSet.allOf(MetricKey.class).forEach(key -> metrics.put(key, new AtomicLong(-1))); - } - - @Override - protected void process(Collection lines) { - Objects.requireNonNull(lines); - - for (final String line : lines) { - if (line.startsWith("Size:")) { - increment(MetricKey.VSS, parseKilobytes(line) * KB); - } else if (line.startsWith("Rss:")) { - increment(MetricKey.RSS, parseKilobytes(line) * KB); - } else if (line.startsWith("Pss:")) { - increment(MetricKey.PSS, parseKilobytes(line) * KB); - } else if (line.startsWith("Swap:")) { - increment(MetricKey.SWAP, parseKilobytes(line) * KB); - } else if (line.startsWith("SwapPss:")) { - increment(MetricKey.SWAPPSS, parseKilobytes(line) * KB); - } - } - } - - public Long getMetric(MetricKey key) { - Objects.requireNonNull(key); - clear(); - return metrics.get(key).longValue(); - } - - private void increment(MetricKey key, long increment) { - Objects.requireNonNull(key); - metrics.get(key).getAndUpdate(currentValue -> currentValue + increment + - (currentValue == -1 ? 1 : 0)); - } - - public enum MetricKey { - /** - * Virtual set size - */ - VSS, - /** - * Resident set size - */ - RSS, - /** - * Proportional set size - */ - PSS, - /** - * Paged out memory - */ - SWAP, - /** - * Paged out memory accounting shared pages. Since Linux 4.3. - */ - SWAPPSS - } -} +//@Deprecated +//public class SystemMemoryStats extends ProcfsRecord { +// +// private static final int KB = 1024; +// private final Map metrics = new HashMap<>(); +// +// public SystemMemoryStats() { +// super(ProcFileHandler.getInstance("smaps")); +// } +// +// /* default */ SystemMemoryStats(ProcFileHandler reader) { +// super(reader); +// } +// +// private static long parseKilobytes(String line) { +// Objects.requireNonNull(line); +// return Long.parseLong(line.split("\\s+")[1]); +// } +// +// @Override +// protected void clear() { +// EnumSet.allOf(MetricKey.class).forEach(key -> metrics.put(key, new AtomicLong(-1))); +// } +// +// @Override +// protected void process(Collection lines) { +// Objects.requireNonNull(lines); +// +// for (final String line : lines) { +// if (line.startsWith("Size:")) { +// increment(MetricKey.VSS, parseKilobytes(line) * KB); +// } else if (line.startsWith("Rss:")) { +// increment(MetricKey.RSS, parseKilobytes(line) * KB); +// } else if (line.startsWith("Pss:")) { +// increment(MetricKey.PSS, parseKilobytes(line) * KB); +// } else if (line.startsWith("Swap:")) { +// increment(MetricKey.SWAP, parseKilobytes(line) * KB); +// } else if (line.startsWith("SwapPss:")) { +// increment(MetricKey.SWAPPSS, parseKilobytes(line) * KB); +// } +// } +// } +// +// public Long getMetric(MetricKey key) { +// Objects.requireNonNull(key); +// clear(); +// return metrics.get(key).longValue(); +// } +// +// private void increment(MetricKey key, long increment) { +// Objects.requireNonNull(key); +// metrics.get(key).getAndUpdate(currentValue -> currentValue + increment + +// (currentValue == -1 ? 1 : 0)); +// } +// +// public enum MetricKey { +// /** +// * Virtual set size +// */ +// VSS, +// /** +// * Resident set size +// */ +// RSS, +// /** +// * Proportional set size +// */ +// PSS, +// /** +// * Paged out memory +// */ +// SWAP, +// /** +// * Paged out memory accounting shared pages. Since Linux 4.3. +// */ +// SWAPPSS +// } +//} From ad4a466d7a4e635389771a25672858bcc91400f8 Mon Sep 17 00:00:00 2001 From: JisoLya <523420504@qq.com> Date: Wed, 13 Aug 2025 19:53:56 +0800 Subject: [PATCH 20/35] refactor: update util folder --- .../hugegraph/store/node/util/Base58.java | 172 ++++++++++++++++++ .../store/node/util/HgExecutorUtil.java | 1 + .../store/node/util/HgRegexUtil.java | 7 +- 3 files changed, 179 insertions(+), 1 deletion(-) create mode 100644 hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/util/Base58.java diff --git a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/util/Base58.java b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/util/Base58.java new file mode 100644 index 0000000000..0ce199f14d --- /dev/null +++ b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/util/Base58.java @@ -0,0 +1,172 @@ +/* + * 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.store.node.util; + +import java.io.UnsupportedEncodingException; +import java.math.BigInteger; + +public class Base58 { + + public static final char[] ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz".toCharArray(); + private static final int[] INDEXES = new int[128]; + + static { + for (int i = 0; i < INDEXES.length; i++) { + INDEXES[i] = -1; + } + for (int i = 0; i < ALPHABET.length; i++) { + INDEXES[ALPHABET[i]] = i; + } + } + + /** + * Encodes the given bytes in base58. No checksum is appended. + */ + public static String encode(byte[] input) { + if (input.length == 0) { + return ""; + } + input = copyOfRange(input, 0, input.length); + // Count leading zeroes. + int zeroCount = 0; + while (zeroCount < input.length && input[zeroCount] == 0) { + ++zeroCount; + } + // The actual encoding. + byte[] temp = new byte[input.length * 2]; + int j = temp.length; + + int startAt = zeroCount; + while (startAt < input.length) { + byte mod = divmod58(input, startAt); + if (input[startAt] == 0) { + ++startAt; + } + temp[--j] = (byte) ALPHABET[mod]; + } + + // Strip extra '1' if there are some after decoding. + while (j < temp.length && temp[j] == ALPHABET[0]) { + ++j; + } + // Add as many leading '1' as there were leading zeros. + while (--zeroCount >= 0) { + temp[--j] = (byte) ALPHABET[0]; + } + + byte[] output = copyOfRange(temp, j, temp.length); + try { + return new String(output, "US-ASCII"); + } catch (UnsupportedEncodingException e) { + throw new RuntimeException(e); // Cannot happen. + } + } + + public static byte[] decode(String input) throws IllegalArgumentException { + if (input.length() == 0) { + return new byte[0]; + } + byte[] input58 = new byte[input.length()]; + // Transform the String to a base58 byte sequence + for (int i = 0; i < input.length(); ++i) { + char c = input.charAt(i); + + int digit58 = -1; + if (c >= 0 && c < 128) { + digit58 = INDEXES[c]; + } + if (digit58 < 0) { + throw new IllegalArgumentException("Illegal character " + c + " at " + i); + } + + input58[i] = (byte) digit58; + } + // Count leading zeroes + int zeroCount = 0; + while (zeroCount < input58.length && input58[zeroCount] == 0) { + ++zeroCount; + } + // The encoding + byte[] temp = new byte[input.length()]; + int j = temp.length; + + int startAt = zeroCount; + while (startAt < input58.length) { + byte mod = divmod256(input58, startAt); + if (input58[startAt] == 0) { + ++startAt; + } + + temp[--j] = mod; + } + // Do no add extra leading zeroes, move j to first non null byte. + while (j < temp.length && temp[j] == 0) { + ++j; + } + + return copyOfRange(temp, j - zeroCount, temp.length); + } + + public static BigInteger decodeToBigInteger(String input) throws IllegalArgumentException { + return new BigInteger(1, decode(input)); + } + + // + // number -> number / 58, returns number % 58 + // + private static byte divmod58(byte[] number, int startAt) { + int remainder = 0; + for (int i = startAt; i < number.length; i++) { + int digit256 = (int) number[i] & 0xFF; + int temp = remainder * 256 + digit256; + + number[i] = (byte) (temp / 58); + + remainder = temp % 58; + } + + return (byte) remainder; + } + + // + // number -> number / 256, returns number % 256 + // + private static byte divmod256(byte[] number58, int startAt) { + int remainder = 0; + for (int i = startAt; i < number58.length; i++) { + int digit58 = (int) number58[i] & 0xFF; + int temp = remainder * 58 + digit58; + + number58[i] = (byte) (temp / 256); + + remainder = temp % 256; + } + + return (byte) remainder; + } + + private static byte[] copyOfRange(byte[] source, int from, int to) { + byte[] range = new byte[to - from]; + System.arraycopy(source, from, range, 0, range.length); + + return range; + } + + +} + diff --git a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/util/HgExecutorUtil.java b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/util/HgExecutorUtil.java index b8bb30f13f..62cf3767df 100644 --- a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/util/HgExecutorUtil.java +++ b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/util/HgExecutorUtil.java @@ -33,6 +33,7 @@ * 2022/03/04 */ @Slf4j +@Deprecated public final class HgExecutorUtil { private final static Map threadPoolMap = new ConcurrentHashMap<>(); diff --git a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/util/HgRegexUtil.java b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/util/HgRegexUtil.java index 905e9b5d0c..051a964711 100644 --- a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/util/HgRegexUtil.java +++ b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/util/HgRegexUtil.java @@ -18,7 +18,9 @@ package org.apache.hugegraph.store.node.util; import java.util.ArrayList; +import java.util.LinkedHashMap; import java.util.List; +import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -27,6 +29,8 @@ */ public class HgRegexUtil { + private static Map patternMap = new LinkedHashMap<>(); + public static String getGroupValue(String regex, String source, int groupId) { if (regex == null || "".equals(regex) || source == null || "".equals(source)) { return null; @@ -52,7 +56,8 @@ public static List toGroupValues(String regex, String source) { return null; } - Pattern p = Pattern.compile(regex); + Pattern p = patternMap.computeIfAbsent(regex, key -> Pattern.compile(regex)); + // Pattern p = Pattern.compile(regex); Matcher m = p.matcher(source); List list = null; From 7e5d052a9597de4403d84cc3cd68aac1630570eb Mon Sep 17 00:00:00 2001 From: JisoLya <523420504@qq.com> Date: Wed, 13 Aug 2025 19:56:30 +0800 Subject: [PATCH 21/35] refactor: update util folder --- .../hugegraph/store/node/util/Base58.java | 344 +++++++++--------- 1 file changed, 172 insertions(+), 172 deletions(-) diff --git a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/util/Base58.java b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/util/Base58.java index 0ce199f14d..0a1450e53c 100644 --- a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/util/Base58.java +++ b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/util/Base58.java @@ -1,172 +1,172 @@ -/* - * 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.store.node.util; - -import java.io.UnsupportedEncodingException; -import java.math.BigInteger; - -public class Base58 { - - public static final char[] ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz".toCharArray(); - private static final int[] INDEXES = new int[128]; - - static { - for (int i = 0; i < INDEXES.length; i++) { - INDEXES[i] = -1; - } - for (int i = 0; i < ALPHABET.length; i++) { - INDEXES[ALPHABET[i]] = i; - } - } - - /** - * Encodes the given bytes in base58. No checksum is appended. - */ - public static String encode(byte[] input) { - if (input.length == 0) { - return ""; - } - input = copyOfRange(input, 0, input.length); - // Count leading zeroes. - int zeroCount = 0; - while (zeroCount < input.length && input[zeroCount] == 0) { - ++zeroCount; - } - // The actual encoding. - byte[] temp = new byte[input.length * 2]; - int j = temp.length; - - int startAt = zeroCount; - while (startAt < input.length) { - byte mod = divmod58(input, startAt); - if (input[startAt] == 0) { - ++startAt; - } - temp[--j] = (byte) ALPHABET[mod]; - } - - // Strip extra '1' if there are some after decoding. - while (j < temp.length && temp[j] == ALPHABET[0]) { - ++j; - } - // Add as many leading '1' as there were leading zeros. - while (--zeroCount >= 0) { - temp[--j] = (byte) ALPHABET[0]; - } - - byte[] output = copyOfRange(temp, j, temp.length); - try { - return new String(output, "US-ASCII"); - } catch (UnsupportedEncodingException e) { - throw new RuntimeException(e); // Cannot happen. - } - } - - public static byte[] decode(String input) throws IllegalArgumentException { - if (input.length() == 0) { - return new byte[0]; - } - byte[] input58 = new byte[input.length()]; - // Transform the String to a base58 byte sequence - for (int i = 0; i < input.length(); ++i) { - char c = input.charAt(i); - - int digit58 = -1; - if (c >= 0 && c < 128) { - digit58 = INDEXES[c]; - } - if (digit58 < 0) { - throw new IllegalArgumentException("Illegal character " + c + " at " + i); - } - - input58[i] = (byte) digit58; - } - // Count leading zeroes - int zeroCount = 0; - while (zeroCount < input58.length && input58[zeroCount] == 0) { - ++zeroCount; - } - // The encoding - byte[] temp = new byte[input.length()]; - int j = temp.length; - - int startAt = zeroCount; - while (startAt < input58.length) { - byte mod = divmod256(input58, startAt); - if (input58[startAt] == 0) { - ++startAt; - } - - temp[--j] = mod; - } - // Do no add extra leading zeroes, move j to first non null byte. - while (j < temp.length && temp[j] == 0) { - ++j; - } - - return copyOfRange(temp, j - zeroCount, temp.length); - } - - public static BigInteger decodeToBigInteger(String input) throws IllegalArgumentException { - return new BigInteger(1, decode(input)); - } - - // - // number -> number / 58, returns number % 58 - // - private static byte divmod58(byte[] number, int startAt) { - int remainder = 0; - for (int i = startAt; i < number.length; i++) { - int digit256 = (int) number[i] & 0xFF; - int temp = remainder * 256 + digit256; - - number[i] = (byte) (temp / 58); - - remainder = temp % 58; - } - - return (byte) remainder; - } - - // - // number -> number / 256, returns number % 256 - // - private static byte divmod256(byte[] number58, int startAt) { - int remainder = 0; - for (int i = startAt; i < number58.length; i++) { - int digit58 = (int) number58[i] & 0xFF; - int temp = remainder * 58 + digit58; - - number58[i] = (byte) (temp / 256); - - remainder = temp % 256; - } - - return (byte) remainder; - } - - private static byte[] copyOfRange(byte[] source, int from, int to) { - byte[] range = new byte[to - from]; - System.arraycopy(source, from, range, 0, range.length); - - return range; - } - - -} - +/* + * 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.store.node.util; + +import java.io.UnsupportedEncodingException; +import java.math.BigInteger; + +public class Base58 { + + public static final char[] ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz".toCharArray(); + private static final int[] INDEXES = new int[128]; + + static { + for (int i = 0; i < INDEXES.length; i++) { + INDEXES[i] = -1; + } + for (int i = 0; i < ALPHABET.length; i++) { + INDEXES[ALPHABET[i]] = i; + } + } + + /** + * Encodes the given bytes in base58. No checksum is appended. + */ + public static String encode(byte[] input) { + if (input.length == 0) { + return ""; + } + input = copyOfRange(input, 0, input.length); + // Count leading zeroes. + int zeroCount = 0; + while (zeroCount < input.length && input[zeroCount] == 0) { + ++zeroCount; + } + // The actual encoding. + byte[] temp = new byte[input.length * 2]; + int j = temp.length; + + int startAt = zeroCount; + while (startAt < input.length) { + byte mod = divmod58(input, startAt); + if (input[startAt] == 0) { + ++startAt; + } + temp[--j] = (byte) ALPHABET[mod]; + } + + // Strip extra '1' if there are some after decoding. + while (j < temp.length && temp[j] == ALPHABET[0]) { + ++j; + } + // Add as many leading '1' as there were leading zeros. + while (--zeroCount >= 0) { + temp[--j] = (byte) ALPHABET[0]; + } + + byte[] output = copyOfRange(temp, j, temp.length); + try { + return new String(output, "US-ASCII"); + } catch (UnsupportedEncodingException e) { + throw new RuntimeException(e); // Cannot happen. + } + } + + public static byte[] decode(String input) throws IllegalArgumentException { + if (input.length() == 0) { + return new byte[0]; + } + byte[] input58 = new byte[input.length()]; + // Transform the String to a base58 byte sequence + for (int i = 0; i < input.length(); ++i) { + char c = input.charAt(i); + + int digit58 = -1; + if (c >= 0 && c < 128) { + digit58 = INDEXES[c]; + } + if (digit58 < 0) { + throw new IllegalArgumentException("Illegal character " + c + " at " + i); + } + + input58[i] = (byte) digit58; + } + // Count leading zeroes + int zeroCount = 0; + while (zeroCount < input58.length && input58[zeroCount] == 0) { + ++zeroCount; + } + // The encoding + byte[] temp = new byte[input.length()]; + int j = temp.length; + + int startAt = zeroCount; + while (startAt < input58.length) { + byte mod = divmod256(input58, startAt); + if (input58[startAt] == 0) { + ++startAt; + } + + temp[--j] = mod; + } + // Do no add extra leading zeroes, move j to first non null byte. + while (j < temp.length && temp[j] == 0) { + ++j; + } + + return copyOfRange(temp, j - zeroCount, temp.length); + } + + public static BigInteger decodeToBigInteger(String input) throws IllegalArgumentException { + return new BigInteger(1, decode(input)); + } + + // + // number -> number / 58, returns number % 58 + // + private static byte divmod58(byte[] number, int startAt) { + int remainder = 0; + for (int i = startAt; i < number.length; i++) { + int digit256 = (int) number[i] & 0xFF; + int temp = remainder * 256 + digit256; + + number[i] = (byte) (temp / 58); + + remainder = temp % 58; + } + + return (byte) remainder; + } + + // + // number -> number / 256, returns number % 256 + // + private static byte divmod256(byte[] number58, int startAt) { + int remainder = 0; + for (int i = startAt; i < number58.length; i++) { + int digit58 = (int) number58[i] & 0xFF; + int temp = remainder * 58 + digit58; + + number58[i] = (byte) (temp / 256); + + remainder = temp % 256; + } + + return (byte) remainder; + } + + private static byte[] copyOfRange(byte[] source, int from, int to) { + byte[] range = new byte[to - from]; + System.arraycopy(source, from, range, 0, range.length); + + return range; + } + + +} + From 44a8a250177f4e03917388e1f4096f8a674cf2a6 Mon Sep 17 00:00:00 2001 From: JisoLya <523420504@qq.com> Date: Thu, 14 Aug 2025 13:56:03 +0800 Subject: [PATCH 22/35] refactor: update Config,shutdown hook,Application & introduce task folder --- .../hugegraph/store/node/AppConfig.java | 101 ++++- .../hugegraph/store/node/AppShutdownHook.java | 58 ++- .../store/node/StoreNodeApplication.java | 3 + .../hugegraph/store/node/task/TTLCleaner.java | 346 ++++++++++++++++++ .../node/task/ttl/DefaulTaskSubmitter.java | 59 +++ .../node/task/ttl/RaftTaskSubmitter.java | 104 ++++++ .../store/node/task/ttl/TaskInfo.java | 62 ++++ .../store/node/task/ttl/TaskSubmitter.java | 49 +++ 8 files changed, 775 insertions(+), 7 deletions(-) create mode 100644 hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/task/TTLCleaner.java create mode 100644 hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/task/ttl/DefaulTaskSubmitter.java create mode 100644 hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/task/ttl/RaftTaskSubmitter.java create mode 100644 hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/task/ttl/TaskInfo.java create mode 100644 hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/task/ttl/TaskSubmitter.java diff --git a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/AppConfig.java b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/AppConfig.java index 674a7fe417..400c81a6f7 100644 --- a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/AppConfig.java +++ b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/AppConfig.java @@ -22,6 +22,7 @@ import javax.annotation.PostConstruct; +import org.apache.hugegraph.store.options.JobOptions; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.context.properties.ConfigurationProperties; @@ -34,6 +35,8 @@ @Component public class AppConfig { + private static int cpus = Runtime.getRuntime().availableProcessors(); + @Value("${pdserver.address}") private String pdServerAddress; @@ -53,6 +56,9 @@ public class AppConfig { @Value("${app.data-path: store}") private String dataPath; + @Value("${app.placeholder-size: 10}") + private Integer placeholderSize; + @Value("${app.raft-path:}") private String raftPath; @@ -73,6 +79,10 @@ public class AppConfig { private ThreadPoolGrpc threadPoolGrpc; @Autowired private ThreadPoolScan threadPoolScan; + @Autowired + private JobConfig jobConfig; + @Autowired + private QueryPushDownConfig queryPushDownConfig; public String getRaftPath() { if (raftPath == null || raftPath.length() == 0) { @@ -131,6 +141,20 @@ public Map getRocksdbConfig() { return config; } + public JobOptions getJobOptions() { + JobOptions jobOptions = new JobOptions(); + jobOptions.setCore(jobConfig.getCore() == 0 ? cpus : jobConfig.getCore()); + jobOptions.setMax(jobConfig.getMax() == 0 ? cpus * 4 : jobConfig.getMax()); + jobOptions.setQueueSize(jobConfig.getQueueSize()); + jobOptions.setBatchSize(jobConfig.getBatchSize()); + int uninterruptibleCore = jobOptions.getUninterruptibleCore(); + jobOptions.setUninterruptibleCore(uninterruptibleCore == 0 ? cpus : uninterruptibleCore); + int uninterruptibleMax = jobOptions.getUninterruptibleMax(); + jobOptions.setUninterruptibleMax(uninterruptibleMax == 0 ? cpus * 4 : uninterruptibleMax); + jobOptions.setUninterruptibleQueueSize(jobConfig.getUninterruptibleQueueSize()); + return jobOptions; + } + @Data @Configuration public class ThreadPoolGrpc { @@ -170,6 +194,10 @@ public class Raft { private int snapshotLogIndexMargin; @Value("${raft.snapshotInterval:300}") private int snapshotInterval; + + @Value("${raft.snapshotDownloadingThread:8}") + private int snapshotDownloadingThread; + @Value("${raft.disruptorBufferSize:0}") private int disruptorBufferSize; @Value("${raft.max-log-file-size:50000000000}") @@ -182,10 +210,10 @@ public class Raft { private int maxSegmentFileSize; @Value("${raft.maxReplicatorInflightMsgs:256}") private int maxReplicatorInflightMsgs; - @Value("${raft.maxEntriesSize:256}") - private int maxEntriesSize; - @Value("${raft.maxBodySize:524288}") - private int maxBodySize; + @Value("${raft.rpcPoolSize-multipleOfCPU: 6}") + private int rpcPoolSizeByMultipleOfCPU; + @Value("${raft.rpcPoolSize-basic: 256}") + private int rpcPoolSizeOfBasic; } @@ -220,6 +248,70 @@ public class FakePdConfig { private int shardCount; } + @Data + @Configuration + public class JobConfig { + + @Value("${job.interruptableThreadPool.core:128}") + private int core; + + @Value("${job.interruptableThreadPool.max:256}") + private int max; + + @Value("${job.interruptableThreadPool.queue:" + Integer.MAX_VALUE + "}") + private int queueSize; + + @Value("${job.cleaner.batch.size:10000}") + private int batchSize; + + @Value("${job.start-time:0}") + private int startTime; + + @Value("${job.uninterruptibleThreadPool.core:0}") + private int uninterruptibleCore; + + @Value("${job.uninterruptibleThreadPool.max:256}") + private int uninterruptibleMax; + + @Value("${job.uninterruptibleThreadPool.queue:" + Integer.MAX_VALUE + "}") + private int uninterruptibleQueueSize; + } + + @Data + @Configuration + public class QueryPushDownConfig { + + /** + * query v2 thread pool size + */ + @Value("${query.push-down.threads:1500}") + private int threadPoolSize; + + /** + * the batch size that each request gets + */ + @Value("${query.push-down.fetch_batch:20000}") + private int fetchBatchSize; + + /** + * the timeout of request fetch + */ + @Value("${query.push-down.fetch_timeout:3600000}") + private long fetchTimeOut; + + /** + * the limit of memory operations, like sort etc. + */ + @Value("${query.push-down.memory_limit_count:50000}") + private int memoryLimitCount; + + /** + * limit size of index sst file size (kB) + */ + @Value("${query.push-down.index_size_limit_count:50000}") + private int indexSizeLimitCount; + } + @Data @Configuration @ConfigurationProperties(prefix = "app") @@ -235,5 +327,4 @@ public class RocksdbConfig { private final Map rocksdb = new HashMap<>(); } - } diff --git a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/AppShutdownHook.java b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/AppShutdownHook.java index b239a327a5..82dc8362c4 100644 --- a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/AppShutdownHook.java +++ b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/AppShutdownHook.java @@ -17,13 +17,22 @@ package org.apache.hugegraph.store.node; -import org.apache.hugegraph.rocksdb.access.RocksDBFactory; +import java.util.concurrent.ThreadPoolExecutor; + +import org.apache.hugegraph.store.HgStoreEngine; +import org.apache.hugegraph.store.business.BusinessHandlerImpl; + +import lombok.extern.slf4j.Slf4j; /** * copy from web */ +@Slf4j public class AppShutdownHook extends Thread { + private static final String msg = + "there are still uninterruptible jobs that have not been completed and" + + " will wait for them to complete"; private final Thread mainThread; private boolean shutDownSignalReceived; @@ -53,7 +62,52 @@ public boolean shouldShutDown() { return shutDownSignalReceived; } + /** + * shutdown method + * 1. check uninterruptible job + * 2. check compaction job + */ private void doSomethingForShutdown() { - RocksDBFactory.getInstance().releaseAllGraphDB(); + checkUninterruptibleJobs(); + checkCompactJob(); + //RocksDBFactory.getInstance().releaseAllGraphDB(); + } + + private void checkUninterruptibleJobs() { + ThreadPoolExecutor jobs = HgStoreEngine.getUninterruptibleJobs(); + try { + long lastPrint = System.currentTimeMillis() - 5000; + log.info("check for ongoing background jobs that cannot be interrupted...."); + while (jobs.getActiveCount() != 0 || !jobs.getQueue().isEmpty()) { + synchronized (AppShutdownHook.class) { + if (System.currentTimeMillis() - lastPrint > 5000) { + log.warn(msg); + lastPrint = System.currentTimeMillis(); + } + try { + AppShutdownHook.class.wait(200); + } catch (InterruptedException e) { + } + } + } + } catch (Exception e) { + + } + try { + jobs.shutdownNow(); + } catch (Exception e) { + + } + log.info("all ongoing background jobs have been completed and the shutdown will continue"); + } + + private void checkCompactJob() { + ThreadPoolExecutor jobs = BusinessHandlerImpl.getCompactionPool(); + try { + jobs.shutdownNow(); + } catch (Exception e) { + + } + log.info("closed compact job pool"); } } diff --git a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/StoreNodeApplication.java b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/StoreNodeApplication.java index c793ed96f2..6c31a5825c 100644 --- a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/StoreNodeApplication.java +++ b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/StoreNodeApplication.java @@ -19,6 +19,7 @@ import org.apache.hugegraph.store.node.listener.ContextClosedListener; import org.apache.hugegraph.store.node.listener.PdConfigureListener; +import org.apache.hugegraph.store.node.listener.PlaceHolderListener; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.ConfigurableApplicationContext; @@ -56,8 +57,10 @@ public static void start() { SpringApplication application = new SpringApplication(StoreNodeApplication.class); PdConfigureListener listener = new PdConfigureListener(); ContextClosedListener closedListener = new ContextClosedListener(); + PlaceHolderListener placeHolderListener = new PlaceHolderListener(); application.addListeners(listener); application.addListeners(closedListener); + application.addListeners(placeHolderListener); ConfigurableApplicationContext context = application.run(); listener.setContext(context); System.out.println("StoreNodeApplication started."); diff --git a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/task/TTLCleaner.java b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/task/TTLCleaner.java new file mode 100644 index 0000000000..df3524809f --- /dev/null +++ b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/task/TTLCleaner.java @@ -0,0 +1,346 @@ +/* + * 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.store.node.task; + +import java.time.Duration; +import java.time.LocalDateTime; +import java.util.Arrays; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.ScheduledThreadPoolExecutor; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicLong; +import java.util.function.BiFunction; + +import org.apache.commons.lang3.ArrayUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.tuple.ImmutableTriple; +import org.apache.commons.lang3.tuple.Triple; +import org.apache.hugegraph.pd.client.KvClient; +import org.apache.hugegraph.pd.client.PDConfig; +import org.apache.hugegraph.pd.grpc.kv.KResponse; +import org.apache.hugegraph.rocksdb.access.RocksDBSession; +import org.apache.hugegraph.rocksdb.access.ScanIterator; +import org.apache.hugegraph.rocksdb.access.SessionOperator; +import org.apache.hugegraph.serializer.DirectBinarySerializer; +import org.apache.hugegraph.serializer.DirectBinarySerializer.DirectHugeElement; +import org.apache.hugegraph.store.HgStoreEngine; +import org.apache.hugegraph.store.business.BusinessHandlerImpl; +import org.apache.hugegraph.store.business.InnerKeyCreator; +import org.apache.hugegraph.store.business.InnerKeyFilter; +import org.apache.hugegraph.store.constant.HugeServerTables; +import org.apache.hugegraph.store.consts.PoolNames; +import org.apache.hugegraph.store.node.AppConfig; +import org.apache.hugegraph.store.node.grpc.HgStoreNodeService; +import org.apache.hugegraph.store.node.task.ttl.DefaulTaskSubmitter; +import org.apache.hugegraph.store.node.task.ttl.RaftTaskSubmitter; +import org.apache.hugegraph.store.node.task.ttl.TaskInfo; +import org.apache.hugegraph.store.node.task.ttl.TaskSubmitter; +import org.apache.hugegraph.store.pd.DefaultPdProvider; +import org.apache.hugegraph.store.pd.PdProvider; +import org.apache.hugegraph.store.util.DefaultThreadFactory; +import org.apache.hugegraph.store.util.ExecutorUtil; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import com.google.common.collect.Sets; +import com.google.gson.Gson; +import com.google.protobuf.ByteString; + +import lombok.extern.slf4j.Slf4j; + +/** + * @date 2023/6/12 + **/ +@Service +@Slf4j +public class TTLCleaner implements Runnable { + + private static final String[] tables = + new String[]{ + HugeServerTables.VERTEX_TABLE, + HugeServerTables.IN_EDGE_TABLE, + HugeServerTables.OUT_EDGE_TABLE, + HugeServerTables.INDEX_TABLE + }; + private final ScheduledExecutorService scheduler; + private final HgStoreEngine storeEngine; + private PdProvider pd; + private KvClient client; + private ThreadPoolExecutor executor; + private final Set failedPartitions = Sets.newConcurrentHashSet(); + private final ScheduledFuture future; + private final String key = "HUGEGRAPH/hg/EXPIRED"; + private final DirectBinarySerializer serializer = new DirectBinarySerializer(); + @Autowired + private HgStoreNodeService service; + private final AtomicBoolean running = new AtomicBoolean(false); + + private final AppConfig appConfig; + private final AppConfig.JobConfig jobConfig; + + public TTLCleaner(@Autowired AppConfig config) { + this.appConfig = config; + jobConfig = config.getJobConfig(); + LocalDateTime now = LocalDateTime.now(); + int startTime = jobConfig.getStartTime(); + if (startTime < 0 || startTime > 23) { + startTime = 19; + } + LocalDateTime next = now.withHour(startTime).withMinute(0).withSecond(0).withNano(0); + Duration between = Duration.between(now, next); + long delay = between.getSeconds(); // 计算开始的时间,凌晨开始比较合适 + if (delay < 0) { + delay += 3600 * 24; + } + log.info("clean task will begin in {} seconds", delay); + DefaultThreadFactory factory = new DefaultThreadFactory("ttl-cleaner"); + scheduler = new ScheduledThreadPoolExecutor(1, factory); + future = scheduler.scheduleAtFixedRate(this, delay, 24 * 3600, TimeUnit.SECONDS); + storeEngine = HgStoreEngine.getInstance(); + } + + public void submit() { + scheduler.submit(this); + } + + public BiFunction getJudge(String table) { + + try { + switch (table) { + case HugeServerTables.VERTEX_TABLE: + return (key, value) -> { + DirectHugeElement el = serializer.parseVertex(key, value); + return predicate(el); + }; + case HugeServerTables.OUT_EDGE_TABLE: + case HugeServerTables.IN_EDGE_TABLE: + return (key, value) -> { + DirectHugeElement el = serializer.parseEdge(key, value); + return predicate(el); + }; + case HugeServerTables.INDEX_TABLE: + return (key, value) -> { + DirectHugeElement el = serializer.parseIndex(key, value); + return predicate(el); + }; + default: + throw new UnsupportedOperationException("unsupported table"); + } + + } catch (Exception e) { + log.error("failed to parse entry: ", e); + throw e; + } + } + + private Boolean predicate(DirectHugeElement el) { + long expiredTime = el.expiredTime(); + if (expired(expiredTime)) { + return Boolean.TRUE; + } else { + return Boolean.FALSE; + } + } + + private boolean expired(long expiredTime) { + return expiredTime != 0 && expiredTime < System.currentTimeMillis(); + } + + @Override + public void run() { + if (!running.compareAndSet(false, true)) { + return; + } + try { + running.set(true); + if (client == null) { + PDConfig config = PDConfig.of(appConfig.getPdServerAddress()); + config.setAuthority(DefaultPdProvider.name, DefaultPdProvider.authority); + client = new KvClient(config); + } + KResponse k = client.get(key); + String g = k.getValue(); + + log.info("cleaner config:{}", jobConfig); + if (executor == null) { + executor = + ExecutorUtil.createExecutor(PoolNames.I_JOB, jobConfig.getCore(), + jobConfig.getMax(), + jobConfig.getQueueSize()); + } + BusinessHandlerImpl handler = (BusinessHandlerImpl) storeEngine.getBusinessHandler(); + if (!StringUtils.isEmpty(g)) { + String[] graphs = StringUtils.split(g, ","); + log.info("clean task got graphs:{}", Arrays.toString(graphs)); + if (ArrayUtils.isEmpty(graphs)) { + return; + } + runAll(graphs, handler); + } else { + log.info("there is no specific graph to clean up and will do compact directly"); + Set leaderPartitions = handler.getLeaderPartitionIdSet(); + leaderPartitions.forEach( + p -> new RaftTaskSubmitter(service, handler).submitCompaction(p)); + } + } catch (Exception e) { + log.error("clean ttl with error.", e); + } finally { + running.set(false); + } + } + + private void runAll(String[] graphs, BusinessHandlerImpl handler) throws InterruptedException { + long start = System.currentTimeMillis(); + Map tasks = new ConcurrentHashMap<>(graphs.length); + LinkedList> elements = new LinkedList<>(); + Map pc = new ConcurrentHashMap<>(); + for (String graph : graphs) { + if (!StringUtils.isEmpty(graph)) { + String[] fields = graph.split(":"); + String graphName; + long startTime = 0; + boolean isRaft = false; + if (fields.length > 0) { + graphName = fields[0]; + if (fields.length > 1) { + String time = StringUtils.isEmpty(fields[1]) ? "0" : fields[1]; + startTime = Long.parseLong(time); + } + if (fields.length > 2) { + String raft = StringUtils.isEmpty(fields[2]) ? "0" : fields[2]; + if ("1".equals(raft)) { + isRaft = true; + } + } + TaskInfo taskInfo = new TaskInfo(handler, graphName, isRaft, startTime, tables, + service); + tasks.put(graphName, taskInfo); + List ids = taskInfo.getPartitionIds(); + for (Integer pId : ids) { + for (String table : tables) { + Triple triple = + new ImmutableTriple<>(pId, graphName, table); + elements.add(triple); + } + pc.putIfAbsent(pId, new AtomicLong(0)); + } + } + } + } + CountDownLatch latch = new CountDownLatch(elements.size()); + for (Triple t : elements) { + Runnable r = getTask(handler, latch, t, tasks, pc); + executor.execute(r); + } + latch.await(); + for (Map.Entry entry : pc.entrySet()) { + AtomicLong count = entry.getValue(); + if (count.get() > 0) { + Integer id = entry.getKey(); + new DefaulTaskSubmitter(service, handler).submitCompaction(id); + } + } + Gson gson = new Gson(); + String msg = gson.toJson(tasks); + long end = System.currentTimeMillis(); + log.info("clean data cost:{}, size :{}", (end - start), msg); + } + + private Runnable getTask( + BusinessHandlerImpl handler, + CountDownLatch latch, + Triple t, + Map counter, + Map pc) { + int batchSize = appConfig.getJobConfig().getBatchSize(); + return () -> { + Integer id = t.getLeft(); + String graph = t.getMiddle(); + String table = t.getRight(); + TaskInfo taskInfo = counter.get(graph); + ScanIterator scan = null; + try { + Map graphCounter = taskInfo.getTableCounter(); + TaskSubmitter submitter = taskInfo.getTaskSubmitter(); + AtomicLong tableCounter = graphCounter.get(table); + RocksDBSession session = handler.getSession(id); + InnerKeyCreator keyCreator = handler.getKeyCreator(); + SessionOperator op = session.sessionOp(); + BiFunction judge = getJudge(table); + scan = op.scan(table, + keyCreator.getStartKey(id, graph), + keyCreator.getEndKey(id, graph), + ScanIterator.Trait.SCAN_LT_END); + InnerKeyFilter filter = new InnerKeyFilter(scan, true); + LinkedList all = new LinkedList<>(); + AtomicBoolean state = new AtomicBoolean(true); + AtomicLong partitionCounter = pc.get(id); + while (filter.hasNext() && state.get()) { + RocksDBSession.BackendColumn current = filter.next(); + byte[] realKey = + Arrays.copyOfRange(current.name, 0, current.name.length - Short.BYTES); + if (judge.apply(realKey, current.value)) { + ByteString e = ByteString.copyFrom(current.name); + all.add(e); + } + if (all.size() >= batchSize) { + submitter.submitClean(id, graph, table, all, state, tableCounter, + partitionCounter); + all = new LinkedList<>(); + } + } + if (all.size() > 0 && state.get()) { + submitter.submitClean(id, graph, table, all, state, tableCounter, + partitionCounter); + } + log.info("id:{}, graph:{}, table:{}, count:{} clean ttl data done and will do " + + "compact", id, graph, table, tableCounter.get()); + } catch (Exception e) { + String s = "clean ttl with error by: partition-%s,graph-%s,table-%s:"; + String msg = String.format(s, id, graph, table); + log.error(msg, e); + } finally { + latch.countDown(); + if (scan != null) { + scan.close(); + } + } + }; + } + + public ScheduledFuture getFuture() { + return future; + } + + public ThreadPoolExecutor getExecutor() { + return executor; + } + + public ScheduledExecutorService getScheduler() { + return scheduler; + } +} diff --git a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/task/ttl/DefaulTaskSubmitter.java b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/task/ttl/DefaulTaskSubmitter.java new file mode 100644 index 0000000000..df8d0c87f7 --- /dev/null +++ b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/task/ttl/DefaulTaskSubmitter.java @@ -0,0 +1,59 @@ +/* + * 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.store.node.task.ttl; + +import java.util.LinkedList; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicLong; + +import org.apache.hugegraph.store.business.BusinessHandler; +import org.apache.hugegraph.store.node.grpc.HgStoreNodeService; + +import com.alipay.sofa.jraft.Status; +import com.alipay.sofa.jraft.error.RaftError; +import com.google.protobuf.ByteString; + +/** + * @date 2024/5/7 + **/ +public class DefaulTaskSubmitter extends TaskSubmitter { + + public DefaulTaskSubmitter(HgStoreNodeService service, BusinessHandler handler) { + super(service, handler); + } + + @Override + public Status submitClean(Integer id, String graph, String table, LinkedList all, + AtomicBoolean state, AtomicLong tableCounter, + AtomicLong partitionCounter) { + try { + this.handler.cleanTtl(graph, id, table, all); + tableCounter.getAndAdd(all.size()); + partitionCounter.getAndAdd(all.size()); + return Status.OK(); + } catch (Exception e) { + return new Status(RaftError.UNKNOWN, e.getMessage()); + } + } + + @Override + public Status submitCompaction(Integer id) { + this.handler.dbCompaction("", id); + return Status.OK(); + } +} diff --git a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/task/ttl/RaftTaskSubmitter.java b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/task/ttl/RaftTaskSubmitter.java new file mode 100644 index 0000000000..15abb74e66 --- /dev/null +++ b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/task/ttl/RaftTaskSubmitter.java @@ -0,0 +1,104 @@ +/* + * 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.store.node.task.ttl; + +import java.util.LinkedList; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.atomic.AtomicReference; + +import org.apache.hugegraph.pd.grpc.kv.V; +import org.apache.hugegraph.store.HgStoreEngine; +import org.apache.hugegraph.store.PartitionEngine; +import org.apache.hugegraph.store.business.BusinessHandler; +import org.apache.hugegraph.store.cmd.request.DbCompactionRequest; +import org.apache.hugegraph.store.grpc.common.TTLCleanRequest; +import org.apache.hugegraph.store.node.grpc.GrpcClosure; +import org.apache.hugegraph.store.node.grpc.HgStoreNodeService; +import org.apache.hugegraph.store.raft.RaftClosure; +import org.apache.hugegraph.store.raft.RaftOperation; + +import com.alipay.sofa.jraft.Status; +import com.alipay.sofa.jraft.error.RaftError; +import com.google.protobuf.ByteString; + +import lombok.extern.slf4j.Slf4j; + +@Slf4j +/** + * @date 2024/5/7 + **/ +public class RaftTaskSubmitter extends TaskSubmitter { + + public RaftTaskSubmitter(HgStoreNodeService service, BusinessHandler handler) { + super(service, handler); + } + + @Override + public Status submitClean(Integer id, String graph, String table, LinkedList all, + AtomicBoolean state, AtomicLong tableCounter, + AtomicLong partitionCounter) { + AtomicReference result = new AtomicReference<>(); + try { + TTLCleanRequest cleanRequest = + TTLCleanRequest.newBuilder().addAllIds(all).setGraph(graph).setPartitionId(id) + .setTable(table).build(); + tableCounter.getAndAdd(all.size()); + CountDownLatch latch = new CountDownLatch(1); + GrpcClosure c = new GrpcClosure() { + @Override + public void run(Status status) { + try { + if (!status.isOk()) { + log.warn("submit task got status: {}", status); + state.set(false); + } else { + partitionCounter.getAndAdd(all.size()); + } + result.set(status); + } catch (Exception e) { + log.warn("submit task with error:", e); + state.set(false); + result.set(new Status(RaftError.UNKNOWN, e.getMessage())); + } finally { + latch.countDown(); + } + } + }; + service.addRaftTask(HgStoreNodeService.TTL_CLEAN_OP, graph, id, cleanRequest, c); + latch.await(); + } catch (Exception e) { + throw new RuntimeException(e); + } + return result.get(); + } + + @Override + public Status submitCompaction(Integer id) { + DbCompactionRequest cr = new DbCompactionRequest(); + cr.setPartitionId(id); + cr.setTableName(""); + cr.setGraphName(""); + PartitionEngine engine = HgStoreEngine.getInstance().getPartitionEngine(id); + RaftClosure closure = status -> log.info("ttl compaction:{}, status is {}", id, status); + RaftOperation operation = RaftOperation.create(RaftOperation.DB_COMPACTION, cr); + engine.addRaftTask(operation, closure); + return Status.OK(); + } +} diff --git a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/task/ttl/TaskInfo.java b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/task/ttl/TaskInfo.java new file mode 100644 index 0000000000..9bcf0f00b5 --- /dev/null +++ b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/task/ttl/TaskInfo.java @@ -0,0 +1,62 @@ +/* + * 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.store.node.task.ttl; + +import java.util.List; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicLong; + +import org.apache.hugegraph.store.business.BusinessHandlerImpl; +import org.apache.hugegraph.store.node.grpc.HgStoreNodeService; + +import lombok.Data; + +/** + * @date 2024/5/7 + **/ +@Data +public class TaskInfo { + + String graph; + boolean isRaft; + transient BusinessHandlerImpl handler; + long startTime; + String[] tables; + ConcurrentHashMap tableCounter; + transient TaskSubmitter taskSubmitter; + + public TaskInfo(BusinessHandlerImpl handler, String graph, boolean isRaft, long startTime, + String[] tables, HgStoreNodeService service) { + this.handler = handler; + this.graph = graph; + this.isRaft = isRaft; + this.tables = tables; + this.startTime = startTime; + this.tableCounter = new ConcurrentHashMap(tables.length); + for (String table : tables) { + tableCounter.put(table, new AtomicLong()); + } + this.taskSubmitter = + isRaft ? new RaftTaskSubmitter(service, handler) : + new DefaulTaskSubmitter(service, handler); + } + + public List getPartitionIds() { + return isRaft ? handler.getLeaderPartitionIds(graph) : handler.getPartitionIds(graph); + } +} diff --git a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/task/ttl/TaskSubmitter.java b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/task/ttl/TaskSubmitter.java new file mode 100644 index 0000000000..5c1020b37d --- /dev/null +++ b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/task/ttl/TaskSubmitter.java @@ -0,0 +1,49 @@ +/* + * 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.store.node.task.ttl; + +import java.util.LinkedList; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicLong; + +import org.apache.hugegraph.store.business.BusinessHandler; +import org.apache.hugegraph.store.node.grpc.HgStoreNodeService; + +import com.alipay.sofa.jraft.Status; +import com.google.protobuf.ByteString; + +/** + * @date 2024/5/7 + **/ +public abstract class TaskSubmitter { + + protected BusinessHandler handler; + protected HgStoreNodeService service; + + public TaskSubmitter(HgStoreNodeService service, BusinessHandler handler) { + this.service = service; + this.handler = handler; + } + + public abstract Status submitClean(Integer id, String graph, String table, + LinkedList all, + AtomicBoolean state, AtomicLong tableCounter, + AtomicLong partitionCounter); + + public abstract Status submitCompaction(Integer id); +} From 7aa48ae8bfab5ecf875d7db1d7c875d6c720603f Mon Sep 17 00:00:00 2001 From: JisoLya <523420504@qq.com> Date: Thu, 14 Aug 2025 13:56:31 +0800 Subject: [PATCH 23/35] refactor: reformat code --- .../node/controller/FixGraphIdController.java | 134 +++++++------- .../controller/HgStoreMetricsController.java | 5 +- .../controller/HgStoreStatusController.java | 14 +- .../node/controller/HgTestController.java | 35 ++-- .../store/node/controller/PartitionAPI.java | 12 +- .../store/node/controller/RaftAPI.java | 8 +- .../store/node/entry/PartitionRequest.java | 2 +- .../store/node/grpc/GRpcServerConfig.java | 1 + .../store/node/grpc/GrpcClosure.java | 2 +- .../store/node/grpc/HgStoreNodeService.java | 7 +- .../store/node/grpc/HgStoreSessionImpl.java | 13 +- .../store/node/grpc/HgStoreStreamImpl.java | 3 +- .../store/node/grpc/ParallelScanIterator.java | 7 +- .../store/node/grpc/ScanBatchResponse.java | 9 +- .../grpc/query/AggregativeQueryObserver.java | 112 ++++++------ .../grpc/query/AggregativeQueryService.java | 70 ++++---- .../node/grpc/query/MultiKeyComparator.java | 13 +- .../store/node/grpc/query/QueryStage.java | 15 +- .../store/node/grpc/query/QueryStages.java | 1 - .../store/node/grpc/query/QueryUtil.java | 164 ++++++++++-------- .../node/grpc/query/model/PipelineResult.java | 7 +- .../grpc/query/model/PipelineResultType.java | 2 +- .../node/grpc/query/model/QueryPlan.java | 24 +-- .../node/grpc/query/stages/AggStage.java | 50 +++--- .../query/stages/DeserializationStage.java | 4 +- .../grpc/query/stages/EarlyStopException.java | 3 +- .../query/stages/ExtractAggFieldStage.java | 16 +- .../node/grpc/query/stages/FilterStage.java | 4 +- .../node/grpc/query/stages/LimitStage.java | 9 +- .../node/grpc/query/stages/OlapStage.java | 29 ++-- .../node/grpc/query/stages/OrderByStage.java | 21 ++- .../grpc/query/stages/ProjectionStage.java | 18 +- .../node/grpc/query/stages/SampleStage.java | 6 +- .../grpc/query/stages/SimpleCountStage.java | 8 +- .../node/grpc/query/stages/TopStage.java | 17 +- .../node/grpc/query/stages/TtlCheckStage.java | 5 +- .../node/grpc/scan/ScanResponseObserver.java | 9 +- .../node/listener/ContextClosedListener.java | 9 +- .../node/listener/PdConfigureListener.java | 3 +- .../node/listener/PlaceHolderListener.java | 7 +- .../store/node/metrics/JRaftMetrics.java | 42 ++--- .../store/node/metrics/ProcfsReader.java | 49 +++--- .../store/node/metrics/ProcfsSmaps.java | 61 +++---- .../node/metrics/RocksDBMetricsConst.java | 21 ++- .../store/node/metrics/SystemMemoryStats.java | 7 - .../hugegraph/store/node/util/Base58.java | 11 +- .../store/node/util/HgRegexUtil.java | 2 +- 47 files changed, 566 insertions(+), 505 deletions(-) diff --git a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/controller/FixGraphIdController.java b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/controller/FixGraphIdController.java index 468fc606da..010fe50e78 100644 --- a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/controller/FixGraphIdController.java +++ b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/controller/FixGraphIdController.java @@ -32,16 +32,7 @@ import java.util.Set; import java.util.stream.Collectors; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - import org.apache.hugegraph.backend.BackendColumn; - import org.apache.hugegraph.rocksdb.access.RocksDBSession; import org.apache.hugegraph.rocksdb.access.ScanIterator; import org.apache.hugegraph.rocksdb.access.SessionOperator; @@ -54,6 +45,14 @@ import org.apache.hugegraph.store.node.grpc.query.QueryUtil; import org.apache.hugegraph.structure.BaseElement; import org.apache.hugegraph.structure.BaseProperty; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + import com.google.protobuf.Int64Value; import com.google.protobuf.InvalidProtocolBufferException; @@ -65,13 +64,8 @@ public class FixGraphIdController { private static final String GRAPH_ID_PREFIX = "@GRAPH_ID@"; + private static final List graphs = new ArrayList<>(); - @Autowired - private HgStoreNodeService nodeService; - - private final BinaryElementSerializer serializer = BinaryElementSerializer.getInstance(); - - private static List graphs = new ArrayList<>(); static { String graphNames = "acgopgs/acg_opg/g\n" + "acgopgs/acg/g\n" + @@ -241,11 +235,22 @@ public class FixGraphIdController { graphs.addAll(List.of(graphNames.split("\n"))); } + private final BinaryElementSerializer serializer = BinaryElementSerializer.getInstance(); + @Autowired + private HgStoreNodeService nodeService; + + public static byte[] getShortBytes(int x) { + byte[] buf = new byte[2]; + buf[0] = (byte) (x >> 8); + buf[1] = (byte) (x); + return buf; + } + @GetMapping(value = "/update_next_id/{partition_id}/{graph_id}", produces = "application/json") public String updateMaxGraphId(@PathVariable(value = "partition_id") int pid, @PathVariable( "graph_id") long graphId) throws IOException { var businessHandler = nodeService.getStoreEngine().getBusinessHandler(); - try(var manager = new GraphIdManager(businessHandler, pid)){ + try (var manager = new GraphIdManager(businessHandler, pid)) { var key = MetadataKeyHelper.getCidKey(GRAPH_ID_PREFIX); log.info("update max graph id to {}, partition, {}", graphId, pid); manager.put(key, Int64Value.of(graphId)); @@ -256,10 +261,10 @@ public String updateMaxGraphId(@PathVariable(value = "partition_id") int pid, @P @GetMapping(value = "/next_id/{partition_id}", produces = "application/json") public String getNextId(@PathVariable(value = "partition_id") int pid) throws IOException { - var handler = (BusinessHandlerImpl)nodeService.getStoreEngine().getBusinessHandler(); + var handler = (BusinessHandlerImpl) nodeService.getStoreEngine().getBusinessHandler(); var op = handler.getSession(pid).sessionOp(); var next = op.get(GraphIdManager.DEFAULT_CF_NAME, - MetadataKeyHelper.getCidKey(GRAPH_ID_PREFIX)); + MetadataKeyHelper.getCidKey(GRAPH_ID_PREFIX)); if (next != null) { return String.valueOf(Int64Value.parseFrom(next).getValue()); } @@ -269,8 +274,8 @@ public String getNextId(@PathVariable(value = "partition_id") int pid) throws IO @PostMapping(value = "/update_graph_id/{partition_id}", produces = "application/json") public String updateGraphId(@PathVariable(value = "partition_id") int pid, @RequestBody Map idMap) throws IOException { - var handler = (BusinessHandlerImpl)nodeService.getStoreEngine().getBusinessHandler(); - try(var manager = new GraphIdManager(handler, pid)) { + var handler = (BusinessHandlerImpl) nodeService.getStoreEngine().getBusinessHandler(); + try (var manager = new GraphIdManager(handler, pid)) { idMap.forEach((graphName, graphId) -> { log.info("update graph id of {} to {}, partition, {}", graphName, graphId, pid); var graphIdKey = MetadataKeyHelper.getGraphIDKey(graphName); @@ -285,32 +290,26 @@ public String updateGraphId(@PathVariable(value = "partition_id") int pid, return "OK"; } - public static byte[] getShortBytes(int x) { - byte[] buf = new byte[2]; - buf[0] = (byte) (x >> 8); - buf[1] = (byte) (x); - return buf; - } - /** * 统计整个表中 graph id 对应对 count 以及随机抽样 100 条 (精确的数字) - * @param op op + * + * @param op op * @param table table * @return count map and sample map */ private Map.Entry, Map>> - scanAndSample(SessionOperator op, String table) { + scanAndSample(SessionOperator op, String table) { Map countMap = new HashMap<>(); Map> sampleMap = new HashMap<>(); Random random = new Random(); try (var iterator = op.scan(table)) { - while (iterator.hasNext()){ - var col = (RocksDBSession.BackendColumn)iterator.next(); - if (col.name.length > 2){ + while (iterator.hasNext()) { + var col = (RocksDBSession.BackendColumn) iterator.next(); + if (col.name.length > 2) { int id = (col.name[0] << 8) + (col.name[1]); - if (!countMap.containsKey(id)){ + if (!countMap.containsKey(id)) { countMap.put(id, 0); sampleMap.put(id, new ArrayList<>()); } @@ -329,7 +328,7 @@ public static byte[] getShortBytes(int x) { } } } - return new AbstractMap.SimpleEntry<> (countMap, sampleMap); + return new AbstractMap.SimpleEntry<>(countMap, sampleMap); } private long getLabelId(RocksDBSession.BackendColumn col, String table) { @@ -342,6 +341,7 @@ private long getLabelId(RocksDBSession.BackendColumn col, String table) { /** * 效率优化,只查前 10 万条 + * * @param op * @param table * @param start @@ -353,7 +353,7 @@ private Map scanAndSample(SessionOperator op, String table, byte Random random = new Random(); Set labels = new HashSet<>(); - try(var iterator = op.scan(table, start, end, ScanIterator.Trait.SCAN_LT_END)) { + try (var iterator = op.scan(table, start, end, ScanIterator.Trait.SCAN_LT_END)) { int count = 0; List sample = new ArrayList<>(); while (iterator.hasNext()) { @@ -376,13 +376,14 @@ private Map scanAndSample(SessionOperator op, String table, byte } return Map.of("count", count, "sample", sample, "labels", labels.stream().map(String::valueOf) - .collect(Collectors.joining(","))); + .collect(Collectors.joining(","))); } } /** * 性能优化版,按照 graph id 去扫描,根据预估文件大小,决定是否要扫这个分区 + * * @param session * @return */ @@ -390,7 +391,7 @@ private Map scanAndSample(SessionOperator op, String table, byte private Map> scanAndSample(RocksDBSession session) { Map> result = new HashMap<>(); var op = session.sessionOp(); - for (int i = 0 ; i < 65536; i ++) { + for (int i = 0; i < 65536; i++) { var start = getShortBytes(i); var end = getShortBytes(i + 1); long size = session.getApproximateDataSize(start, end); @@ -398,7 +399,7 @@ private Map> scanAndSample(RocksDBSession session) var vMap = scanAndSample(op, "g+v", start, end); var eMap = scanAndSample(op, "g+ie", start, end); - if ((int)vMap.get("count") + (int)eMap.get("count") > 0) { + if ((int) vMap.get("count") + (int) eMap.get("count") > 0) { result.put(i, Map.of("vCount", vMap.get("count"), "eCount", eMap.get("count"), "size", size, @@ -412,13 +413,12 @@ private Map> scanAndSample(RocksDBSession session) return result; } - private String elementToString(BaseElement element) { if (element == null) { return ""; } StringBuilder builder = new StringBuilder(); - for (var property: element.getProperties().entrySet()) { + for (var property : element.getProperties().entrySet()) { BaseProperty value = property.getValue(); var v = property.getValue().value(); if (v instanceof String) { @@ -429,28 +429,30 @@ private String elementToString(BaseElement element) { return builder.toString(); } - private String runDeserialize(List list, boolean isVertex){ + private String runDeserialize(List list, boolean isVertex) { if (list == null || list.isEmpty()) { return "empty"; } int total = list.size(); StringBuilder buffer = new StringBuilder(); - for (String graph: graphs) { + for (String graph : graphs) { int success = 0; BaseElement element = null; for (var column : list) { BackendColumn newCol = BackendColumn.of(Arrays.copyOfRange(column.name, Short.BYTES, - column.name.length - Short.BYTES), column.value); + column.name.length - + Short.BYTES), + column.value); try { element = QueryUtil.parseEntry(BusinessHandlerImpl.getGraphSupplier(graph), - newCol, isVertex); + newCol, isVertex); success++; } catch (Exception e) { } } if (success > total * 0.8) { - buffer.append(String.format("%s: %f, %s\n", graph, success*1.0/total, + buffer.append(String.format("%s: %f, %s\n", graph, success * 1.0 / total, element == null ? "FAIL" : element.toString())); } } @@ -459,25 +461,28 @@ private String runDeserialize(List list, boolean i /** * 要同时满足能够解析定点和边 + * * @param list1 vertex list * @param list2 edge list * @return */ private Map runDeserialize(List list1, - List list2){ + List list2) { int total1 = list1.size(); int total2 = list2.size(); List passed = new ArrayList<>(); BaseElement element = null; BaseElement element2 = null; - for (String graph: graphs) { + for (String graph : graphs) { int success = 0; int success2 = 0; for (var column : list1) { BackendColumn newCol = BackendColumn.of(Arrays.copyOfRange(column.name, Short.BYTES, - column.name.length - Short.BYTES), column.value); + column.name.length - + Short.BYTES), + column.value); try { element = QueryUtil.parseEntry(BusinessHandlerImpl.getGraphSupplier(graph), newCol, true); @@ -491,10 +496,12 @@ private Map runDeserialize(List li for (var column : list2) { BackendColumn newCol = BackendColumn.of(Arrays.copyOfRange(column.name, Short.BYTES, - column.name.length - Short.BYTES), column.value); + column.name.length - + Short.BYTES), + column.value); try { element2 = QueryUtil.parseEntry(BusinessHandlerImpl.getGraphSupplier(graph), - newCol, false); + newCol, false); success2++; } catch (Exception e) { } @@ -507,17 +514,17 @@ private Map runDeserialize(List li } return Map.of("graphs", String.join("\n", passed), "samples", - String.join("\n",List.of(elementToString(element), - elementToString(element2)))); + String.join("\n", List.of(elementToString(element), + elementToString(element2)))); } private Map getGraphIds(RocksDBSession session) { Map graphs = new HashMap<>(); var op = session.sessionOp(); var prefix = MetadataKeyHelper.getGraphIDKey(""); - try (var iterator = op.scan(GraphIdManager.DEFAULT_CF_NAME, prefix)){ - while (iterator.hasNext()){ - var col = (RocksDBSession.BackendColumn)iterator.next(); + try (var iterator = op.scan(GraphIdManager.DEFAULT_CF_NAME, prefix)) { + while (iterator.hasNext()) { + var col = (RocksDBSession.BackendColumn) iterator.next(); try { int graphId = (int) Int64Value.parseFrom(col.value).getValue(); String graphName = new String(col.name).replace("HUGEGRAPH/GRAPH_ID/", ""); @@ -533,9 +540,9 @@ private Set getSlotIds(RocksDBSession session) { Set result = new HashSet<>(); var op = session.sessionOp(); var prefix = MetadataKeyHelper.getCidSlotKeyPrefix(GRAPH_ID_PREFIX); - try (var iterator = op.scan(GraphIdManager.DEFAULT_CF_NAME, prefix)){ - while (iterator.hasNext()){ - var col = (RocksDBSession.BackendColumn)iterator.next(); + try (var iterator = op.scan(GraphIdManager.DEFAULT_CF_NAME, prefix)) { + while (iterator.hasNext()) { + var col = (RocksDBSession.BackendColumn) iterator.next(); try { int graphId = (int) Int64Value.parseFrom(col.value).getValue(); result.add(graphId); @@ -548,17 +555,17 @@ private Set getSlotIds(RocksDBSession session) { } @GetMapping(value = "/graph_ids/{id}", produces = "application/json") - public Map> allGraphIds(@PathVariable(value = "id") int id) { + public Map> allGraphIds(@PathVariable(value = "id") int id) { var session = nodeService.getStoreEngine().getBusinessHandler().getSession(id); var graphs = getGraphIds(session); var slotIds = getSlotIds(session); Map> result = new HashMap<>(); - for (int i = 0 ; i < 65536; i ++) { + for (int i = 0; i < 65536; i++) { var start = getShortBytes(i); var end = getShortBytes(i + 1); long size = session.getApproximateDataSize(start, end); long count = 0; - if (size > 0 && size < 512){ + if (size > 0 && size < 512) { count = session.sessionOp().keyCount(start, end, "g+v"); if (count == 0) { continue; @@ -580,7 +587,6 @@ public Map> allGraphIds(@PathVariable(value = "id") return result; } - @GetMapping(value = "/check/{id}", produces = "application/json") public Map> checkGraphId(@PathVariable(value = "id") int id) { var businessHandler = nodeService.getStoreEngine().getBusinessHandler(); @@ -590,7 +596,7 @@ public Map> checkGraphId(@PathVariable(value = "id" var result = new HashMap>(); var samples = scanAndSample(session); - for (var entry : samples.entrySet()){ + for (var entry : samples.entrySet()) { var graphId = entry.getKey(); var value = entry.getValue(); @@ -624,7 +630,7 @@ public String deleteGraphId(@PathVariable(value = "partition") int pid, var op = businessHandler.getSession(pid).sessionOp(); var tables = List.of("g+v", "g+ie", "g+oe", "g+index", "g+olap"); for (var table : tables) { - op.deleteRange(table, start,end); + op.deleteRange(table, start, end); } return "OK"; } diff --git a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/controller/HgStoreMetricsController.java b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/controller/HgStoreMetricsController.java index a07d838868..a7aea39b1d 100644 --- a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/controller/HgStoreMetricsController.java +++ b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/controller/HgStoreMetricsController.java @@ -38,11 +38,10 @@ @RequestMapping(value = "/metrics", method = RequestMethod.GET) public class HgStoreMetricsController { - @Autowired - HgStoreNodeService nodeService; - private final SystemMetrics systemMetrics = new SystemMetrics(); private final DriveMetrics driveMetrics = new DriveMetrics(); + @Autowired + HgStoreNodeService nodeService; @GetMapping public Map index() { diff --git a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/controller/HgStoreStatusController.java b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/controller/HgStoreStatusController.java index fce51ab1b8..86a6830795 100644 --- a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/controller/HgStoreStatusController.java +++ b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/controller/HgStoreStatusController.java @@ -19,6 +19,12 @@ import java.io.Serializable; +import org.apache.hugegraph.store.grpc.state.ScanState; +import org.apache.hugegraph.store.node.entry.RestResult; +import org.apache.hugegraph.store.node.grpc.HgStoreNodeState; +import org.apache.hugegraph.store.node.grpc.HgStoreStreamImpl; +import org.apache.hugegraph.store.node.model.HgNodeStatus; +import org.apache.hugegraph.store.node.task.TTLCleaner; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.GetMapping; @@ -27,12 +33,6 @@ import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.RestController; -import org.apache.hugegraph.store.grpc.state.ScanState; -import org.apache.hugegraph.store.node.entry.RestResult; -import org.apache.hugegraph.store.node.grpc.HgStoreNodeState; -import org.apache.hugegraph.store.node.grpc.HgStoreStreamImpl; -import org.apache.hugegraph.store.node.model.HgNodeStatus; -import org.apache.hugegraph.store.node.task.TTLCleaner; import com.google.protobuf.util.JsonFormat; /** @@ -102,7 +102,7 @@ public Serializable ttlClean() { try { cleaner.submit(); result.setState(RestResult.OK); - result.setMessage("" ); + result.setMessage(""); return result; } catch (Exception e) { result.setState(RestResult.ERR); diff --git a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/controller/HgTestController.java b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/controller/HgTestController.java index a13ecf1394..dec310abb0 100644 --- a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/controller/HgTestController.java +++ b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/controller/HgTestController.java @@ -17,13 +17,13 @@ package org.apache.hugegraph.store.node.controller; -import com.alipay.sofa.jraft.entity.PeerId; +import java.util.ArrayList; +import java.util.List; + import org.apache.hugegraph.store.PartitionEngine; -import org.apache.hugegraph.store.node.grpc.HgStoreNodeService; import org.apache.hugegraph.store.meta.Partition; import org.apache.hugegraph.store.meta.Store; - -import lombok.extern.slf4j.Slf4j; +import org.apache.hugegraph.store.node.grpc.HgStoreNodeService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.GetMapping; @@ -31,8 +31,9 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; -import java.util.ArrayList; -import java.util.List; +import com.alipay.sofa.jraft.entity.PeerId; + +import lombok.extern.slf4j.Slf4j; /** * For testing only @@ -61,9 +62,9 @@ public Store testGetStoreInfo() { @GetMapping(value = "/raftRestart/{groupId}", produces = MediaType.APPLICATION_JSON_VALUE) public String restartRaftNode(@PathVariable(value = "groupId") int groupId) { PartitionEngine engine = nodeService.getStoreEngine().getPartitionEngine(groupId); - if (engine != null ) { - engine.restartRaftNode(); - return "OK"; + if (engine != null) { + engine.restartRaftNode(); + return "OK"; } else { return "partition engine not found"; } @@ -151,7 +152,8 @@ public String transferLeaders() { public String noVote() { try { nodeService.getStoreEngine().getPartitionEngines().values().forEach(engine -> { - engine.getRaftNode().disableVote(); }); + engine.getRaftNode().disableVote(); + }); return "OK"; } catch (Exception e) { log.error("pulse reset error: ", e); @@ -162,7 +164,8 @@ public String noVote() { @GetMapping(value = "/restart_raft", produces = MediaType.APPLICATION_JSON_VALUE) public String restartRaft() { try { - nodeService.getStoreEngine().getPartitionEngines().values().forEach(PartitionEngine::restartRaftNode); + nodeService.getStoreEngine().getPartitionEngines().values() + .forEach(PartitionEngine::restartRaftNode); return "OK"; } catch (Exception e) { log.error("pulse reset error: ", e); @@ -170,16 +173,16 @@ public String restartRaft() { } } - @GetMapping(value = "/all_raft_start", produces = MediaType.APPLICATION_JSON_VALUE) public String isRaftAllStarted() { try { var engine = nodeService.getStoreEngine(); var storeId = engine.getPartitionManager().getStore().getId(); - var flag = nodeService.getStoreEngine().getPdProvider().getPartitionsByStore(storeId).stream() - .mapToInt(Partition::getId) - .allMatch(i -> engine.getPartitionEngine(i) != null); - return flag ? "OK" : "NO" ; + var flag = nodeService.getStoreEngine().getPdProvider().getPartitionsByStore(storeId) + .stream() + .mapToInt(Partition::getId) + .allMatch(i -> engine.getPartitionEngine(i) != null); + return flag ? "OK" : "NO"; } catch (Exception e) { log.error("pulse reset error: ", e); return e.getMessage(); diff --git a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/controller/PartitionAPI.java b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/controller/PartitionAPI.java index 8088bf137b..00b3b1a967 100644 --- a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/controller/PartitionAPI.java +++ b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/controller/PartitionAPI.java @@ -200,20 +200,22 @@ public Map arthasstart( return okMap("arthasstart", ret); } - public Map compact(@PathVariable(value = "id")int id){ - boolean submitted = nodeService.getStoreEngine().getBusinessHandler().blockingCompact("", id); + public Map compact(@PathVariable(value = "id") int id) { + boolean submitted = + nodeService.getStoreEngine().getBusinessHandler().blockingCompact("", id); Map map = new HashMap<>(); if (submitted) { map.put("code", "OK"); - map.put("msg", "compaction was successfully submitted. See the log for more information"); + map.put("msg", + "compaction was successfully submitted. See the log for more information"); } else { map.put("code", "Failed"); - map.put("msg", "compaction task fail to submit, and there could be another task in progress"); + map.put("msg", + "compaction task fail to submit, and there could be another task in progress"); } return map; } - public Map okMap(String k, Object v) { Map map = new HashMap<>(); map.put("status", 0); diff --git a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/controller/RaftAPI.java b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/controller/RaftAPI.java index 544338d687..610800e75a 100644 --- a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/controller/RaftAPI.java +++ b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/controller/RaftAPI.java @@ -19,6 +19,10 @@ import javax.servlet.http.HttpServletRequest; +import org.apache.hugegraph.store.HgStoreEngine; +import org.apache.hugegraph.store.PartitionEngine; +import org.apache.hugegraph.store.node.entry.PartitionRequest; +import org.apache.hugegraph.store.node.entry.RestResult; import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; @@ -27,10 +31,6 @@ import org.springframework.web.bind.annotation.RestController; import com.alipay.sofa.jraft.option.RpcOptions; -import org.apache.hugegraph.store.HgStoreEngine; -import org.apache.hugegraph.store.PartitionEngine; -import org.apache.hugegraph.store.node.entry.PartitionRequest; -import org.apache.hugegraph.store.node.entry.RestResult; import lombok.extern.slf4j.Slf4j; diff --git a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/entry/PartitionRequest.java b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/entry/PartitionRequest.java index ee278b7e1e..678f890c2f 100644 --- a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/entry/PartitionRequest.java +++ b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/entry/PartitionRequest.java @@ -19,8 +19,8 @@ import lombok.Data; - @Data public class PartitionRequest { + private Integer id; } diff --git a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/GRpcServerConfig.java b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/GRpcServerConfig.java index df06b988a5..0227e9192b 100644 --- a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/GRpcServerConfig.java +++ b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/GRpcServerConfig.java @@ -31,6 +31,7 @@ */ @Component public class GRpcServerConfig extends GRpcServerBuilderConfigurer { + @Autowired private AppConfig appConfig; diff --git a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/GrpcClosure.java b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/GrpcClosure.java index 0d65066e99..df9fcf67ad 100644 --- a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/GrpcClosure.java +++ b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/GrpcClosure.java @@ -30,7 +30,7 @@ * 2022/1/27 */ -abstract class GrpcClosure implements RaftClosure { +public abstract class GrpcClosure implements RaftClosure { private final Map leaderMap = new HashMap<>(); private V result; diff --git a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/HgStoreNodeService.java b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/HgStoreNodeService.java index 28682374a3..d742d172af 100644 --- a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/HgStoreNodeService.java +++ b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/HgStoreNodeService.java @@ -102,7 +102,7 @@ public void init() { .isUseRocksDBSegmentLogStorage()); setMaxSegmentFileSize(appConfig.getRaft().getMaxSegmentFileSize()); setMaxReplicatorInflightMsgs(appConfig.getRaft().getMaxReplicatorInflightMsgs()); - if (appConfig.getRaft().getRpcPoolSizeByMultipleOfCPU() > 0){ + if (appConfig.getRaft().getRpcPoolSizeByMultipleOfCPU() > 0) { setRaftRpcThreadPoolSize(Utils.cpus() * raft.getRpcPoolSizeByMultipleOfCPU()); } setRaftRpcThreadPoolSizeOfBasic(raft.getRpcPoolSizeOfBasic()); @@ -114,7 +114,7 @@ public void init() { setShardCount(appConfig.getFakePdConfig().getShardCount()); }}); - setQueryPushDownOption(new QueryPushDownOption(){{ + setQueryPushDownOption(new QueryPushDownOption() {{ setThreadPoolSize(appConfig.getQueryPushDownConfig().getThreadPoolSize()); setFetchBatchSize(appConfig.getQueryPushDownConfig().getFetchBatchSize()); setFetchTimeout(appConfig.getQueryPushDownConfig().getFetchTimeOut()); @@ -142,7 +142,8 @@ public List getGraphLeaderPartitionIds(String graphName) { /** * Add raft task, forward data to raft * - * @return true means the data has been submitted, false means not submitted, used to reduce batch splitting for single-replica storage + * @return true means the data has been submitted, false means not submitted, used to reduce + * batch splitting for single-replica storage */ public void addRaftTask(byte methodId, String graphName, Integer partitionId, Req req, diff --git a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/HgStoreSessionImpl.java b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/HgStoreSessionImpl.java index d6182f093a..a569b31f8f 100644 --- a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/HgStoreSessionImpl.java +++ b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/HgStoreSessionImpl.java @@ -26,14 +26,11 @@ import org.apache.hugegraph.pd.common.PDException; import org.apache.hugegraph.pd.grpc.Metapb; import org.apache.hugegraph.pd.grpc.Metapb.GraphMode; -import org.apache.hugegraph.rocksdb.access.ScanIterator; -import org.apache.hugegraph.store.business.BusinessHandler; import org.apache.hugegraph.store.grpc.common.Key; import org.apache.hugegraph.store.grpc.common.Kv; import org.apache.hugegraph.store.grpc.common.ResCode; import org.apache.hugegraph.store.grpc.common.ResStatus; import org.apache.hugegraph.store.grpc.common.TTLCleanRequest; -import org.apache.hugegraph.store.grpc.session.Agg; import org.apache.hugegraph.store.grpc.session.BatchEntry; import org.apache.hugegraph.store.grpc.session.BatchGetReq; import org.apache.hugegraph.store.grpc.session.BatchReq; @@ -46,7 +43,6 @@ import org.apache.hugegraph.store.grpc.session.KeyValueResponse; import org.apache.hugegraph.store.grpc.session.TableReq; import org.apache.hugegraph.store.grpc.session.ValueResponse; -import org.apache.hugegraph.store.grpc.stream.ScanStreamReq; import org.apache.hugegraph.store.meta.Graph; import org.apache.hugegraph.store.meta.GraphManager; import org.apache.hugegraph.store.node.AppConfig; @@ -255,7 +251,8 @@ public void batch(BatchReq request, StreamObserver observer) { GraphMode graphMode = graphState.getMode(); if (graphMode != null && graphMode.getNumber() == GraphMode.ReadOnly_VALUE) { - // When in read-only state, getMetric the latest graph state from pd, the graph's read-only state will be updated in pd's notification. + // When in read-only state, getMetric the latest graph state from pd, + // the graph's read-only state will be updated in pd's notification. Metapb.Graph pdGraph = pd.getPDClient().getGraph(graph); Metapb.GraphState pdGraphState = @@ -264,13 +261,15 @@ public void batch(BatchReq request, StreamObserver observer) { pdGraphState.getMode() != null && pdGraphState.getMode().getNumber() == GraphMode.ReadOnly_VALUE) { - // Confirm that the current state stored in pd is also read-only, then inserting data is not allowed. + // Confirm that the current state stored in pd is also read-only, + // then inserting data is not allowed. throw new PDException(-1, "the graph space size " + "has " + "reached the threshold"); } - // pd status is inconsistent with local cache, update local cache to the status in pd + // pd status is inconsistent with local cache, update local cache to + // the status in pd managerGraph.setProtoObj(pdGraph); } } diff --git a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/HgStoreStreamImpl.java b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/HgStoreStreamImpl.java index 41e6b6b891..e198b42b17 100644 --- a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/HgStoreStreamImpl.java +++ b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/HgStoreStreamImpl.java @@ -71,7 +71,8 @@ public ThreadPoolExecutor getExecutor() { if (this.executor == null) { AppConfig.ThreadPoolScan scan = this.appConfig.getThreadPoolScan(); this.executor = - HgExecutorUtil.createExecutor(PoolNames.SCAN, scan.getCore(), scan.getMax(), + HgExecutorUtil.createExecutor(PoolNames.SCAN, scan.getCore(), + scan.getMax(), scan.getQueue()); } } diff --git a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/ParallelScanIterator.java b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/ParallelScanIterator.java index 1f34b043f6..56ce1f45f3 100644 --- a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/ParallelScanIterator.java +++ b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/ParallelScanIterator.java @@ -107,7 +107,9 @@ public boolean hasNext() { while (current == null && tryTimes < waitDataMaxTryTimes) { try { if (queue.size() != 0 || !finished) { - current = queue.poll(100, TimeUnit.MILLISECONDS); // Regularly check if the client has been closed. + current = queue.poll(100, + TimeUnit.MILLISECONDS); // Regularly check if the + // client has been closed. if (current == null && !finished) { wakeUpScanner(); } @@ -343,7 +345,8 @@ public void scanKV() { if ((entriesSize >= batchSize || bodySize >= maxBodySize) || (orderEdge && bodySize >= maxBodySize / 2)) { if (orderEdge) { - // Sort the edges, ensure all edges of one point are consecutive, prevent other points from inserting. + // Sort the edges, ensure all edges of one point are consecutive, + // prevent other points from inserting. canNext = putData(dataList, iterator != null && iterator.hasNext()); } else { canNext = putData(dataList); diff --git a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/ScanBatchResponse.java b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/ScanBatchResponse.java index a99587cf23..49693ee38c 100644 --- a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/ScanBatchResponse.java +++ b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/ScanBatchResponse.java @@ -40,7 +40,8 @@ * Batch query processor, batch query data, stream back data. * 1. Server-side streaming data to the client * 2. The client returns the batch number to the server after consuming each batch of data. - * 3. The server decides how much data to send based on the batch number, ensuring the uninterrupted transmission of data, + * 3. The server decides how much data to send based on the batch number, ensuring the + * uninterrupted transmission of data, */ @Slf4j public class ScanBatchResponse implements StreamObserver { @@ -48,7 +49,8 @@ public class ScanBatchResponse implements StreamObserver { static ByteBufferAllocator alloc = new ByteBufferAllocator(ParallelScanIterator.maxBodySize * 3 / 2, 1000); private final int maxInFlightCount = PropertyUtil.getInt("app.scan.stream.inflight", 16); - private final int activeTimeout = PropertyUtil.getInt("app.scan.stream.timeout", 60); // unit: second + private final int activeTimeout = PropertyUtil.getInt("app.scan.stream.timeout", 60); + // unit: second private final StreamObserver sender; private final HgStoreWrapperEx wrapper; private final ThreadPoolExecutor executor; @@ -253,7 +255,8 @@ private State setStateIdle() { } /** - * Check for activity, if the client does not request data for a certain period of time, it is considered inactive, close the connection to release resources. + * Check for activity, if the client does not request data for a certain period of time, it + * is considered inactive, close the connection to release resources. */ public void checkActiveTimeout() { if ((System.currentTimeMillis() - activeTime) > activeTimeout * 1000L) { diff --git a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/query/AggregativeQueryObserver.java b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/query/AggregativeQueryObserver.java index eb2840e190..607de298ec 100644 --- a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/query/AggregativeQueryObserver.java +++ b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/query/AggregativeQueryObserver.java @@ -17,6 +17,18 @@ package org.apache.hugegraph.store.node.grpc.query; +import static org.apache.hugegraph.store.node.grpc.query.AggregativeQueryService.errorResponse; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; + import org.apache.hugegraph.backend.BackendColumn; import org.apache.hugegraph.rocksdb.access.RocksDBSession; import org.apache.hugegraph.rocksdb.access.ScanIterator; @@ -32,54 +44,35 @@ import org.apache.hugegraph.structure.BaseEdge; import org.apache.hugegraph.structure.BaseElement; import org.apache.hugegraph.structure.BaseVertex; + import com.google.protobuf.ByteString; + import io.grpc.stub.StreamObserver; import lombok.extern.slf4j.Slf4j; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.concurrent.atomic.AtomicReference; - -import static org.apache.hugegraph.store.node.grpc.query.AggregativeQueryService.errorResponse; - - @Slf4j public class AggregativeQueryObserver implements StreamObserver { private static final int RESULT_COUNT = 16; - - private StreamObserver sender; - private final ExecutorService threadPool; - private final long timeout; - private final int batchSize; - private final AtomicInteger consumeCount = new AtomicInteger(0); - private final AtomicInteger sendCount = new AtomicInteger(0); - private final AtomicBoolean clientCanceled = new AtomicBoolean(false); - + // private final ThreadLocal localBuilder = ThreadLocal.withInitial + // (QueryResponse::newBuilder); +// private final ThreadLocal localKvBuilder = ThreadLocal.withInitial +// (Kv::newBuilder); + private final BinaryElementSerializer serializer = BinaryElementSerializer.getInstance(); + private final StreamObserver sender; private volatile ScanIterator iterator = null; - private QueryPlan plan = null; - private String queryId; -// private final ThreadLocal localBuilder = ThreadLocal.withInitial(QueryResponse::newBuilder); -// private final ThreadLocal localKvBuilder = ThreadLocal.withInitial(Kv::newBuilder); - private final BinaryElementSerializer serializer = BinaryElementSerializer.getInstance(); - - public AggregativeQueryObserver(StreamObserver sender, ExecutorService threadPool, long timeout, - int batchSize) { + public AggregativeQueryObserver(StreamObserver sender, + ExecutorService threadPool, long timeout, + int batchSize) { this.sender = sender; this.threadPool = threadPool; this.batchSize = batchSize; @@ -99,7 +92,8 @@ public void onNext(QueryRequest request) { iterator = QueryUtil.getIterator(request); plan = QueryUtil.buildPlan(request); threadPool.submit(this::sendData); - log.debug("query id: {}, init data cost: {} ms", queryId, (System.nanoTime() - current) * 1.0 / 1000000); + log.debug("query id: {}, init data cost: {} ms", queryId, + (System.nanoTime() - current) * 1.0 / 1000000); } else { this.consumeCount.incrementAndGet(); log.debug("query id: {}, send feedback of {}", queryId, this.consumeCount.get()); @@ -125,13 +119,14 @@ public void sendData() { var responseBuilder = getBuilder(); var kvBuilder = getKvBuilder(); - while (! this.clientCanceled.get()) { + while (!this.clientCanceled.get()) { // produces more result than consumer, just waiting if (sendCount.get() - consumeCount.get() >= RESULT_COUNT) { // read timeout, takes long time not to read data if (System.currentTimeMillis() - lastSend > timeout) { this.sender.onNext(errorResponse(getBuilder(), queryId, - new RuntimeException("sending-timeout, server closed"))); + new RuntimeException( + "sending-timeout, server closed"))); this.sender.onCompleted(); return; } @@ -159,7 +154,7 @@ public void sendData() { } } - if (builder.getIsFinished() || ! builder.getIsOk()) { + if (builder.getIsFinished() || !builder.getIsOk()) { break; } } @@ -172,15 +167,16 @@ public void sendData() { /** * 1.1: pipeline is empty: - * --> read data from iterator + * --> read data from iterator * 1.2: pipeline is not empty - * 1.2.1: only stop stage: --> just finish - * 1.2.2: has Agg or top or sort --> multi thread - * 1.2.3: plain stage: --> read data from iterator through pipeline + * 1.2.1: only stop stage: --> just finish + * 1.2.2: has Agg or top or sort --> multi thread + * 1.2.3: plain stage: --> read data from iterator through pipeline * * @return result builder */ - private QueryResponse.Builder readBatchData(QueryResponse.Builder builder, Kv.Builder kvBuilder) { + private QueryResponse.Builder readBatchData(QueryResponse.Builder builder, + Kv.Builder kvBuilder) { ScanIterator itr = this.iterator; boolean empty = plan.isEmpty(); boolean finish = false; @@ -190,7 +186,7 @@ private QueryResponse.Builder readBatchData(QueryResponse.Builder builder, Kv.Bu long current = System.nanoTime(); try { - if (! empty) { + if (!empty) { if (this.plan.onlyStopStage()) { builder.setIsOk(true).setIsFinished(true); return builder; @@ -235,7 +231,7 @@ private QueryResponse.Builder readBatchData(QueryResponse.Builder builder, Kv.Bu builder.clear(); List batchResult = new ArrayList<>(); - while (itr.hasNext() && ! this.clientCanceled.get()) { + while (itr.hasNext() && !this.clientCanceled.get()) { if (count >= batchSize) { break; } @@ -245,10 +241,12 @@ private QueryResponse.Builder readBatchData(QueryResponse.Builder builder, Kv.Bu var column = (RocksDBSession.BackendColumn) iterator.next(); if (column != null) { batchResult.add(kvBuilder.clear().setKey(ByteString.copyFrom(column.name)) - .setValue(column.value == null ? ByteString.EMPTY : ByteString.copyFrom(column.value)) - .build()); + .setValue(column.value == null ? ByteString.EMPTY : + ByteString.copyFrom(column.value)) + .build()); // builder.addData(kvBuilder.setKey(ByteString.copyFrom(column.name)) - // .setValue(column.value == null ? ByteString.EMPTY : ByteString.copyFrom(column.value)) + // .setValue(column.value == null ? ByteString.EMPTY : ByteString + // .copyFrom(column.value)) // .build()); count++; } @@ -273,15 +271,17 @@ private QueryResponse.Builder readBatchData(QueryResponse.Builder builder, Kv.Bu } catch (Exception e) { log.error("readBatchData got error: ", e); return builder.setIsOk(false).setIsFinished(false).setMessage("Store Server Error: " - + Arrays.toString(e.getStackTrace())); + + Arrays.toString( + e.getStackTrace())); } - if (checkIterator){ + if (checkIterator) { // check the iterator - finish = ! itr.hasNext(); + finish = !itr.hasNext(); } - log.debug("query id: {}, finished batch, with size :{}, finish:{}, cost: {} ms", queryId, count, - finish, (System.nanoTime() - current) * 1.0 / 1000000); + log.debug("query id: {}, finished batch, with size :{}, finish:{}, cost: {} ms", queryId, + count, + finish, (System.nanoTime() - current) * 1.0 / 1000000); return builder.setIsOk(true).setIsFinished(finish); } @@ -289,9 +289,10 @@ private QueryResponse.Builder readBatchData(QueryResponse.Builder builder, Kv.Bu public ScanIterator executePlainPipeline(ScanIterator itr) { return new ScanIterator() { private boolean limitFlag = false; + @Override public boolean hasNext() { - return itr.hasNext() && ! limitFlag; + return itr.hasNext() && !limitFlag; } @Override @@ -315,17 +316,17 @@ public void close() { }; } - /** * 用于并行化处理 + * * @param itr input iterator */ private void execute(ScanIterator itr) { long recordCount = 0; long current = System.nanoTime(); - while (itr.hasNext() && ! this.clientCanceled.get()) { + while (itr.hasNext() && !this.clientCanceled.get()) { try { - recordCount ++; + recordCount++; executePipeline(itr.next()); if (System.currentTimeMillis() - current > timeout * 1000) { throw new RuntimeException("execution timeout"); @@ -368,7 +369,8 @@ private Kv toKv(Kv.Builder builder, PipelineResult result) { case BACKEND_COLUMN: var column = result.getColumn(); builder.setKey(ByteString.copyFrom(column.name)); - builder.setValue(column.value == null ? ByteString.EMPTY : ByteString.copyFrom(column.value)); + builder.setValue(column.value == null ? ByteString.EMPTY : + ByteString.copyFrom(column.value)); break; case MKV: var mkv = result.getKv(); @@ -381,7 +383,7 @@ private Kv toKv(Kv.Builder builder, PipelineResult result) { BackendColumn backendColumn; if (element instanceof BaseVertex) { backendColumn = serializer.writeVertex((BaseVertex) element); - } else { // if (element instanceof BaseEdge) { + } else { // if (element instanceof BaseEdge) { backendColumn = serializer.writeEdge((BaseEdge) element); } diff --git a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/query/AggregativeQueryService.java b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/query/AggregativeQueryService.java index 4d91ba1527..c327f7bf70 100644 --- a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/query/AggregativeQueryService.java +++ b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/query/AggregativeQueryService.java @@ -17,6 +17,11 @@ package org.apache.hugegraph.store.node.grpc.query; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.atomic.AtomicLong; + import org.apache.hugegraph.rocksdb.access.RocksDBSession; import org.apache.hugegraph.store.HgStoreEngine; import org.apache.hugegraph.store.consts.PoolNames; @@ -26,22 +31,19 @@ import org.apache.hugegraph.store.grpc.query.QueryServiceGrpc; import org.apache.hugegraph.store.query.KvSerializer; import org.apache.hugegraph.store.util.ExecutorUtil; +import org.lognet.springboot.grpc.GRpcService; + import com.google.protobuf.ByteString; + import io.grpc.stub.StreamObserver; import lombok.Getter; import lombok.extern.slf4j.Slf4j; -import org.lognet.springboot.grpc.GRpcService; - -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.ThreadPoolExecutor; -import java.util.concurrent.atomic.AtomicLong; @Slf4j @GRpcService public class AggregativeQueryService extends QueryServiceGrpc.QueryServiceImplBase { - private final int batchSize ; + private final int batchSize; private final Long timeout; @@ -55,9 +57,25 @@ public AggregativeQueryService() { batchSize = queryPushDownOption.getFetchBatchSize(); this.threadPool = ExecutorUtil.createExecutor(PoolNames.SCAN_V2, - Runtime.getRuntime().availableProcessors(), - queryPushDownOption.getThreadPoolSize(), - 10000, true); + Runtime.getRuntime().availableProcessors(), + queryPushDownOption.getThreadPoolSize(), + 10000, true); + } + + /** + * 生成错误响应。 + * + * @param queryId 查询标识符 + * @param t 异常对象 + * @return 查询响应对象 + */ + public static QueryResponse errorResponse(QueryResponse.Builder builder, String queryId, + Throwable t) { + return builder.setQueryId(queryId) + .setIsOk(false) + .setIsFinished(false) + .setMessage(t.getMessage() == null ? "" : t.getMessage()) + .build(); } @Override @@ -77,8 +95,9 @@ public void query0(QueryRequest request, StreamObserver observer) var column = (RocksDBSession.BackendColumn) itr.next(); if (column != null) { builder.addData(kvBuilder.setKey(ByteString.copyFrom(column.name)) - .setValue(column.value == null ? ByteString.EMPTY : ByteString.copyFrom(column.value)) - .build()); + .setValue(column.value == null ? ByteString.EMPTY : + ByteString.copyFrom(column.value)) + .build()); } } builder.setQueryId(request.getQueryId()); @@ -94,13 +113,14 @@ public void query0(QueryRequest request, StreamObserver observer) /** * 查询数据条数 * - * @param request 查询请求对象 - * @param observer Observer 对象,用于接收查询响应结果 + * @param request 查询请求对象 + * @param observer Observer 对象,用于接收查询响应结果 */ @Override public void count(QueryRequest request, StreamObserver observer) { - log.debug("query id : {}, simple count of table: {}", request.getQueryId(), request.getTable()); + log.debug("query id : {}, simple count of table: {}", request.getQueryId(), + request.getTable()); var builder = QueryResponse.newBuilder(); var kvBuilder = Kv.newBuilder(); @@ -109,9 +129,10 @@ public void count(QueryRequest request, StreamObserver observer) var handler = new QueryUtil().getHandler(); long start = System.currentTimeMillis(); long count = handler.count(request.getGraph(), request.getTable()); - log.debug("query id: {}, count of cost: {} ms", request.getQueryId(), System.currentTimeMillis() - start); + log.debug("query id: {}, count of cost: {} ms", request.getQueryId(), + System.currentTimeMillis() - start); List array = new ArrayList<>(); - for (int i = 0 ; i < request.getFunctionsList().size(); i ++) { + for (int i = 0; i < request.getFunctionsList().size(); i++) { array.add(new AtomicLong(count)); } @@ -127,19 +148,4 @@ public void count(QueryRequest request, StreamObserver observer) } observer.onCompleted(); } - - /** - * 生成错误响应。 - * - * @param queryId 查询标识符 - * @param t 异常对象 - * @return 查询响应对象 - */ - public static QueryResponse errorResponse(QueryResponse.Builder builder, String queryId, Throwable t) { - return builder.setQueryId(queryId) - .setIsOk(false) - .setIsFinished(false) - .setMessage(t.getMessage() == null ? "" : t.getMessage()) - .build(); - } } diff --git a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/query/MultiKeyComparator.java b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/query/MultiKeyComparator.java index 8cd0a190a3..6a9dc8f4e3 100644 --- a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/query/MultiKeyComparator.java +++ b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/query/MultiKeyComparator.java @@ -17,17 +17,16 @@ package org.apache.hugegraph.store.node.grpc.query; - -import org.apache.hugegraph.store.util.MultiKv; - import java.util.Comparator; import java.util.List; -public class MultiKeyComparator implements Comparator{ +import org.apache.hugegraph.store.util.MultiKv; + +public class MultiKeyComparator implements Comparator { - private List orders; + private final List orders; - public MultiKeyComparator(List orders){ + public MultiKeyComparator(List orders) { this.orders = orders; } @@ -43,7 +42,7 @@ public int compare(MultiKv o1, MultiKv o2) { return key1 == null ? -1 : 1; } - for (int i = 0 ; i < this.orders.size(); i ++) { + for (int i = 0; i < this.orders.size(); i++) { var index = this.orders.get(i); var v1 = key1.size() > index ? key1.get(index) : null; var v2 = key2.size() > index ? key2.get(index) : null; diff --git a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/query/QueryStage.java b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/query/QueryStage.java index 51a1afd0f8..5d58d2e7c8 100644 --- a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/query/QueryStage.java +++ b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/query/QueryStage.java @@ -17,20 +17,22 @@ package org.apache.hugegraph.store.node.grpc.query; +import java.util.Iterator; + import org.apache.hugegraph.store.node.grpc.query.model.PipelineResult; import org.apache.hugegraph.store.node.grpc.query.stages.EarlyStopException; -import java.util.Iterator; - public interface QueryStage { /** * init params + * * @param objects params */ - default void init(Object... objects) {} + default void init(Object... objects) { + } - default PipelineResult handle (PipelineResult result) throws EarlyStopException { + default PipelineResult handle(PipelineResult result) throws EarlyStopException { return null; } @@ -41,7 +43,10 @@ default boolean isIterator() { default Iterator handleIterator(PipelineResult result) { return null; } + String getName(); - default void close() {}; + default void close() { + } + } diff --git a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/query/QueryStages.java b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/query/QueryStages.java index c6addc0980..047edc345d 100644 --- a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/query/QueryStages.java +++ b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/query/QueryStages.java @@ -17,7 +17,6 @@ package org.apache.hugegraph.store.node.grpc.query; - import org.apache.hugegraph.store.node.grpc.query.stages.AggStage; import org.apache.hugegraph.store.node.grpc.query.stages.DeserializationStage; import org.apache.hugegraph.store.node.grpc.query.stages.ExtractAggFieldStage; diff --git a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/query/QueryUtil.java b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/query/QueryUtil.java index 2768ad14e9..42d78cc43b 100644 --- a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/query/QueryUtil.java +++ b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/query/QueryUtil.java @@ -17,6 +17,19 @@ package org.apache.hugegraph.store.node.grpc.query; +import static org.apache.hugegraph.store.business.BusinessHandlerImpl.getGraphSupplier; +import static org.apache.hugegraph.store.constant.HugeServerTables.OLAP_TABLE; +import static org.apache.hugegraph.store.constant.HugeServerTables.TASK_TABLE; +import static org.apache.hugegraph.store.constant.HugeServerTables.VERTEX_TABLE; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.function.Supplier; +import java.util.stream.Collectors; + import org.apache.hugegraph.HugeGraphSupplier; import org.apache.hugegraph.backend.BackendColumn; import org.apache.hugegraph.id.Id; @@ -28,9 +41,9 @@ import org.apache.hugegraph.store.business.BusinessHandler; import org.apache.hugegraph.store.grpc.query.AggregationType; import org.apache.hugegraph.store.grpc.query.DeDupOption; +import org.apache.hugegraph.store.grpc.query.QueryRequest; import org.apache.hugegraph.store.grpc.query.ScanType; import org.apache.hugegraph.store.grpc.query.ScanTypeParam; -import org.apache.hugegraph.store.grpc.query.QueryRequest; import org.apache.hugegraph.store.node.grpc.EmptyIterator; import org.apache.hugegraph.store.node.grpc.query.model.QueryPlan; import org.apache.hugegraph.store.query.QueryTypeParam; @@ -39,21 +52,10 @@ import org.apache.hugegraph.store.query.func.AggregationFunctions; import org.apache.hugegraph.structure.BaseElement; import org.apache.hugegraph.structure.BaseVertex; -import com.google.protobuf.ByteString; -import lombok.extern.slf4j.Slf4j; -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashSet; -import java.util.List; -import java.util.Set; -import java.util.function.Supplier; -import java.util.stream.Collectors; +import com.google.protobuf.ByteString; -import static org.apache.hugegraph.store.business.BusinessHandlerImpl.getGraphSupplier; -import static org.apache.hugegraph.store.constant.HugeServerTables.OLAP_TABLE; -import static org.apache.hugegraph.store.constant.HugeServerTables.TASK_TABLE; -import static org.apache.hugegraph.store.constant.HugeServerTables.VERTEX_TABLE; +import lombok.extern.slf4j.Slf4j; @Slf4j public class QueryUtil { @@ -64,24 +66,15 @@ public class QueryUtil { private static BusinessHandler handler; - private static BinaryElementSerializer serializer = new BinaryElementSerializer(); + private static final BinaryElementSerializer serializer = new BinaryElementSerializer(); - private static Set vertexTables = new HashSet<>(List.of(VERTEX_TABLE, OLAP_TABLE, TASK_TABLE)); - - public BusinessHandler getHandler() { - if (this.handler == null) { - synchronized (this) { - if (this.handler == null) { - this.handler = HgStoreEngine.getInstance().getBusinessHandler(); - } - } - } - return handler; - } + private static final Set vertexTables = + new HashSet<>(List.of(VERTEX_TABLE, OLAP_TABLE, TASK_TABLE)); /** * 要求有语意和顺序关系 * implementation + * * @param request query request * @return query plan */ @@ -95,13 +88,13 @@ public static QueryPlan buildPlan(QueryRequest request) { } if (request.getSampleFactor() < 1.0) { - var sampleStage= QueryStages.ofSampleStage(); + var sampleStage = QueryStages.ofSampleStage(); sampleStage.init(request.getSampleFactor()); plan.addStage(sampleStage); } // only count agg. fast-forward - if (isOnlyCountAggregationFunction(request)){ + if (isOnlyCountAggregationFunction(request)) { var simple = QueryStages.ofSimpleCountStage(); simple.init(request.getFunctionsList().size()); plan.addStage(simple); @@ -126,7 +119,7 @@ public static QueryPlan buildPlan(QueryRequest request) { plan.addStage(olap); } - if (! request.getCondition().isEmpty()) { + if (!request.getCondition().isEmpty()) { var filterStage = QueryStages.ofFilterStage(); filterStage.init(request.getCondition().toByteArray()); plan.addStage(filterStage); @@ -160,7 +153,7 @@ public static QueryPlan buildPlan(QueryRequest request) { plan.addStage(agg); } - if (!isEmpty(request.getPropertyList()) || request.getNullProperty()){ + if (!isEmpty(request.getPropertyList()) || request.getNullProperty()) { var selector = QueryStages.ofProjectionStage(); selector.init(request.getPropertyList(), request.getNullProperty()); plan.addStage(selector); @@ -175,8 +168,8 @@ public static QueryPlan buildPlan(QueryRequest request) { if (!isEmpty(request.getOrderByList())) { var order = QueryStages.ofOrderByStage(); order.init(request.getOrderByList(), request.getGroupByList(), - !isEmpty(request.getFunctionsList()), - request.getSortOrder()); + !isEmpty(request.getFunctionsList()), + request.getSortOrder()); plan.addStage(order); } @@ -191,15 +184,17 @@ public static QueryPlan buildPlan(QueryRequest request) { return plan; } - private static boolean isOnlyCountAggregationFunction(QueryRequest request){ + private static boolean isOnlyCountAggregationFunction(QueryRequest request) { return !isEmpty(request.getFunctionsList()) && - request.getFunctionsList().stream().allMatch(f -> f.getFuncType() == AggregationType.COUNT) && - isEmpty(request.getGroupByList()) && request.getCondition().isEmpty() - && !request.getGroupBySchemaLabel(); + request.getFunctionsList().stream() + .allMatch(f -> f.getFuncType() == AggregationType.COUNT) && + isEmpty(request.getGroupByList()) && request.getCondition().isEmpty() + && !request.getGroupBySchemaLabel(); } private static boolean canOptimiseToTop(QueryRequest request) { - return !isEmpty(request.getOrderByList()) && request.getLimit() < TOP_LIMIT && request.getLimit() > 0; + return !isEmpty(request.getOrderByList()) && request.getLimit() < TOP_LIMIT && + request.getLimit() > 0; } /** @@ -211,7 +206,7 @@ private static boolean canOptimiseToTop(QueryRequest request) { private static boolean needDeserialize(QueryRequest request) { return !isEmpty(request.getOrderByList()) || !isEmpty(request.getPropertyList()) || !request.getCondition().isEmpty() || !isEmpty(request.getFunctionsList()) - && !request.getGroupBySchemaLabel(); + && !request.getGroupBySchemaLabel(); } /** @@ -231,31 +226,32 @@ public static ScanIterator getIterator(QueryRequest request) { case PRIMARY_SCAN: // id scan // todo: 多个主键查询 + 精确去重+limit 的情况,考虑使用 map 做一部分的精确 - return handler.scan(request.getGraph(), request.getTable(), toQTP(request.getScanTypeParamList()), - request.getDedupOption()); + return handler.scan(request.getGraph(), request.getTable(), + toQTP(request.getScanTypeParamList()), + request.getDedupOption()); case NO_SCAN: // no scan 不需要反查: // 1. 能够直接解析,不需要反查。2. 不需要消重,直接取 count return handler.scanIndex(request.getGraph(), - request.getIndexesList().stream() - .map(x -> toQTP(x.getParamsList())) - .collect(Collectors.toList()), - request.getDedupOption(), - request.getLoadPropertyFromIndex(), - request.getCheckTtl()); + request.getIndexesList().stream() + .map(x -> toQTP(x.getParamsList())) + .collect(Collectors.toList()), + request.getDedupOption(), + request.getLoadPropertyFromIndex(), + request.getCheckTtl()); case INDEX_SCAN: return handler.scanIndex(request.getGraph(), - request.getTable(), - request.getIndexesList().stream() - .map(x -> toQTP(x.getParamsList())) - .collect(Collectors.toList()), - request.getDedupOption(), - true, - needIndexTransKey(request), - request.getCheckTtl(), - request.getLimit()); + request.getTable(), + request.getIndexesList().stream() + .map(x -> toQTP(x.getParamsList())) + .collect(Collectors.toList()), + request.getDedupOption(), + true, + needIndexTransKey(request), + request.getCheckTtl(), + request.getLimit()); default: break; } @@ -266,12 +262,14 @@ public static ScanIterator getIterator(QueryRequest request) { /** * 1. no scan/ 不需要回表 * 2. 只有一个索引, + * * @param request * @return */ private static boolean needIndexTransKey(QueryRequest request) { - if (request.getScanType() == ScanType.NO_SCAN ) { - return ! isOnlyCountAggregationFunction(request) && request.getDedupOption() == DeDupOption.NONE; + if (request.getScanType() == ScanType.NO_SCAN) { + return !isOnlyCountAggregationFunction(request) && + request.getDedupOption() == DeDupOption.NONE; } return true; } @@ -282,19 +280,18 @@ private static List toQTP(List range) { private static QueryTypeParam fromScanTypeParam(ScanTypeParam param) { return new QueryTypeParam(param.getKeyStart().toByteArray(), - param.getKeyEnd().toByteArray(), - param.getScanBoundary(), - param.getIsPrefix(), - param.getIsSecondaryIndex(), - param.getCode(), - param.getIdPrefix().toByteArray()); + param.getKeyEnd().toByteArray(), + param.getScanBoundary(), + param.getIsPrefix(), + param.getIsSecondaryIndex(), + param.getCode(), + param.getIdPrefix().toByteArray()); } - public static boolean isEmpty(Collection c) { + public static boolean isEmpty(Collection c) { return c == null || c.size() == 0; } - public static BaseElement parseEntry(HugeGraphSupplier graph, BackendColumn column, boolean isVertex) { @@ -311,29 +308,34 @@ public static BaseElement parseOlap(BackendColumn column, BaseVertex vertex) { /** * 一次的顶点序列化 - 反序列化 + * * @param vertexColumn vertex - * @param olap olap vertex + * @param olap olap vertex * @return new vertex */ - public static BackendColumn combineColumn(BackendColumn vertexColumn, List olap) { + public static BackendColumn combineColumn(BackendColumn vertexColumn, + List olap) { return serializer.mergeCols(vertexColumn, olap.toArray(new BackendColumn[0])); } - public static AggregationFunction createFunc(AggregationType funcType, String genericType) { AggregationFunction func = null; switch (funcType) { case AVG: - func = new AggregationFunctions.AvgFunction(getAggregationBufferSupplier(genericType)); + func = new AggregationFunctions.AvgFunction( + getAggregationBufferSupplier(genericType)); break; case SUM: - func = new AggregationFunctions.SumFunction(getAggregationBufferSupplier(genericType)); + func = new AggregationFunctions.SumFunction( + getAggregationBufferSupplier(genericType)); break; case MAX: - func = new AggregationFunctions.MaxFunction(getAggregationBufferSupplier(genericType)); + func = new AggregationFunctions.MaxFunction( + getAggregationBufferSupplier(genericType)); break; case MIN: - func = new AggregationFunctions.MinFunction(getAggregationBufferSupplier(genericType)); + func = new AggregationFunctions.MinFunction( + getAggregationBufferSupplier(genericType)); break; case COUNT: func = new AggregationFunctions.CountFunction(); @@ -348,11 +350,10 @@ public static Supplier getAggregationBufferSupplier(String genericType) { return AggregationFunctions.getAggregationBufferSupplier(genericType); } - public static List fromStringBytes(List list) { return list.stream() - .map(id -> id == null ? null : IdUtil.fromBytes(id.toByteArray())) - .collect(Collectors.toList()); + .map(id -> id == null ? null : IdUtil.fromBytes(id.toByteArray())) + .collect(Collectors.toList()); } /** @@ -371,4 +372,15 @@ public static Long getLabelId(RocksDBSession.BackendColumn column, boolean isVer return id.asLong(); } + public BusinessHandler getHandler() { + if (handler == null) { + synchronized (this) { + if (handler == null) { + handler = HgStoreEngine.getInstance().getBusinessHandler(); + } + } + } + return handler; + } + } diff --git a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/query/model/PipelineResult.java b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/query/model/PipelineResult.java index 0043236949..6af8f3f635 100644 --- a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/query/model/PipelineResult.java +++ b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/query/model/PipelineResult.java @@ -20,6 +20,7 @@ import org.apache.hugegraph.rocksdb.access.RocksDBSession; import org.apache.hugegraph.store.util.MultiKv; import org.apache.hugegraph.structure.BaseElement; + import lombok.Data; @Data @@ -43,16 +44,16 @@ public PipelineResult(BaseElement element) { this.element = element; } - public PipelineResult(MultiKv kv){ + public PipelineResult(MultiKv kv) { this.resultType = PipelineResultType.MKV; this.kv = kv; } - private PipelineResult(){ + private PipelineResult() { this.resultType = PipelineResultType.NULL; } - private PipelineResult(String message){ + private PipelineResult(String message) { this.resultType = PipelineResultType.ERROR; this.message = message; } diff --git a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/query/model/PipelineResultType.java b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/query/model/PipelineResultType.java index 007b5b67f9..614fd72ed5 100644 --- a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/query/model/PipelineResultType.java +++ b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/query/model/PipelineResultType.java @@ -22,5 +22,5 @@ public enum PipelineResultType { BACKEND_COLUMN, HG_ELEMENT, NULL, - ERROR; + ERROR } diff --git a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/query/model/QueryPlan.java b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/query/model/QueryPlan.java index bd412f89c3..9ff76910e5 100644 --- a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/query/model/QueryPlan.java +++ b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/query/model/QueryPlan.java @@ -17,17 +17,18 @@ package org.apache.hugegraph.store.node.grpc.query.model; -import org.apache.hugegraph.store.node.grpc.query.QueryStage; -import org.apache.hugegraph.store.node.grpc.query.stages.EarlyStopException; - import java.util.ArrayList; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.stream.Collectors; +import org.apache.hugegraph.store.node.grpc.query.QueryStage; +import org.apache.hugegraph.store.node.grpc.query.stages.EarlyStopException; + public class QueryPlan { - private List stages; + + private final List stages; public QueryPlan() { stages = new LinkedList<>(); @@ -37,7 +38,7 @@ public void addStage(QueryStage pipeline) { this.stages.add(pipeline); } - public boolean onlyStopStage(){ + public boolean onlyStopStage() { return stages.size() == 1 && "STOP_STAGE".equals(stages.get(0).getName()); } @@ -54,7 +55,8 @@ public boolean containsAggStage() { * execute pipeline * * @param data the input data - * @return null when filtered or limited, iterator when encounter an iterator stage, or element when plain pipeline + * @return null when filtered or limited, iterator when encounter an iterator stage, or + * element when plain pipeline * @throws EarlyStopException throws early stop exception when reach the limit of limit stage */ public Object execute(PipelineResult data) throws EarlyStopException { @@ -94,8 +96,9 @@ public Object execute(PipelineResult data) throws EarlyStopException { return next.iterator(); } - private void callStage(QueryStage stage, List list, PipelineResult pre) throws EarlyStopException { - Object ret ; + private void callStage(QueryStage stage, List list, PipelineResult pre) throws + EarlyStopException { + Object ret; if (stage.isIterator()) { ret = stage.handleIterator(pre); } else { @@ -109,12 +112,13 @@ private void callStage(QueryStage stage, List list, PipelineResult pre) @Override public String toString() { - var names = String.join(", ", stages.stream().map(QueryStage::getName).collect(Collectors.toList())); + var names = String.join(", ", stages.stream().map(QueryStage::getName) + .collect(Collectors.toList())); return "QueryPlan{" + "stages=[" + names + "]}"; } public void clear() { - for (var stage: stages) { + for (var stage : stages) { stage.close(); } this.stages.clear(); diff --git a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/query/stages/AggStage.java b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/query/stages/AggStage.java index 0292cc58d2..6fc0e782c4 100644 --- a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/query/stages/AggStage.java +++ b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/query/stages/AggStage.java @@ -17,6 +17,17 @@ package org.apache.hugegraph.store.node.grpc.query.stages; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.ObjectOutputStream; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.Collectors; + import org.apache.hugegraph.store.business.itrv2.FileObjectIterator; import org.apache.hugegraph.store.business.itrv2.TypeTransIterator; import org.apache.hugegraph.store.business.itrv2.io.SortShuffleSerializer; @@ -31,17 +42,6 @@ import org.apache.hugegraph.store.util.MultiKv; import org.apache.hugegraph.store.util.SortShuffle; -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.ObjectOutputStream; -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; -import java.util.stream.Collectors; - /** * 聚合计算 */ @@ -73,25 +73,25 @@ public boolean isIterator() { public void init(Object... objects) { this.funcMetas = (List>) objects[0]; functionSize = funcMetas.size(); - path = SortShuffle.getBasePath() + "agg_tmp_" + Thread.currentThread().getId() + "/"; + path = SortShuffle.getBasePath() + "agg_tmp_" + Thread.currentThread().getId() + "/"; new File(path).mkdirs(); } /** * 将迭代器中的数据进行处理,并返回结果的迭代器 * - * @param result 数据结果对象 - * @return 返回处理后的迭代器 + * @param result 数据结果对象 + * @return 返回处理后的迭代器 */ @Override public Iterator handleIterator(PipelineResult result) { if (result.getResultType() == PipelineResultType.MKV) { var kv = result.getKv(); - if (! maps.containsKey(kv.getKeys())) { + if (!maps.containsKey(kv.getKeys())) { maps.putIfAbsent(kv.getKeys(), generateFunctions()); } - for (int i = 0 ; i < functionSize; i ++) { + for (int i = 0; i < functionSize; i++) { var function = maps.get(kv.getKeys()).get(i); Object value = kv.getValues().get(i); if (function instanceof AggregationFunctions.AvgFunction) { @@ -115,13 +115,14 @@ public Iterator handleIterator(PipelineResult result) { var list = changeToList(); if (this.file == null) { return new TypeTransIterator<>(list.iterator(), PipelineResult::new, - () -> PipelineResult.EMPTY).toIterator(); + () -> PipelineResult.EMPTY).toIterator(); } else { writeToFile(list); return new TypeTransIterator<>( - new FileObjectIterator<>(this.file, SortShuffleSerializer.ofBackendColumnSerializer()), + new FileObjectIterator<>(this.file, + SortShuffleSerializer.ofBackendColumnSerializer()), PipelineResult::new, () -> PipelineResult.EMPTY - ).toIterator(); + ).toIterator(); } } @@ -130,7 +131,8 @@ public Iterator handleIterator(PipelineResult result) { /** * avg 函数的隐式转换 - * @param clz the class type of the value + * + * @param clz the class type of the value * @param value value * @return Double value */ @@ -174,11 +176,11 @@ private List generateFunctions() { private List changeToList() { List result = new ArrayList<>(); - for (var entry: this.maps.entrySet()) { + for (var entry : this.maps.entrySet()) { result.add(new MultiKv(entry.getKey(), - entry.getValue().stream() - .map(x -> x.getBuffer()) - .collect(Collectors.toList()))); + entry.getValue().stream() + .map(x -> x.getBuffer()) + .collect(Collectors.toList()))); } result.sort(MultiKv::compareTo); diff --git a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/query/stages/DeserializationStage.java b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/query/stages/DeserializationStage.java index 4b229926f1..e1828de4ba 100644 --- a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/query/stages/DeserializationStage.java +++ b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/query/stages/DeserializationStage.java @@ -52,7 +52,7 @@ public PipelineResult handle(PipelineResult result) { return result; } var column = result.getColumn(); - if (column.value == null) { + if (column.value == null) { return null; } try { @@ -61,7 +61,7 @@ public PipelineResult handle(PipelineResult result) { QueryUtil.isVertex(this.table)); return new PipelineResult(element); } catch (Exception e) { - log.error("Deserialization error: {}", graph, e); + log.error("Deserialization error: {}", graph, e); return null; } } diff --git a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/query/stages/EarlyStopException.java b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/query/stages/EarlyStopException.java index 70ad64a460..7a64f37461 100644 --- a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/query/stages/EarlyStopException.java +++ b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/query/stages/EarlyStopException.java @@ -17,5 +17,6 @@ package org.apache.hugegraph.store.node.grpc.query.stages; -public class EarlyStopException extends Exception{ +public class EarlyStopException extends Exception { + } diff --git a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/query/stages/ExtractAggFieldStage.java b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/query/stages/ExtractAggFieldStage.java index 490f267606..2bfd360ae0 100644 --- a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/query/stages/ExtractAggFieldStage.java +++ b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/query/stages/ExtractAggFieldStage.java @@ -17,6 +17,9 @@ package org.apache.hugegraph.store.node.grpc.query.stages; +import java.util.List; +import java.util.stream.Collectors; + import org.apache.hugegraph.id.Id; import org.apache.hugegraph.store.node.grpc.query.QueryStage; import org.apache.hugegraph.store.node.grpc.query.QueryUtil; @@ -24,15 +27,14 @@ import org.apache.hugegraph.store.node.grpc.query.model.PipelineResultType; import org.apache.hugegraph.store.util.MultiKv; import org.apache.hugegraph.structure.BaseElement; -import com.google.protobuf.ByteString; -import java.util.List; -import java.util.stream.Collectors; +import com.google.protobuf.ByteString; /** * 提取聚合函数所需字段 */ public class ExtractAggFieldStage implements QueryStage { + private List groupBys; private List fields; @@ -66,22 +68,22 @@ public PipelineResult handle(PipelineResult result) { return null; } - if (this.groupByElementSchemaId && ! result.isEmpty()) { + if (this.groupByElementSchemaId && !result.isEmpty()) { return new PipelineResult(MultiKv.of(List.of(QueryUtil.getLabelId(result.getColumn(), this.isVertex)), List.of(1L))); } else if (result.getResultType() == PipelineResultType.HG_ELEMENT) { var element = result.getElement(); return new PipelineResult(MultiKv.of(getFields(this.groupBys, element), - getFields(this.fields, element))); + getFields(this.fields, element))); } return result; } private List getFields(List ids, BaseElement element) { return ids.stream() - .map(id -> id == null ? null : element.getPropertyValue(id)) - .collect(Collectors.toList()); + .map(id -> id == null ? null : element.getPropertyValue(id)) + .collect(Collectors.toList()); } private List getSchemaId(BaseElement element) { diff --git a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/query/stages/FilterStage.java b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/query/stages/FilterStage.java index 0e6f03aaa9..6f2e564770 100644 --- a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/query/stages/FilterStage.java +++ b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/query/stages/FilterStage.java @@ -35,7 +35,7 @@ public void init(Object... objects) { @Override public PipelineResult handle(PipelineResult result) { - if (result == null || result.isEmpty()){ + if (result == null || result.isEmpty()) { return result; } @@ -44,7 +44,7 @@ public PipelineResult handle(PipelineResult result) { } if (conditionQUery.resultType().isVertex() || conditionQUery.resultType().isEdge()) { - if (! conditionQUery.test(result.getElement())){ + if (!conditionQUery.test(result.getElement())) { return null; } } diff --git a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/query/stages/LimitStage.java b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/query/stages/LimitStage.java index 0445611f8a..7c024d2c3a 100644 --- a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/query/stages/LimitStage.java +++ b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/query/stages/LimitStage.java @@ -17,12 +17,12 @@ package org.apache.hugegraph.store.node.grpc.query.stages; -import org.apache.hugegraph.store.node.grpc.query.QueryStage; -import org.apache.hugegraph.store.node.grpc.query.model.PipelineResult; - import java.util.Objects; import java.util.concurrent.atomic.AtomicLong; +import org.apache.hugegraph.store.node.grpc.query.QueryStage; +import org.apache.hugegraph.store.node.grpc.query.model.PipelineResult; + /** * 限制N */ @@ -39,7 +39,8 @@ public void init(Object... objects) { @Override public PipelineResult handle(PipelineResult result) throws EarlyStopException { - if (Objects.equals(result, PipelineResult.EMPTY) || counter.getAndIncrement() < this.limit) { + if (Objects.equals(result, PipelineResult.EMPTY) || + counter.getAndIncrement() < this.limit) { return result; } throw new EarlyStopException(); diff --git a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/query/stages/OlapStage.java b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/query/stages/OlapStage.java index 467976ce80..8fecd9ef78 100644 --- a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/query/stages/OlapStage.java +++ b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/query/stages/OlapStage.java @@ -17,6 +17,11 @@ package org.apache.hugegraph.store.node.grpc.query.stages; +import static org.apache.hugegraph.store.constant.HugeServerTables.OLAP_TABLE; + +import java.util.ArrayList; +import java.util.List; + import org.apache.hugegraph.backend.BackendColumn; import org.apache.hugegraph.id.Id; import org.apache.hugegraph.pd.common.PartitionUtils; @@ -29,12 +34,8 @@ import org.apache.hugegraph.store.node.grpc.query.model.PipelineResult; import org.apache.hugegraph.store.node.grpc.query.model.PipelineResultType; import org.apache.hugegraph.structure.BaseVertex; -import com.google.protobuf.ByteString; -import java.util.ArrayList; -import java.util.List; - -import static org.apache.hugegraph.store.constant.HugeServerTables.OLAP_TABLE; +import com.google.protobuf.ByteString; import lombok.extern.slf4j.Slf4j; @@ -43,14 +44,12 @@ */ @Slf4j public class OlapStage implements QueryStage { - private String graph; - - private String table; - - private List properties; private final BusinessHandler handler = new QueryUtil().getHandler(); private final BinaryElementSerializer serializer = new BinaryElementSerializer(); + private String graph; + private String table; + private List properties; @Override public void init(Object... objects) { @@ -61,18 +60,19 @@ public void init(Object... objects) { @Override public PipelineResult handle(PipelineResult result) { - if (result == null){ + if (result == null) { return null; } if (result.getResultType() == PipelineResultType.HG_ELEMENT) { var element = result.getElement(); - var code = PartitionUtils.calcHashcode(BinaryElementSerializer.ownerId(element).asBytes()); + var code = + PartitionUtils.calcHashcode(BinaryElementSerializer.ownerId(element).asBytes()); for (Id property : properties) { // 构建 key var key = getOlapKey(property, element.id()); - var values = handler.doGet(this.graph, code, OLAP_TABLE, key); + var values = handler.doGet(this.graph, code, OLAP_TABLE, key); if (values != null) { var column = BackendColumn.of(key, values); QueryUtil.parseOlap(column, (BaseVertex) element); @@ -106,7 +106,8 @@ public PipelineResult handle(PipelineResult result) { } private byte[] getOlapKey(Id propertyId, Id vertexId) { - BytesBuffer bufferName = BytesBuffer.allocate(1 + propertyId.length() + 1 + vertexId.length()); + BytesBuffer bufferName = + BytesBuffer.allocate(1 + propertyId.length() + 1 + vertexId.length()); bufferName.writeId(propertyId); return bufferName.writeId(vertexId).bytes(); } diff --git a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/query/stages/OrderByStage.java b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/query/stages/OrderByStage.java index 56ddb2c582..ecce9a26b0 100644 --- a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/query/stages/OrderByStage.java +++ b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/query/stages/OrderByStage.java @@ -17,6 +17,10 @@ package org.apache.hugegraph.store.node.grpc.query.stages; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + import org.apache.hugegraph.id.Id; import org.apache.hugegraph.store.business.itrv2.TypeTransIterator; import org.apache.hugegraph.store.business.itrv2.io.SortShuffleSerializer; @@ -29,18 +33,17 @@ import org.apache.hugegraph.store.util.MultiKv; import org.apache.hugegraph.store.util.SortShuffle; import org.apache.hugegraph.structure.BaseElement; + import com.google.protobuf.ByteString; -import lombok.extern.slf4j.Slf4j; -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; +import lombok.extern.slf4j.Slf4j; /** * 排序 */ @Slf4j public class OrderByStage implements QueryStage { + private SortShuffle sortShuffle; private Iterator iterator; @@ -58,19 +61,20 @@ public void init(Object... objects) { // agg if ((Boolean) objects[2]) { if (orderBys == null) { - sortShuffle = new SortShuffle<>(MultiKv::compareTo, SortShuffleSerializer.ofMultiKvSerializer()); + sortShuffle = new SortShuffle<>(MultiKv::compareTo, + SortShuffleSerializer.ofMultiKvSerializer()); } else { List orders = new ArrayList<>(); for (Id id : orderBys) { orders.add(groupBys.indexOf(id)); } sortShuffle = new SortShuffle<>(new MultiKeyComparator(orders), - SortShuffleSerializer.ofMultiKvSerializer()); + SortShuffleSerializer.ofMultiKvSerializer()); } resultType = PipelineResultType.MKV; } else { sortShuffle = new SortShuffle<>(new BaseElementComparator(orderBys, this.isAsc), - SortShuffleSerializer.ofBaseElementSerializer()); + SortShuffleSerializer.ofBaseElementSerializer()); resultType = PipelineResultType.HG_ELEMENT; } @@ -86,7 +90,7 @@ public Iterator handleIterator(PipelineResult result) { if (result == null) { return null; } - if (! result.isEmpty()) { + if (!result.isEmpty()) { try { if (result.getResultType() == PipelineResultType.MKV) { sortShuffle.append(result.getKv()); @@ -110,6 +114,7 @@ public Iterator handleIterator(PipelineResult result) { return new TypeTransIterator(new Iterator<>() { private boolean closeFlag = false; + @Override public boolean hasNext() { var ret = iterator.hasNext(); diff --git a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/query/stages/ProjectionStage.java b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/query/stages/ProjectionStage.java index d1f057ad80..2975668a53 100644 --- a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/query/stages/ProjectionStage.java +++ b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/query/stages/ProjectionStage.java @@ -17,22 +17,23 @@ package org.apache.hugegraph.store.node.grpc.query.stages; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + import org.apache.hugegraph.id.Id; import org.apache.hugegraph.store.node.grpc.query.QueryStage; import org.apache.hugegraph.store.node.grpc.query.QueryUtil; import org.apache.hugegraph.store.node.grpc.query.model.PipelineResult; import org.apache.hugegraph.store.node.grpc.query.model.PipelineResultType; -import com.google.protobuf.ByteString; - -import java.util.HashSet; -import java.util.List; -import java.util.Set; +import com.google.protobuf.ByteString; /** * 剪裁 */ public class ProjectionStage implements QueryStage { + private Set propertySet; private boolean removeAllProperty; @@ -51,13 +52,14 @@ public PipelineResult handle(PipelineResult result) { if (result.getResultType() == PipelineResultType.HG_ELEMENT) { var element = result.getElement(); - for (var id : element.getProperties().entrySet()){ - if (! this.propertySet.contains(id.getKey()) || this.removeAllProperty) { + for (var id : element.getProperties().entrySet()) { + if (!this.propertySet.contains(id.getKey()) || this.removeAllProperty) { element.removeProperty(id.getKey()); } } return result; - } else if (result.getResultType() == PipelineResultType.BACKEND_COLUMN && this.removeAllProperty){ + } else if (result.getResultType() == PipelineResultType.BACKEND_COLUMN && + this.removeAllProperty) { var column = result.getColumn(); column.value = new byte[0]; } diff --git a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/query/stages/SampleStage.java b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/query/stages/SampleStage.java index d391efe3e9..f2ec598321 100644 --- a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/query/stages/SampleStage.java +++ b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/query/stages/SampleStage.java @@ -17,12 +17,12 @@ package org.apache.hugegraph.store.node.grpc.query.stages; -import org.apache.hugegraph.store.node.grpc.query.QueryStage; -import org.apache.hugegraph.store.node.grpc.query.model.PipelineResult; - import java.util.Objects; import java.util.Random; +import org.apache.hugegraph.store.node.grpc.query.QueryStage; +import org.apache.hugegraph.store.node.grpc.query.model.PipelineResult; + /** * 抽样 */ diff --git a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/query/stages/SimpleCountStage.java b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/query/stages/SimpleCountStage.java index e7956c9451..b20ac4cfba 100644 --- a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/query/stages/SimpleCountStage.java +++ b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/query/stages/SimpleCountStage.java @@ -17,14 +17,14 @@ package org.apache.hugegraph.store.node.grpc.query.stages; -import org.apache.hugegraph.store.node.grpc.query.QueryStage; -import org.apache.hugegraph.store.node.grpc.query.model.PipelineResult; -import org.apache.hugegraph.store.util.MultiKv; +import static org.apache.hugegraph.store.node.grpc.query.QueryUtil.EMPTY_AGG_KEY; import java.util.ArrayList; import java.util.List; -import static org.apache.hugegraph.store.node.grpc.query.QueryUtil.EMPTY_AGG_KEY; +import org.apache.hugegraph.store.node.grpc.query.QueryStage; +import org.apache.hugegraph.store.node.grpc.query.model.PipelineResult; +import org.apache.hugegraph.store.util.MultiKv; /** * 简单的count计数 diff --git a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/query/stages/TopStage.java b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/query/stages/TopStage.java index b25c54b995..873ad0366a 100644 --- a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/query/stages/TopStage.java +++ b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/query/stages/TopStage.java @@ -17,6 +17,11 @@ package org.apache.hugegraph.store.node.grpc.query.stages; +import java.util.Iterator; +import java.util.List; +import java.util.PriorityQueue; +import java.util.concurrent.PriorityBlockingQueue; + import org.apache.hugegraph.store.business.itrv2.TypeTransIterator; import org.apache.hugegraph.store.node.grpc.query.QueryStage; import org.apache.hugegraph.store.node.grpc.query.QueryUtil; @@ -24,14 +29,11 @@ import org.apache.hugegraph.store.node.grpc.query.model.PipelineResultType; import org.apache.hugegraph.store.query.BaseElementComparator; import org.apache.hugegraph.structure.BaseElement; -import com.google.protobuf.ByteString; -import java.util.Iterator; -import java.util.List; -import java.util.PriorityQueue; -import java.util.concurrent.PriorityBlockingQueue; +import com.google.protobuf.ByteString; public class TopStage implements QueryStage { + private PriorityBlockingQueue queue; private BaseElementComparator comparator; @@ -46,8 +48,9 @@ public void init(Object... objects) { this.isAsc = (boolean) objects[2]; // 需要构建一个相反的堆 - this.comparator = new BaseElementComparator(QueryUtil.fromStringBytes((List) objects[1]), - !isAsc); + this.comparator = + new BaseElementComparator(QueryUtil.fromStringBytes((List) objects[1]), + !isAsc); this.queue = new PriorityBlockingQueue<>(limit, this.comparator); } diff --git a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/query/stages/TtlCheckStage.java b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/query/stages/TtlCheckStage.java index 6888337cf9..315ec2d31d 100644 --- a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/query/stages/TtlCheckStage.java +++ b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/query/stages/TtlCheckStage.java @@ -29,10 +29,11 @@ */ @Slf4j public class TtlCheckStage implements QueryStage { + private boolean isVertex; - private DirectBinarySerializer serializer = new DirectBinarySerializer(); - private long now ; + private final DirectBinarySerializer serializer = new DirectBinarySerializer(); + private long now; @Override public void init(Object... objects) { diff --git a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/scan/ScanResponseObserver.java b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/scan/ScanResponseObserver.java index 69b44844f7..f1d7a3b53b 100644 --- a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/scan/ScanResponseObserver.java +++ b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/scan/ScanResponseObserver.java @@ -46,11 +46,11 @@ public class ScanResponseObserver implements StreamObserver { - private static int batchSize = 100000; private static final int MAX_PAGE = 8; // private static final Error ok = Error.newBuilder().setType(ErrorType.OK).build(); private static final ResponseHeader okHeader = ResponseHeader.newBuilder().setError(ok).build(); + private static int batchSize = 100000; private final BusinessHandler handler; private final AtomicInteger nextSeqNo = new AtomicInteger(0); private final AtomicInteger cltSeqNo = new AtomicInteger(0); @@ -82,7 +82,8 @@ public class ScanResponseObserver implements * November 2, 2022 * 1. Read the thread of rocksdb iterator read * 2. Perform data conversion and send to the blocking queue thread offer - * 3. Thread for reading data from the blocking queue and sending, including waking up the reading and sending threads when no data is read + * 3. Thread for reading data from the blocking queue and sending, including waking up the + * reading and sending threads when no data is read * */ public ScanResponseObserver(StreamObserver sender, @@ -219,8 +220,6 @@ private void close() { } } catch (Exception e) { log.warn("on Complete with error:", e); - } finally { - } } @@ -247,7 +246,7 @@ public void run() { break; } } - if (iter.getStopCause() != null){ + if (iter.getStopCause() != null) { throw iter.getStopCause(); } if (!(iter.hasNext() && leftCount > -1)) { diff --git a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/listener/ContextClosedListener.java b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/listener/ContextClosedListener.java index 133d8894d6..bcca6111b0 100644 --- a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/listener/ContextClosedListener.java +++ b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/listener/ContextClosedListener.java @@ -20,16 +20,15 @@ import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ThreadPoolExecutor; -import com.alipay.sofa.jraft.Status; -import com.alipay.sofa.jraft.entity.PeerId; - import org.apache.hugegraph.store.HgStoreEngine; +import org.apache.hugegraph.store.node.grpc.HgStoreStreamImpl; +import org.apache.hugegraph.store.node.task.TTLCleaner; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationListener; import org.springframework.context.event.ContextClosedEvent; -import org.apache.hugegraph.store.node.grpc.HgStoreStreamImpl; -import org.apache.hugegraph.store.node.task.TTLCleaner; +import com.alipay.sofa.jraft.Status; +import com.alipay.sofa.jraft.entity.PeerId; import lombok.extern.slf4j.Slf4j; diff --git a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/listener/PdConfigureListener.java b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/listener/PdConfigureListener.java index 9f873b4ffb..015982ba2b 100644 --- a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/listener/PdConfigureListener.java +++ b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/listener/PdConfigureListener.java @@ -103,7 +103,8 @@ public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) { client.listen(TIMESTAMP_KEY, (Consumer) o -> { log.info("receive message to restart :" + o); try { - // Prioritize updating the latest configuration file to avoid old files being loaded first when modifying parameters like ports. + // Prioritize updating the latest configuration file to avoid old files being + // loaded first when modifying parameters like ports. ScanPrefixResponse responseNew = client.scanPrefix(CONFIG_PREFIX); Map kvsMapNew = responseNew.getKvsMap(); String config = kvsMapNew.get(CONFIG_FIX_PREFIX); diff --git a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/listener/PlaceHolderListener.java b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/listener/PlaceHolderListener.java index 1ddbf69a2e..04f7377e98 100644 --- a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/listener/PlaceHolderListener.java +++ b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/listener/PlaceHolderListener.java @@ -23,11 +23,10 @@ import org.apache.commons.io.FileUtils; import org.apache.commons.lang.StringUtils; -import org.springframework.boot.context.event.ApplicationReadyEvent; -import org.springframework.context.ApplicationListener; - import org.apache.hugegraph.store.node.AppConfig; import org.apache.hugegraph.store.options.HgStoreEngineOptions; +import org.springframework.boot.context.event.ApplicationReadyEvent; +import org.springframework.context.ApplicationListener; import lombok.extern.slf4j.Slf4j; @@ -44,7 +43,7 @@ public void onApplicationEvent(ApplicationReadyEvent event) { String dataPath = config.getDataPath(); String[] paths = dataPath.split(","); Integer size = config.getPlaceholderSize(); - Arrays.stream(paths).parallel().forEach(path->{ + Arrays.stream(paths).parallel().forEach(path -> { if (!StringUtils.isEmpty(path)) { File ph = new File(path + "/" + HgStoreEngineOptions.PLACE_HOLDER_PREFIX); if (!ph.exists() && size > 0) { diff --git a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/metrics/JRaftMetrics.java b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/metrics/JRaftMetrics.java index 2dc151f6f4..07ef732446 100644 --- a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/metrics/JRaftMetrics.java +++ b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/metrics/JRaftMetrics.java @@ -127,27 +127,6 @@ private static void registerNodeMetrics() { } - private static class HistogramWrapper { - - private com.codahale.metrics.Histogram histogram; - - private Snapshot snapshot; - private long ts = System.currentTimeMillis(); - - HistogramWrapper(com.codahale.metrics.Histogram histogram) { - this.histogram = histogram; - this.snapshot = this.histogram.getSnapshot(); - } - - Snapshot getSnapshot() { - if (System.currentTimeMillis() - this.ts > 30_000) { - this.snapshot = this.histogram.getSnapshot(); - this.ts = System.currentTimeMillis(); - } - return this.snapshot; - } - } - private static HistogramWrapper toWrapper(com.codahale.metrics.Histogram histogram) { return new HistogramWrapper(histogram); } @@ -312,4 +291,25 @@ private static void registerGauge(String group, String name, } } + + private static class HistogramWrapper { + + private final com.codahale.metrics.Histogram histogram; + + private Snapshot snapshot; + private long ts = System.currentTimeMillis(); + + HistogramWrapper(com.codahale.metrics.Histogram histogram) { + this.histogram = histogram; + this.snapshot = this.histogram.getSnapshot(); + } + + Snapshot getSnapshot() { + if (System.currentTimeMillis() - this.ts > 30_000) { + this.snapshot = this.histogram.getSnapshot(); + this.ts = System.currentTimeMillis(); + } + return this.snapshot; + } + } } diff --git a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/metrics/ProcfsReader.java b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/metrics/ProcfsReader.java index c1891dcefc..435d1219dd 100644 --- a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/metrics/ProcfsReader.java +++ b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/metrics/ProcfsReader.java @@ -20,27 +20,24 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; -import java.util.*; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Objects; class ProcfsReader { + /* default */ static final long CACHE_DURATION_MS = 100; private static final Map instances = new HashMap<>(); - private static final Object instancesLock = new Object(); - private static final Map> data = new HashMap<>(); - private static final Object dataLock = new Object(); - private static final Path BASE = Paths.get("/proc", "self"); - - /* default */ static final long CACHE_DURATION_MS = 100; - - /* default */ long lastReadTime = -1; - private final Path entryPath; - private final boolean osSupport; + /* default */ long lastReadTime = -1; private ProcfsReader(String entry) { this(BASE, entry, false); @@ -57,7 +54,22 @@ private ProcfsReader(Path base, String entry, boolean forceOSSupport) { this.entryPath = base.resolve(entry); this.osSupport = forceOSSupport - || System.getProperty("os.name").toLowerCase(Locale.ENGLISH).startsWith("linux"); + || System.getProperty("os.name").toLowerCase(Locale.ENGLISH) + .startsWith("linux"); + } + + /* default */ + static ProcfsReader getInstance(String entry) { + Objects.requireNonNull(entry); + + synchronized (instancesLock) { + ProcfsReader reader = instances.get(entry); + if (reader == null) { + reader = new ProcfsReader(entry); + instances.put(entry, reader); + } + return reader; + } } /* default */ Path getEntryPath() { @@ -105,19 +117,6 @@ private ProcfsReader(Path base, String entry, boolean forceOSSupport) { return System.currentTimeMillis(); } - /* default */ static ProcfsReader getInstance(String entry) { - Objects.requireNonNull(entry); - - synchronized (instancesLock) { - ProcfsReader reader = instances.get(entry); - if (reader == null) { - reader = new ProcfsReader(entry); - instances.put(entry, reader); - } - return reader; - } - } - /* default */ static class ReadResult { private final List lines; diff --git a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/metrics/ProcfsSmaps.java b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/metrics/ProcfsSmaps.java index bf205a1830..e5fc1d39f1 100644 --- a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/metrics/ProcfsSmaps.java +++ b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/metrics/ProcfsSmaps.java @@ -16,37 +16,17 @@ */ package org.apache.hugegraph.store.node.metrics; -import java.util.*; +import java.util.Collection; +import java.util.EnumSet; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; import java.util.concurrent.atomic.AtomicLong; import java.util.function.LongUnaryOperator; public class ProcfsSmaps extends ProcfsEntry { - public enum KEY { - /** - * Virtual set size - */ - VSS, - /** - * Resident set size - */ - RSS, - /** - * Proportional set size - */ - PSS, - /** - * Paged out memory - */ - SWAP, - /** - * Paged out memory accounting shared pages. Since Linux 4.3. - */ - SWAPPSS - } - private static final int KILOBYTE = 1024; - private final Map values = new HashMap<>(); public ProcfsSmaps() { @@ -57,6 +37,12 @@ public ProcfsSmaps() { super(reader); } + private static long parseKiloBytes(String line) { + Objects.requireNonNull(line); + + return Long.parseLong(line.split("\\s+")[1]); + } + @Override protected void reset() { EnumSet.allOf(KEY.class).forEach(key -> values.put(key, new AtomicLong(-1))); @@ -101,10 +87,27 @@ public long applyAsLong(long currentValue) { }); } - private static long parseKiloBytes(String line) { - Objects.requireNonNull(line); - - return Long.parseLong(line.split("\\s+")[1]); + public enum KEY { + /** + * Virtual set size + */ + VSS, + /** + * Resident set size + */ + RSS, + /** + * Proportional set size + */ + PSS, + /** + * Paged out memory + */ + SWAP, + /** + * Paged out memory accounting shared pages. Since Linux 4.3. + */ + SWAPPSS } } diff --git a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/metrics/RocksDBMetricsConst.java b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/metrics/RocksDBMetricsConst.java index 075d4a1439..94bdc4c6bc 100644 --- a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/metrics/RocksDBMetricsConst.java +++ b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/metrics/RocksDBMetricsConst.java @@ -76,9 +76,11 @@ public final class RocksDBMetricsConst { TickerType.GET_HIT_L0, // Level 0 get hits. TickerType.GET_HIT_L1, // Level 1 get hits. TickerType.GET_HIT_L2_AND_UP, // Level 2 and above get hits. - TickerType.COMPACTION_KEY_DROP_NEWER_ENTRY, // Keys dropped due to newer entry during compaction. + TickerType.COMPACTION_KEY_DROP_NEWER_ENTRY, + // Keys dropped due to newer entry during compaction. TickerType.COMPACTION_KEY_DROP_OBSOLETE, // Obsolete keys dropped during compaction. - TickerType.COMPACTION_KEY_DROP_RANGE_DEL, // Range deletion keys dropped during compaction. + TickerType.COMPACTION_KEY_DROP_RANGE_DEL, + // Range deletion keys dropped during compaction. TickerType.COMPACTION_KEY_DROP_USER, // User keys dropped during compaction. TickerType.COMPACTION_RANGE_DEL_DROP_OBSOLETE, // Obsolete range deletes dropped. TickerType.NUMBER_KEYS_WRITTEN, // Total keys written. @@ -122,7 +124,8 @@ public final class RocksDBMetricsConst { TickerType.COMPACT_READ_BYTES, // Bytes read during compaction. TickerType.COMPACT_WRITE_BYTES, // Bytes written during compaction. TickerType.FLUSH_WRITE_BYTES, // Bytes written during flush. - TickerType.NUMBER_DIRECT_LOAD_TABLE_PROPERTIES, // Number of direct load table properties. + TickerType.NUMBER_DIRECT_LOAD_TABLE_PROPERTIES, + // Number of direct load table properties. TickerType.NUMBER_SUPERVERSION_ACQUIRES, // Acquired superversions. TickerType.NUMBER_SUPERVERSION_RELEASES, // Released superversions. TickerType.NUMBER_SUPERVERSION_CLEANUPS, // Cleanups of superversions. @@ -133,7 +136,8 @@ public final class RocksDBMetricsConst { TickerType.FILTER_OPERATION_TOTAL_TIME, // Time spent in filter operations. TickerType.ROW_CACHE_HIT, // Hits in row cache. TickerType.ROW_CACHE_MISS, // Misses in row cache. - TickerType.READ_AMP_ESTIMATE_USEFUL_BYTES, // Estimated useful bytes read due to read amplification. + TickerType.READ_AMP_ESTIMATE_USEFUL_BYTES, + // Estimated useful bytes read due to read amplification. TickerType.READ_AMP_TOTAL_READ_BYTES, // Total bytes read due to read amplification. TickerType.NUMBER_RATE_LIMITER_DRAINS, // Number of times rate limiter is drained. TickerType.NUMBER_ITER_SKIP, // Number of iterator skips. @@ -153,16 +157,19 @@ public final class RocksDBMetricsConst { HistogramType.COMPACTION_TIME, // Time spent in compactions. HistogramType.SUBCOMPACTION_SETUP_TIME, // Time spent setting up subcompactions. HistogramType.TABLE_SYNC_MICROS, // Time spent synchronizing tables. - HistogramType.COMPACTION_OUTFILE_SYNC_MICROS, // Time spent syncing compaction output files. + HistogramType.COMPACTION_OUTFILE_SYNC_MICROS, + // Time spent syncing compaction output files. HistogramType.WAL_FILE_SYNC_MICROS, // Time spent syncing WAL files. HistogramType.MANIFEST_FILE_SYNC_MICROS, // Time spent syncing manifest files. HistogramType.TABLE_OPEN_IO_MICROS, // Time spent opening tables (I/O). HistogramType.DB_MULTIGET, // Latency of database multi-get operations. - HistogramType.READ_BLOCK_COMPACTION_MICROS, // Time spent reading blocks during compaction. + HistogramType.READ_BLOCK_COMPACTION_MICROS, + // Time spent reading blocks during compaction. HistogramType.READ_BLOCK_GET_MICROS, // Time spent reading blocks during get. HistogramType.WRITE_RAW_BLOCK_MICROS, // Time spent writing raw blocks. HistogramType.STALL_L0_SLOWDOWN_COUNT, // Count of stalls due to L0 slowdown. - HistogramType.STALL_MEMTABLE_COMPACTION_COUNT, // Count of stalls due to memtable compaction. + HistogramType.STALL_MEMTABLE_COMPACTION_COUNT, + // Count of stalls due to memtable compaction. HistogramType.STALL_L0_NUM_FILES_COUNT, // Count of stalls due to number of files at L0. HistogramType.HARD_RATE_LIMIT_DELAY_COUNT, // Count of delays due to hard rate limits. HistogramType.SOFT_RATE_LIMIT_DELAY_COUNT, // Count of delays due to soft rate limits. diff --git a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/metrics/SystemMemoryStats.java b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/metrics/SystemMemoryStats.java index 4f315f1ace..917a4525b9 100644 --- a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/metrics/SystemMemoryStats.java +++ b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/metrics/SystemMemoryStats.java @@ -16,13 +16,6 @@ */ package org.apache.hugegraph.store.node.metrics; -import java.util.Collection; -import java.util.EnumSet; -import java.util.HashMap; -import java.util.Map; -import java.util.Objects; -import java.util.concurrent.atomic.AtomicLong; - //@Deprecated //public class SystemMemoryStats extends ProcfsRecord { // diff --git a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/util/Base58.java b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/util/Base58.java index 0a1450e53c..b3da787a88 100644 --- a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/util/Base58.java +++ b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/util/Base58.java @@ -19,10 +19,12 @@ import java.io.UnsupportedEncodingException; import java.math.BigInteger; +import java.nio.charset.StandardCharsets; public class Base58 { - public static final char[] ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz".toCharArray(); + public static final char[] ALPHABET = + "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz".toCharArray(); private static final int[] INDEXES = new int[128]; static { @@ -70,11 +72,7 @@ public static String encode(byte[] input) { } byte[] output = copyOfRange(temp, j, temp.length); - try { - return new String(output, "US-ASCII"); - } catch (UnsupportedEncodingException e) { - throw new RuntimeException(e); // Cannot happen. - } + return new String(output, StandardCharsets.US_ASCII); } public static byte[] decode(String input) throws IllegalArgumentException { @@ -167,6 +165,5 @@ private static byte[] copyOfRange(byte[] source, int from, int to) { return range; } - } diff --git a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/util/HgRegexUtil.java b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/util/HgRegexUtil.java index 051a964711..e5e9659148 100644 --- a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/util/HgRegexUtil.java +++ b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/util/HgRegexUtil.java @@ -29,7 +29,7 @@ */ public class HgRegexUtil { - private static Map patternMap = new LinkedHashMap<>(); + private static final Map patternMap = new LinkedHashMap<>(); public static String getGroupValue(String regex, String source, int groupId) { if (regex == null || "".equals(regex) || source == null || "".equals(source)) { From 98256cebc99953acb3b2894bc08bf02183924e3e Mon Sep 17 00:00:00 2001 From: JisoLya <523420504@qq.com> Date: Thu, 14 Aug 2025 14:56:06 +0800 Subject: [PATCH 24/35] fix: fix some conflict --- .../hugegraph/store/meta/GraphIdManager.java | 2 +- .../store/meta/base/PartitionMetaStore.java | 2 +- .../store/options/HgStoreEngineOptions.java | 2 +- .../store/node/grpc/EmptyIterator.java | 2 +- .../store/node/grpc/HgStoreNodeService.java | 27 ++++++++++--------- .../store/node/grpc/HgStoreStateService.java | 3 ++- .../node/grpc/query/model/QueryPlan.java | 4 +-- .../node/task/ttl/RaftTaskSubmitter.java | 5 +++- 8 files changed, 26 insertions(+), 21 deletions(-) diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/meta/GraphIdManager.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/meta/GraphIdManager.java index d3cc00f216..92a8440ac3 100644 --- a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/meta/GraphIdManager.java +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/meta/GraphIdManager.java @@ -177,7 +177,7 @@ protected long getCId(String key, long max) { /** * Return key with used Cid */ - private byte[] genCIDSlotKey(String key, long value) { + public byte[] genCIDSlotKey(String key, long value) { byte[] keySlot = MetadataKeyHelper.getCidSlotKeyPrefix(key); ByteBuffer buf = ByteBuffer.allocate(keySlot.length + Long.SIZE); buf.put(keySlot); diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/meta/base/PartitionMetaStore.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/meta/base/PartitionMetaStore.java index 948b5ccc27..fd47d0689e 100644 --- a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/meta/base/PartitionMetaStore.java +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/meta/base/PartitionMetaStore.java @@ -44,7 +44,7 @@ protected String getCFName() { return DEFAULT_CF_NAME; } - protected void flush() { + public void flush() { try (RocksDBSession dbSession = getRocksDBSession()) { dbSession.flush(true); } diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/options/HgStoreEngineOptions.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/options/HgStoreEngineOptions.java index a76c760892..aa5a1af109 100644 --- a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/options/HgStoreEngineOptions.java +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/options/HgStoreEngineOptions.java @@ -43,7 +43,7 @@ public class HgStoreEngineOptions { private final int partitionHBInterval = 5; // Waiting for leader timeout, in seconds private final int waitLeaderTimeout = 30; - private final int raftRpcThreadPoolSize = Utils.cpus() * 6; + private int raftRpcThreadPoolSize = Utils.cpus() * 6; private int raftRpcThreadPoolSizeOfBasic = 256; // No PD mode, for development and debugging use only private boolean fakePD = false; diff --git a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/EmptyIterator.java b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/EmptyIterator.java index a6d2b6283d..6648802e0f 100644 --- a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/EmptyIterator.java +++ b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/EmptyIterator.java @@ -22,7 +22,7 @@ /** * 2021/11/29 */ -final class EmptyIterator implements ScanIterator { +public final class EmptyIterator implements ScanIterator { @Override public boolean hasNext() { diff --git a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/HgStoreNodeService.java b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/HgStoreNodeService.java index d742d172af..ba8d4a39a8 100644 --- a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/HgStoreNodeService.java +++ b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/HgStoreNodeService.java @@ -82,7 +82,8 @@ public HgStoreEngine getStoreEngine() { public void init() { log.info("{}", appConfig.toString()); HgStoreEngineOptions options = new HgStoreEngineOptions() {{ - setRaftAddress(appConfig.getRaft().getAddress()); + AppConfig.Raft raft = appConfig.getRaft(); + setRaftAddress(raft.getAddress()); setDataPath(appConfig.getDataPath()); setRaftPath(appConfig.getRaftPath()); setPdAddress(appConfig.getPdServerAddress()); @@ -91,18 +92,18 @@ public void init() { setGrpcAddress(appConfig.getStoreServerAddress()); setLabels(appConfig.getLabelConfig().getLabel()); setRaftOptions(new RaftOptions() {{ - setMetrics(appConfig.getRaft().isMetrics()); - setRpcDefaultTimeout(appConfig.getRaft().getRpcTimeOut()); - setSnapshotLogIndexMargin(appConfig.getRaft().getSnapshotLogIndexMargin()); - setSnapshotIntervalSecs(appConfig.getRaft().getSnapshotInterval()); - setDisruptorBufferSize(appConfig.getRaft().getDisruptorBufferSize()); - setMaxLogSize(appConfig.getRaft().getMaxLogFileSize()); - setAveLogEntrySizeRatio(appConfig.getRaft().getAveLogEntrySizeRation()); - setUseRocksDBSegmentLogStorage(appConfig.getRaft() - .isUseRocksDBSegmentLogStorage()); - setMaxSegmentFileSize(appConfig.getRaft().getMaxSegmentFileSize()); - setMaxReplicatorInflightMsgs(appConfig.getRaft().getMaxReplicatorInflightMsgs()); - if (appConfig.getRaft().getRpcPoolSizeByMultipleOfCPU() > 0) { + setMetrics(raft.isMetrics()); + setRpcDefaultTimeout(raft.getRpcTimeOut()); + setSnapshotLogIndexMargin(raft.getSnapshotLogIndexMargin()); + setSnapshotIntervalSecs(raft.getSnapshotInterval()); + setSnapshotDownloadingThreads(raft.getSnapshotDownloadingThread()); + setDisruptorBufferSize(raft.getDisruptorBufferSize()); + setMaxLogSize(raft.getMaxLogFileSize()); + setAveLogEntrySizeRatio(raft.getAveLogEntrySizeRation()); + setUseRocksDBSegmentLogStorage(raft.isUseRocksDBSegmentLogStorage()); + setMaxSegmentFileSize(raft.getMaxSegmentFileSize()); + setMaxReplicatorInflightMsgs(raft.getMaxReplicatorInflightMsgs()); + if (raft.getRpcPoolSizeByMultipleOfCPU() > 0) { setRaftRpcThreadPoolSize(Utils.cpus() * raft.getRpcPoolSizeByMultipleOfCPU()); } setRaftRpcThreadPoolSizeOfBasic(raft.getRpcPoolSizeOfBasic()); diff --git a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/HgStoreStateService.java b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/HgStoreStateService.java index 0fddb86bef..c051f227b2 100644 --- a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/HgStoreStateService.java +++ b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/HgStoreStateService.java @@ -72,7 +72,8 @@ public void getPeers(PartitionRequest request, StreamObserver obs StringBuilder result = new StringBuilder(); if (engine != null) { Node raftNode = engine.getRaftNode(); - Configuration conf = raftNode.getCurrentConf(); + //todo soya need to check + Configuration conf = new Configuration(raftNode.listPeers(), raftNode.listLearners()); List peers = conf.getPeers(); for (PeerId id : peers) { result.append(id.getEndpoint().toString()); diff --git a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/query/model/QueryPlan.java b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/query/model/QueryPlan.java index 9ff76910e5..0bd1e33dd2 100644 --- a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/query/model/QueryPlan.java +++ b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/query/model/QueryPlan.java @@ -43,9 +43,9 @@ public boolean onlyStopStage() { } /** - * 判断聚合阶段是否存在。 + * judge if there is aggregation stage * - * @return 如果存在聚合阶段则返回true,否则返回false。 + * @return return false if not */ public boolean containsAggStage() { return stages.stream().anyMatch(stage -> stage.getName().equals("AGG_STAGE")); diff --git a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/task/ttl/RaftTaskSubmitter.java b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/task/ttl/RaftTaskSubmitter.java index 15abb74e66..6d36f0e224 100644 --- a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/task/ttl/RaftTaskSubmitter.java +++ b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/task/ttl/RaftTaskSubmitter.java @@ -40,10 +40,11 @@ import lombok.extern.slf4j.Slf4j; -@Slf4j + /** * @date 2024/5/7 **/ +@Slf4j public class RaftTaskSubmitter extends TaskSubmitter { public RaftTaskSubmitter(HgStoreNodeService service, BusinessHandler handler) { @@ -102,3 +103,5 @@ public Status submitCompaction(Integer id) { return Status.OK(); } } + + From 4558d8f47a86b30cfbaafb20fa789b675ccb8437 Mon Sep 17 00:00:00 2001 From: JisoLya <523420504@qq.com> Date: Thu, 14 Aug 2025 17:44:44 +0800 Subject: [PATCH 25/35] fix: add missing method in struct module --- .../serializer/BinaryElementSerializer.java | 58 +++++++++++++------ 1 file changed, 40 insertions(+), 18 deletions(-) diff --git a/hugegraph-struct/src/main/java/org/apache/hugegraph/serializer/BinaryElementSerializer.java b/hugegraph-struct/src/main/java/org/apache/hugegraph/serializer/BinaryElementSerializer.java index 8e18ccbba6..18476843c7 100644 --- a/hugegraph-struct/src/main/java/org/apache/hugegraph/serializer/BinaryElementSerializer.java +++ b/hugegraph-struct/src/main/java/org/apache/hugegraph/serializer/BinaryElementSerializer.java @@ -28,11 +28,6 @@ import org.apache.commons.lang.ArrayUtils; import org.apache.commons.lang.NotImplementedException; -import org.apache.hugegraph.util.Bytes; -import org.apache.hugegraph.util.E; -import org.apache.hugegraph.util.Log; -import org.slf4j.Logger; - import org.apache.hugegraph.HugeGraphSupplier; import org.apache.hugegraph.backend.BackendColumn; import org.apache.hugegraph.backend.BinaryId; @@ -52,10 +47,16 @@ import org.apache.hugegraph.type.HugeType; import org.apache.hugegraph.type.define.Cardinality; import org.apache.hugegraph.type.define.EdgeLabelType; +import org.apache.hugegraph.util.Bytes; +import org.apache.hugegraph.util.E; +import org.apache.hugegraph.util.Log; import org.apache.hugegraph.util.StringEncoding; +import org.slf4j.Logger; + import com.google.common.primitives.Longs; public class BinaryElementSerializer { + static final BinaryElementSerializer INSTANCE = new BinaryElementSerializer(); static Logger log = Log.logger(BinaryElementSerializer.class); @@ -97,9 +98,9 @@ public void parseProperties(HugeGraphSupplier graph, BytesBuffer buffer, /** * 将顶点 kv 数据反序列成 BaseVertex 类型顶点 * - * @param vertexCol 必须是顶点数据的 column - * @param vertex vertex==null 时,用于算子下沉,将 col 数据反序列成 BaseVertex ; - * vertex!=null 时,将 col 信息增加到 vertex 中 + * @param vertexCol 必须是顶点数据的 column + * @param vertex vertex==null 时,用于算子下沉,将 col 数据反序列成 BaseVertex ; + * vertex!=null 时,将 col 信息增加到 vertex 中 */ public BaseVertex parseVertex(HugeGraphSupplier graph, BackendColumn vertexCol, BaseVertex vertex) { @@ -133,12 +134,35 @@ public BaseVertex parseVertex(HugeGraphSupplier graph, BackendColumn vertexCol, return vertex; } + /** + * 从给定的col中解析label id (仅在算子下沉需要按label收集统计信息时使用) + * + * @param col kvColumn + * @param isVertex 当前的column是否是顶点数据 + * @return edge or vertex label id + */ + public Id parseLabelFromCol(BackendColumn col, boolean isVertex) { + BytesBuffer buffer; + if (isVertex) { + buffer = BytesBuffer.wrap(col.value); + // next buffer.readId() is the label id of vertex + } else { + buffer = BytesBuffer.wrap(col.name); + Id ownerVertexId = buffer.readId(); + E.checkState(buffer.remaining() > 0, "Missing column type"); + byte type = buffer.read(); + Id labelId = buffer.readId(); + // next buffer.readId() is the sub-label id of edge + } + return buffer.readId(); + } + /** * 将顶点 kv 数据反序列成 BaseVertex 类型顶点 * - * @param olapVertexCol 必须是顶点数据的 column - * @param vertex vertex==null 时,用于算子下沉,将 col 数据反序列成 olapBaseVertex ; - * vertex!=null 时,将 col 信息增加到 olapBaseVertex 中 + * @param olapVertexCol 必须是顶点数据的 column + * @param vertex vertex==null 时,用于算子下沉,将 col 数据反序列成 olapBaseVertex ; + * vertex!=null 时,将 col 信息增加到 olapBaseVertex 中 */ public BaseVertex parseVertexOlap(HugeGraphSupplier graph, BackendColumn olapVertexCol, BaseVertex vertex) { @@ -307,7 +331,6 @@ public BackendColumn parseIndex(BackendColumn indexCol) { "BinaryElementSerializer.parseIndex"); } - public BackendColumn writeVertex(BaseVertex vertex) { if (vertex.olap()) { return this.writeOlapVertex(vertex); @@ -384,9 +407,7 @@ private byte[] formatIndexName(Index index) { /** * @param index value - * @return - * - * format + * @return format * | empty(field-value) | 0x00 | base64(expiredtime) | */ private byte[] formatIndexValue(Index index) { @@ -438,7 +459,6 @@ public byte[] formatEdgeName(BaseEdge edge) { .writeEdgeId(edge.id()).bytes(); } - protected byte[] formatEdgeValue(BaseEdge edge) { Map> properties = edge.getProperties(); int propsCount = properties.size(); @@ -469,7 +489,6 @@ public void formatProperties(Collection> props, } } - public void formatExpiredTime(long expiredTime, BytesBuffer buffer) { buffer.writeVLong(expiredTime); } @@ -494,7 +513,9 @@ private HugeType parseIndexType(BackendColumn col) { return HugeType.fromString(type); } - /** 计算点/边的ownerid + /** + * 计算点/边的ownerid + * * @param element * @return */ @@ -511,6 +532,7 @@ public static Id ownerId(BaseElement element) { /** * 计算索引的 ownerid + * * @param index * @return */ From 099540091ef35076959bff4f1cbc86aa116d7f19 Mon Sep 17 00:00:00 2001 From: JisoLya <523420504@qq.com> Date: Wed, 27 Aug 2025 16:54:26 +0800 Subject: [PATCH 26/35] refactor: integrate client module --- .../apache/hugegraph/store/HgKvIterator.java | 11 +- .../org/apache/hugegraph/store/HgKvStore.java | 8 + .../apache/hugegraph/store/HgSeekAble.java | 8 +- .../hugegraph/store/HgSessionConfig.java | 26 + .../hugegraph/store/HgSessionProvider.java | 2 + .../apache/hugegraph/store/HgStoreClient.java | 6 + .../client/HgNodePartitionerBuilder.java | 1 + .../store/client/HgStoreNodePartitioner.java | 16 + .../client/HgStoreNodePartitionerImpl.java | 26 + .../store/client/HgStoreSessionProvider.java | 6 + .../store/client/MultiNodeSessionFactory.java | 15 +- .../store/client/NodeTxSessionProxy.java | 28 +- .../client/query/CommonKvStreamObserver.java | 191 ++++++ .../client/query/ErrorMessageIterator.java | 41 ++ .../client/query/MultiStreamIterator.java | 90 +++ .../store/client/query/QueryExecutor.java | 550 ++++++++++++++++++ .../store/client/query/QueryV2Client.java | 67 +++ .../store/client/query/ResultState.java | 52 ++ .../query/StreamFinalAggregationIterator.java | 167 ++++++ .../store/client/query/StreamKvIterator.java | 106 ++++ .../client/query/StreamLimitIterator.java | 68 +++ .../client/query/StreamSampleIterator.java | 80 +++ .../client/query/StreamSortedIterator.java | 133 +++++ .../query/StreamStrictOrderIterator.java | 117 ++++ .../hugegraph/store/client/util/Base58.java | 179 ++++++ .../node/controller/HgTestController.java | 24 +- .../node/listener/ContextClosedListener.java | 13 +- 27 files changed, 1997 insertions(+), 34 deletions(-) create mode 100644 hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/HgSessionConfig.java create mode 100644 hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/query/CommonKvStreamObserver.java create mode 100644 hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/query/ErrorMessageIterator.java create mode 100644 hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/query/MultiStreamIterator.java create mode 100644 hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/query/QueryExecutor.java create mode 100644 hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/query/QueryV2Client.java create mode 100644 hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/query/ResultState.java create mode 100644 hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/query/StreamFinalAggregationIterator.java create mode 100644 hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/query/StreamKvIterator.java create mode 100644 hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/query/StreamLimitIterator.java create mode 100644 hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/query/StreamSampleIterator.java create mode 100644 hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/query/StreamSortedIterator.java create mode 100644 hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/query/StreamStrictOrderIterator.java create mode 100644 hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/util/Base58.java diff --git a/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/HgKvIterator.java b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/HgKvIterator.java index 38c8b0039b..b78d154cb7 100644 --- a/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/HgKvIterator.java +++ b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/HgKvIterator.java @@ -25,11 +25,16 @@ */ public interface HgKvIterator extends Iterator, HgSeekAble, Closeable { - byte[] key(); + default byte[] key() { + return new byte[0]; + } - byte[] value(); + default byte[] value() { + return new byte[0]; + } @Override - void close(); + default void close() { + } } diff --git a/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/HgKvStore.java b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/HgKvStore.java index f04e743f32..21016a287c 100644 --- a/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/HgKvStore.java +++ b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/HgKvStore.java @@ -19,8 +19,12 @@ import java.util.List; +import org.apache.hugegraph.HugeGraphSupplier; +import org.apache.hugegraph.pd.common.PDException; import org.apache.hugegraph.store.client.grpc.KvCloseableIterator; import org.apache.hugegraph.store.grpc.stream.ScanStreamReq; +import org.apache.hugegraph.store.query.StoreQueryParam; +import org.apache.hugegraph.structure.BaseElement; /** * @version 0.2.0 @@ -98,8 +102,12 @@ HgKvIterator scanIterator(String table, int codeFrom, int codeTo, int HgKvIterator scanIterator(ScanStreamReq.Builder scanReqBuilder); + //todo soya to be removed in the future long count(String table); + List> query(StoreQueryParam query, HugeGraphSupplier supplier) throws + PDException; + boolean truncate(); default boolean existsTable(String table) { diff --git a/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/HgSeekAble.java b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/HgSeekAble.java index fe6a580a1c..b7b2ddd985 100644 --- a/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/HgSeekAble.java +++ b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/HgSeekAble.java @@ -22,7 +22,11 @@ */ public interface HgSeekAble { - byte[] position(); + default byte[] position() { + return null; + } + + default void seek(byte[] position) { + } - void seek(byte[] position); } diff --git a/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/HgSessionConfig.java b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/HgSessionConfig.java new file mode 100644 index 0000000000..fe779ff45e --- /dev/null +++ b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/HgSessionConfig.java @@ -0,0 +1,26 @@ +/* + * 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.store; + + +import lombok.Data; + +@Data +public class HgSessionConfig { + private long queryPushDownTimeout = 1800_000; +} diff --git a/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/HgSessionProvider.java b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/HgSessionProvider.java index 7049c27b01..8e97aab7b2 100644 --- a/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/HgSessionProvider.java +++ b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/HgSessionProvider.java @@ -26,4 +26,6 @@ public interface HgSessionProvider { HgStoreSession createSession(String graphName); + + HgStoreSession createSession(String graphName, HgSessionConfig sessionConfig); } diff --git a/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/HgStoreClient.java b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/HgStoreClient.java index 0f8ebb929f..17ab82c888 100644 --- a/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/HgStoreClient.java +++ b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/HgStoreClient.java @@ -37,6 +37,8 @@ public final class HgStoreClient { private final HgSessionProvider sessionProvider; private PDClient pdClient; + private HgSessionConfig sessionConfig; + public HgStoreClient() { this.sessionProvider = new HgStoreSessionProvider(); } @@ -69,6 +71,10 @@ public void setPDConfig(PDConfig config) { setPdClient(pdClient); } + public void setSessionConfig(HgSessionConfig sessionConfig) { + this.sessionConfig = sessionConfig; + } + /** * Retrieve or create a HgStoreSession. * diff --git a/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/HgNodePartitionerBuilder.java b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/HgNodePartitionerBuilder.java index 4bb0705b74..4b29abfeef 100644 --- a/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/HgNodePartitionerBuilder.java +++ b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/HgNodePartitionerBuilder.java @@ -67,4 +67,5 @@ public void setPartitions(Set partitions) { this.partitions = partitions; } + } diff --git a/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/HgStoreNodePartitioner.java b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/HgStoreNodePartitioner.java index d540f68aa7..13ff874fbb 100644 --- a/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/HgStoreNodePartitioner.java +++ b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/HgStoreNodePartitioner.java @@ -17,6 +17,9 @@ package org.apache.hugegraph.store.client; +import java.util.List; + +import org.apache.hugegraph.pd.common.PDException; import org.apache.hugegraph.store.client.util.HgStoreClientConst; /** @@ -63,4 +66,17 @@ default int partition(HgNodePartitionerBuilder builder, String graphName, int pa , HgStoreClientConst.ALL_PARTITION_OWNER , HgStoreClientConst.ALL_PARTITION_OWNER); } + + default String partition(String graphName, byte[] startKey) throws PDException { + return null; + } + + default String partition(String graphName, int code) throws PDException { + return null; + } + + default List getStores(String graphName) throws PDException { + return null; + } + } diff --git a/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/HgStoreNodePartitionerImpl.java b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/HgStoreNodePartitionerImpl.java index 606b279e8d..f34acf54de 100644 --- a/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/HgStoreNodePartitionerImpl.java +++ b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/HgStoreNodePartitionerImpl.java @@ -20,6 +20,7 @@ import java.util.Arrays; import java.util.HashSet; import java.util.List; +import java.util.stream.Collectors; import org.apache.hugegraph.pd.client.PDClient; import org.apache.hugegraph.pd.common.KVPair; @@ -140,6 +141,18 @@ public int partition(HgNodePartitionerBuilder builder, String graphName, return 0; } + @Override + public String partition(String graphName, byte[] startKey) throws PDException { + var shard = pdClient.getPartition(graphName, startKey).getValue(); + return pdClient.getStore(shard.getStoreId()).getAddress(); + } + + @Override + public String partition(String graphName, int code) throws PDException { + var shard = pdClient.getPartitionByCode(graphName, code).getValue(); + return pdClient.getStore(shard.getStoreId()).getAddress(); + } + /** * Query hgstore information * @@ -196,4 +209,17 @@ public Metapb.Graph delGraph(String graphName) { public void setNodeManager(HgStoreNodeManager nodeManager) { this.nodeManager = nodeManager; } + + @Override + public List getStores(String graphName) throws PDException { + var list = pdClient.getCache().getLeaderStoreAddresses(); + if (list.isEmpty()) { + // 缓存正在被清空的case,获取原始的 + return pdClient.getActiveStores(graphName).stream() + .map(Metapb.Store::getAddress) + .collect(Collectors.toList()); + } + return list; + } } + diff --git a/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/HgStoreSessionProvider.java b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/HgStoreSessionProvider.java index 37fa51cb4a..c4dcc70675 100644 --- a/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/HgStoreSessionProvider.java +++ b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/HgStoreSessionProvider.java @@ -19,6 +19,7 @@ import javax.annotation.concurrent.ThreadSafe; +import org.apache.hugegraph.store.HgSessionConfig; import org.apache.hugegraph.store.HgSessionProvider; import org.apache.hugegraph.store.HgStoreSession; @@ -34,4 +35,9 @@ public class HgStoreSessionProvider implements HgSessionProvider { public HgStoreSession createSession(String graphName) { return this.sessionFactory.createStoreSession(graphName); } + + @Override + public HgStoreSession createSession(String graphName, HgSessionConfig sessionConfig) { + return this.sessionFactory.createStoreSession(graphName, sessionConfig); + } } diff --git a/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/MultiNodeSessionFactory.java b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/MultiNodeSessionFactory.java index ff7cde0db8..e1e41214a3 100644 --- a/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/MultiNodeSessionFactory.java +++ b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/MultiNodeSessionFactory.java @@ -19,6 +19,7 @@ import javax.annotation.concurrent.ThreadSafe; +import org.apache.hugegraph.store.HgSessionConfig; import org.apache.hugegraph.store.HgStoreSession; /** @@ -42,13 +43,21 @@ static MultiNodeSessionFactory getInstance() { } HgStoreSession createStoreSession(String graphName) { - return buildProxy(graphName); + return buildProxy(graphName, null); } - private HgStoreSession buildProxy(String graphName) { + HgStoreSession createStoreSession(String graphName, HgSessionConfig config) { + return buildProxy(graphName, config); + } + + private HgStoreSession buildProxy(String graphName, HgSessionConfig config) { //return new MultiNodeSessionProxy(graphName, nodeManager, storeNodeDispatcher); //return new NodePartitionSessionProxy(graphName,nodeManager); //return new NodeRetrySessionProxy(graphName,nodeManager); - return new NodeTxSessionProxy(graphName, nodeManager); + if (config == null) { + return new NodeTxSessionProxy(graphName, nodeManager); + } + + return new NodeTxSessionProxy(graphName, nodeManager, config); } } diff --git a/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/NodeTxSessionProxy.java b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/NodeTxSessionProxy.java index c5a6e5c4a4..324b7a0263 100644 --- a/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/NodeTxSessionProxy.java +++ b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/NodeTxSessionProxy.java @@ -39,6 +39,7 @@ import javax.annotation.concurrent.NotThreadSafe; +import org.apache.hugegraph.HugeGraphSupplier; import org.apache.hugegraph.store.HgKvEntry; import org.apache.hugegraph.store.HgKvIterator; import org.apache.hugegraph.store.HgKvOrderedIterator; @@ -51,8 +52,10 @@ import org.apache.hugegraph.store.client.util.HgStoreClientConst; import org.apache.hugegraph.store.client.util.HgStoreClientUtil; import org.apache.hugegraph.store.grpc.stream.ScanStreamReq.Builder; +import org.apache.hugegraph.store.query.StoreQueryParam; import org.apache.hugegraph.store.term.HgPair; import org.apache.hugegraph.store.term.HgTriple; +import org.apache.hugegraph.structure.BaseElement; import lombok.extern.slf4j.Slf4j; @@ -503,17 +506,6 @@ public HgKvIterator scanIterator(Builder scanReqBuilder) { return this.toHgKvIteratorProxy(iterators, scanReqBuilder.getLimit()); } - @Override - public long count(String table) { - return this.toNodeTkvList(table) - .parallelStream() - .map( - e -> this.getStoreNode(e.getNodeId()).openSession(this.graphName) - .count(e.getTable()) - ) - .collect(Collectors.summingLong(l -> l)); - } - @Override public List> scanBatch(HgScanQuery scanQuery) { HgAssert.isArgumentNotNull(scanQuery, "scanQuery"); @@ -884,4 +876,18 @@ private List> nodeTkv2Node(Collection node return hgPairs; } + @Override + public List> query(StoreQueryParam query, + HugeGraphSupplier supplier) throws PDException { + long current = System.nanoTime(); + QueryExecutor planner = new QueryExecutor(this.nodePartitioner, supplier, + this.sessionConfig.getQueryPushDownTimeout()); + query.checkQuery(); + var iteratorList = planner.getIterators(query); + log.debug("[time_stat] query id: {}, size {}, get Iterator cost: {} ms", + query.getQueryId(), + iteratorList.size(), + (System.nanoTime() - current) * 1.0 / 1000_000); + return iteratorList; + } } diff --git a/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/query/CommonKvStreamObserver.java b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/query/CommonKvStreamObserver.java new file mode 100644 index 0000000000..a7d8025d29 --- /dev/null +++ b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/query/CommonKvStreamObserver.java @@ -0,0 +1,191 @@ +/* + * 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.store.client.query; + +import io.grpc.stub.StreamObserver; +import lombok.Data; +import lombok.Setter; +import lombok.extern.slf4j.Slf4j; + +import java.util.Iterator; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.Consumer; +import java.util.function.Function; + +import org.apache.hugegraph.store.client.type.HgStoreClientException; + +@Slf4j +public class CommonKvStreamObserver implements StreamObserver { + + /** + * 结果保存的队列 + */ + private final BlockingQueue> queue; + + /** + * 向 server 发送请求的函数 + */ + @Setter + private Consumer requestSender; + + /** + * 当 server 没有结果时候,所做的处理,关闭双向流 + */ + @Setter + private Consumer transferComplete; + + /** + * 解析 server 返回数据的函数 + */ + private final Function> valueExtractor; + /** + * 解析 server 返回数据的状态 + */ + private final Function stateWatcher; + + /** + * 是否结束,可以由 client 发送,即不接收多余的数据了 + */ + private final AtomicBoolean closed = new AtomicBoolean(false); + + @Setter + private long timeout = 1800 * 1000; + + /** + * 监控内部的状态 + */ + private final ResultStateWatcher watcher = new ResultStateWatcher(); + + public CommonKvStreamObserver(Function> valueExtractor, + Function stateWatcher) { + this.queue = new LinkedBlockingQueue<>(); + this.valueExtractor = valueExtractor; + this.stateWatcher = stateWatcher; + } + + /** + * 发送请求 + */ + public void sendRequest() { + if (!isServerFinished() && !closed.get()) { + this.requestSender.accept(true); + this.watcher.setState(ResultState.WAITING); + } + } + + public boolean isServerFinished() { + return this.watcher.getState() == ResultState.FINISHED + || this.watcher.getState() == ResultState.ERROR; + } + + @Override + public void onNext(R value) { + watcher.setState(ResultState.INNER_BUSY); + try { + var state = stateWatcher.apply(value); + log.debug("observer state: {}", state); + + switch (state) { + case IDLE: + case FINISHED: + if (! this.closed.get()) { + queue.offer(this.valueExtractor.apply(value)); + } + // this.stop(); + break; + default: + queue.offer(new ErrorMessageIterator<>(state.getMessage())); + break; + } + watcher.setState(state); + // sendRequest(); + } catch (Exception e) { + log.error("handling server data, got error: ", e); + queue.offer(new ErrorMessageIterator<>(e.getMessage())); + } + } + + public Iterator consume() { + try { + while (!Thread.currentThread().isInterrupted() && (!this.queue.isEmpty() || + !isServerFinished())) { + var iterator = this.queue.poll(200, TimeUnit.MILLISECONDS); + if (iterator != null) { + sendRequest(); + return iterator; + } + + if (System.currentTimeMillis() - watcher.current > this.timeout) { + throw new HgStoreClientException("iterator timeout"); + } + + } + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + + return null; + } + + /** + * 发送 onComplete,停止接收数据 + */ + public void clear() { + if (! this.closed.get()) { + this.closed.set(true); + this.transferComplete.accept(true); + } + this.queue.clear(); + } + + @Override + public void onError(Throwable t) { + log.error("StreamObserver got error:", t); + this.queue.offer(new ErrorMessageIterator<>(t.getMessage())); + this.watcher.setState(ResultState.ERROR); + } + + @Override + public void onCompleted() { + if (watcher.getState() != ResultState.ERROR) { + watcher.setState(ResultState.FINISHED); + } + } + + public void setWatcherQueryId(String queryId) { + this.watcher.setQueryId(queryId); + } + + @Data + private static class ResultStateWatcher{ + private long current = System.nanoTime(); + private volatile ResultState state = ResultState.IDLE; + + private String queryId; + + public void setState(ResultState state) { + log.debug("query Id: {}, COST_STAT: {} -> {}, cost {} ms", this.queryId, this.state, state, + + (System.nanoTime() - current) * 1.0 / 1000000); + this.state = state; + this.current = System.nanoTime(); + } + } +} diff --git a/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/query/ErrorMessageIterator.java b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/query/ErrorMessageIterator.java new file mode 100644 index 0000000000..97cf7e3ade --- /dev/null +++ b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/query/ErrorMessageIterator.java @@ -0,0 +1,41 @@ +/* + * 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.store.client.query; + +import org.apache.hugegraph.store.client.type.HgStoreClientException; + +import java.util.Iterator; + +public class ErrorMessageIterator implements Iterator { + + private String message; + + public ErrorMessageIterator(String message) { + this.message = message; + } + + @Override + public boolean hasNext() { + return true; + } + + @Override + public E next() { + throw new HgStoreClientException(message); + } +} diff --git a/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/query/MultiStreamIterator.java b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/query/MultiStreamIterator.java new file mode 100644 index 0000000000..1414551c10 --- /dev/null +++ b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/query/MultiStreamIterator.java @@ -0,0 +1,90 @@ +/* + * 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.store.client.query; + +import org.apache.hugegraph.store.HgKvIterator; + +import java.util.Iterator; +import java.util.List; +import java.util.NoSuchElementException; + +public class MultiStreamIterator implements HgKvIterator { + private HgKvIterator currentIterator = null; + + private final Iterator> listIterator; + + public MultiStreamIterator(List> iterators) { + this.listIterator = iterators.iterator(); + } + + + @Override + public byte[] key() { + return currentIterator.key(); + } + + @Override + public byte[] value() { + return currentIterator.value(); + } + + @Override + public void close() { + if (currentIterator != null && currentIterator.hasNext()) { + currentIterator.close(); + } + } + + @Override + public byte[] position() { + return currentIterator.position(); + } + + @Override + public void seek(byte[] position) { + // todo: ?? + this.currentIterator.seek(position); + } + + private void getNextIterator() { + if (currentIterator != null && currentIterator.hasNext()) { + return; + } + + while (listIterator.hasNext()) { + currentIterator = listIterator.next(); + if (currentIterator.hasNext()) { + break; + } + } + } + + @Override + public boolean hasNext() { + getNextIterator(); + return currentIterator != null && currentIterator.hasNext(); + } + + @Override + public E next() { + if (currentIterator == null || ! currentIterator.hasNext()) { + throw new NoSuchElementException(); + } + return currentIterator.next(); + } +} diff --git a/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/query/QueryExecutor.java b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/query/QueryExecutor.java new file mode 100644 index 0000000000..09ae6461b3 --- /dev/null +++ b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/query/QueryExecutor.java @@ -0,0 +1,550 @@ +/* + * 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.store.client.query; + +import org.apache.hugegraph.HugeGraphSupplier; +import org.apache.hugegraph.backend.BackendColumn; +import org.apache.hugegraph.id.Id; +import org.apache.hugegraph.pd.common.PDException; +import org.apache.hugegraph.pd.common.PartitionUtils; +import org.apache.hugegraph.serializer.BinaryElementSerializer; +import org.apache.hugegraph.serializer.BytesBuffer; +import org.apache.hugegraph.store.HgKvIterator; +import org.apache.hugegraph.store.client.HgStoreNodePartitioner; +import org.apache.hugegraph.store.grpc.common.Kv; +import org.apache.hugegraph.store.grpc.query.AggregateFunc; +import org.apache.hugegraph.store.grpc.query.AggregationType; +import org.apache.hugegraph.store.grpc.query.DeDupOption; +import org.apache.hugegraph.store.grpc.query.Index; +import org.apache.hugegraph.store.grpc.query.QueryRequest; +import org.apache.hugegraph.store.grpc.query.QueryResponse; +import org.apache.hugegraph.store.grpc.query.ScanType; +import org.apache.hugegraph.store.grpc.query.ScanTypeParam; +import org.apache.hugegraph.store.query.BaseElementComparator; +import org.apache.hugegraph.store.query.KvSerializer; +import org.apache.hugegraph.store.query.StoreQueryType; +import org.apache.hugegraph.store.query.Tuple2; +import org.apache.hugegraph.store.query.func.AggregationFunctionParam; +import org.apache.hugegraph.store.query.util.KeyUtil; +import org.apache.hugegraph.store.query.QueryTypeParam; +import org.apache.hugegraph.store.query.StoreQueryParam; +import org.apache.hugegraph.structure.BaseElement; +import org.apache.hugegraph.structure.KvElement; +import com.google.protobuf.ByteString; +import lombok.extern.slf4j.Slf4j; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; + +import static org.apache.hugegraph.store.constant.HugeServerTables.IN_EDGE_TABLE; +import static org.apache.hugegraph.store.constant.HugeServerTables.OUT_EDGE_TABLE; + +@Slf4j +public class QueryExecutor { + + private final HgStoreNodePartitioner nodePartitioner; + + private static QueryV2Client client = new QueryV2Client(); + + private static BinaryElementSerializer serializer = new BinaryElementSerializer(); + + private final HugeGraphSupplier supplier; + + private long timeout = 1800_000; + + /** + * 用于调试单台机器 + */ + public static String filterStore = null; + + public QueryExecutor(HgStoreNodePartitioner nodePartitioner, HugeGraphSupplier supplier, Long timeout) { + this.nodePartitioner = nodePartitioner; + this.supplier = supplier; + this.timeout = timeout; + } + + /** + * 根据查询条件获取迭代器列表。 + * + * @param query 查询参数 + * @return 迭代器列表 + * @throws PDException 如果发生任何错误则抛出此异常。 + */ + public List> getIterators(StoreQueryParam query) throws PDException { + if (isSimpleQuery(query)) { + return getSimpleIterator(query); + } + + List> iterators; + + if (isSimpleCountQuery(query)) { + iterators = getCountIterator(query); + } else { + // 获取所有 node 节点的 iterator + iterators = getNodeTasks(query) + .parallelStream() + .map(tuple -> getIterator(tuple.getV1(), tuple.getV2().build())) + .collect(Collectors.toList()); + } + + + if (isEmpty(query.getFuncList()) && isEmpty(query.getOrderBy()) && query.getLimit() == 0) { + return iterators; + } + + HgKvIterator iterator; + + if (! isEmpty(query.getFuncList())) { + // agg: 先排序,后计算 + iterator = new StreamSortedIterator<>(iterators, (o1, o2) -> { + if (o1 == null && o2 == null) { + return 0; + } + + if (o1 != null) { + return ((KvElement) o1).compareTo((KvElement) o2); + } + + return 0; + }); + + iterator = new StreamFinalAggregationIterator<>(iterator, query.getFuncList()); + if (query.getSampleFactor() != 1) { + // 抽样不下沉,在最后做 + iterator = new StreamSampleIterator<>(iterator, query.getSampleFactor()); + } + } else if (!isEmpty(query.getOrderBy())) { + // 有排序 + if (query.getSortOrder() != StoreQueryParam.SORT_ORDER.STRICT_ORDER) { + iterator = new StreamSortedIterator<>(iterators, new BaseElementComparator(query.getOrderBy(), + query.getSortOrder() == StoreQueryParam.SORT_ORDER.ASC)); + } else { + iterator = new StreamStrictOrderIterator<>(query.getQueryParam(), iterators, + query.getGraph(), this.nodePartitioner); + } + } else { + // 后面还有 limit + iterator = new MultiStreamIterator<>(iterators); + } + + if (query.getLimit() > 0) { + iterator = new StreamLimitIterator<>(iterator, query.getLimit()); + } + + return List.of(iterator); + } + + /** + * 使用 StreamKvIterator 封装返回的结果 + * @param address store node addr + * @param request initial request + * @return iterator result + */ + private StreamKvIterator getIterator(String address, QueryRequest request) { + var stub = client.getQueryServiceStub(address); + var hasAgg = ! isEmpty(request.getFunctionsList()); + + var observer = new CommonKvStreamObserver( + response -> new Iterator<>() { + final Iterator itr = response.getDataList().iterator(); + + BaseElement element = null; + + @Override + public boolean hasNext() { + if (element == null) { + while (itr.hasNext()) { + element = fromKv(request.getTable(), itr.next(), hasAgg); + if (element != null) { + break; + } + } + } + return element != null; + } + + @Override + public BaseElement next() { + try { + return element; + } finally { + element = null; + } + // return fromKv(request.getTable(), itr.next(), hasAgg); + } + }, + + response -> { + // is OK | is finished + // T | T -> finished + // T | F -> has more result + // F | T -> busy + // F | F -> error + var ok = response.getIsOk(); + var finished = response.getIsFinished(); + + if (ok & finished) { + return ResultState.FINISHED; + } + + if (ok & !finished) { + return ResultState.IDLE; + } + + if (finished) { + return ResultState.ERROR.setMessage("server is busy"); + } + return ResultState.ERROR.setMessage(response.getMessage()); + } + + ); + + var reqStream = stub.query(observer); + observer.setWatcherQueryId(request.getQueryId() + '-' + address); + observer.setRequestSender(r -> reqStream.onNext(request)); + observer.setTransferComplete(r -> reqStream.onCompleted()); + observer.setTimeout(this.timeout); + + var itr = new StreamKvIterator<>(b -> observer.clear(), observer::consume); + observer.sendRequest(); + return itr; + } + + private static boolean isEmpty(Collection c) { + return c == null || c.isEmpty(); + } + + // 返回 node addr -> query proto + // 只对 id scan 进行拆分,剩下的发送给所有的 store + private List> getNodeTasks(StoreQueryParam query) throws PDException { + var graph = query.getGraph(); + var stores = this.nodePartitioner.getStores(graph); + + if (stores.isEmpty()) { + log.warn("no stores found, query: {}", query); + } + + Map tasks = new HashMap<>(); + + if (query.getQueryType() == StoreQueryType.PRIMARY_SCAN) { + // primary 拆分 query param,主要是 id scan + for (var param : query.getQueryParam()) { + if (param.getCode() != -1) { + var addr = this.nodePartitioner.partition(graph, param.getCode()); + if (! tasks.containsKey(addr)) { + tasks.put(addr, fromQuery(query)) ; + } + tasks.get(addr).addScanTypeParam(fromParam(query.getTable(), param)); + } else { + for (String addr : stores) { + if (! tasks.containsKey(addr)) { + tasks.put(addr, fromQuery(query)) ; + } + tasks.get(addr).addScanTypeParam(fromParam(query.getTable(), param)); + } + } + } + } else { + for (String addr : stores) { + tasks.computeIfAbsent(addr, t -> fromQuery(query)); + } + } + + if (filterStore != null) { + return tasks.containsKey(filterStore) ? List.of(Tuple2.of(filterStore, tasks.get(filterStore))) : List.of(); + } + + return tasks.entrySet().stream() + .map(entry -> Tuple2.of(entry.getKey(), entry.getValue())) + .collect(Collectors.toList()); + } + + /** + * 从查询参数构建 QueryRequest.Builder 对象。 + * + * @param query 查询参数对象 + * @return 返回构建好的 QueryRequest.Builder 对象 + */ + private static QueryRequest.Builder fromQuery(StoreQueryParam query) { + var builder = QueryRequest.newBuilder() + .setQueryId(query.getQueryId()) + .setGraph(query.getGraph()) + .setTable(query.getTable()) + .addAllFunctions(getAggregationProto(query.getFuncList())) + .addAllProperty(idToBytes(query.getProperties().getPropertyIds())) + .setNullProperty(query.getProperties().isEmptyId()) + .addAllGroupBy(idToBytes(query.getGroupBy())) + .addAllOrderBy(idToBytes(query.getOrderBy())) + .addAllHaving(getOrCreate(query.getHaving())) + .setScanType(ScanType.forNumber(query.getQueryType().ordinal())) + // .addAllScanTypeParam(fromParams(query.getTable(), query.getQueryParam())) + .setDedupOption(DeDupOption.forNumber(query.getDedupOption().ordinal())) + .setOffset(query.getOffset()) + .addAllOlapProperty(idToBytes(query.getOlapProperties())) + .setLoadPropertyFromIndex(query.isLoadPropertyFromIndex()) + .setGroupBySchemaLabel(query.isGroupBySchemaLabel()); + + if (query.getSortOrder() != StoreQueryParam.SORT_ORDER.STRICT_ORDER) { + builder.setSortOrder(query.getSortOrder() == StoreQueryParam.SORT_ORDER.ASC); + } + + // count 不需要反查的时候 (不需要去重,不需要取并集),改为 NO_SCAN, 每个 size 都是 1(都是 index scan) + if (query.getQueryType() == StoreQueryType.INDEX_SCAN + && query.getDedupOption() == StoreQueryParam.DEDUP_OPTION.NONE) { + if (! isEmpty(query.getFuncList()) && query.getFuncList().stream().allMatch(f -> f.getFunctionType() == + AggregationFunctionParam.AggregationFunctionType.COUNT) && query.getConditionQuery() == null){ + if (query.getIndexes().stream().allMatch(i -> i.size() == 1 && i.get(0).isIndexScan())) { + log.info("trans query id {} from INDEX_SCAN to NO_SCAN", query.getQueryId()); + builder.setScanType(ScanType.NO_SCAN); + } + } + } + + if (query.getConditionQuery() != null) { + builder.setCondition(ByteString.copyFrom(query.getConditionQuery().bytes())); + } + + if (query.getPosition() != null) { + builder.setPosition(ByteString.copyFrom(query.getPosition())); + } + + // sample and limit are set to default when has function list. + builder.setSampleFactor(isEmpty(query.getFuncList()) ? query.getSampleFactor() : 1.0); + builder.setLimit(isEmpty(query.getFuncList()) ? query.getLimit() : 0); + + if (query.getIndexes() != null) { + builder.addAllIndexes(fromIndex(query.getIndexes())); + } + + builder.setCheckTtl(query.isCheckTTL()); + + return builder; + } + + private static ScanTypeParam fromParam(String table, QueryTypeParam param) { + var builder = ScanTypeParam.newBuilder() + .setKeyStart(ByteString.copyFrom(param.getStart())) + .setScanBoundary(param.getBoundary()) + .setIsPrefix(param.isPrefix()) + .setIsSecondaryIndex(param.isSecondaryIndex()); + + if (param.getEnd() != null) { + builder.setKeyEnd(ByteString.copyFrom(param.getEnd())); + } + + if (param.isIdScan() && param.getCode() == -1) { + builder.setCode(PartitionUtils.calcHashcode(KeyUtil.getOwnerKey(table, param.getStart()))); + } else { + builder.setCode(param.getCode()); + } + + if (param.getIdPrefix() != null) { + builder.setIdPrefix(ByteString.copyFrom(param.getIdPrefix())); + } + return builder.build(); + } + + private static List fromParams(String table, List params) { + if (isEmpty(params)) { + return new ArrayList<>(); + } + return params.stream().map(p -> fromParam(table, p)).collect(Collectors.toList()); + } + + private static List fromIndex(List> indexes) { + return indexes.stream() + .map(x -> Index.newBuilder().addAllParams(fromParams("", x)).build()) + .collect(Collectors.toList()); + } + + private static List getAggregationProto(List aggParams) { + if (isEmpty(aggParams)) { + return new ArrayList<>(); + } + return aggParams.stream().map(param -> { + + var builder = AggregateFunc.newBuilder(); + + builder.setFuncType(AggregationType.forNumber(param.getFunctionType().ordinal())); + + if (param.getField() != null) { + builder.setField(idToBytes(param.getField())); + } + + if (param.getFiledType() != null) { + builder.setType(param.getFiledType().getGenericType()); + } + return builder.build(); + }).collect(Collectors.toList()); + } + + private static List idToBytes(List ids) { + if (isEmpty(ids)) { + return new ArrayList<>(); + } + + return ids.stream().map(QueryExecutor::idToBytes).collect(Collectors.toList()); + } + + public static ByteString idToBytes(Id id) { + BytesBuffer buffer = BytesBuffer.allocate(2); + buffer.writeId(id); + return ByteString.copyFrom(buffer.bytes()); + } + + private static List getOrCreate(List list){ + if (list != null) { + return list; + } + return new ArrayList<>(); + } + + private BaseElement fromKv(String table, Kv kv, boolean isAgg) { + if (isAgg) { + return KvElement.of(KvSerializer.fromBytes(kv.getKey().toByteArray()), + KvSerializer.fromObjectBytes(kv.getValue().toByteArray())); + } + + var backendColumn = BackendColumn.of(kv.getKey().toByteArray(), kv.getValue().toByteArray()); + try { + if (IN_EDGE_TABLE.equals(table) || OUT_EDGE_TABLE.equals(table)) { + return serializer.parseEdge(this.supplier, backendColumn, null, true); + } + return serializer.parseVertex(this.supplier, backendColumn, null); + } catch (Exception e) { + log.error("parse element error,", e); + return null; + } + } + + /** + * execute plan 为空,只是简单的查询 + * @param param query param + * @return true if id simple scan query + */ + private boolean isSimpleQuery(StoreQueryParam param) { + if (param.getQueryType() == StoreQueryType.PRIMARY_SCAN && ! param.isCheckTTL() && param.getLimit() == 0) { + // all id scan: + if (param.getQueryParam().stream().allMatch(QueryTypeParam::isIdScan)) { + return isEmpty(param.getFuncList()) && + isEmpty(param.getOrderBy()) && + isEmpty(param.getGroupBy()) && + isEmpty(param.getOlapProperties()) && + ! param.getProperties().needSerialize() && + param.getConditionQuery() == null && + param.getSampleFactor() == 1.0; + } + } + + return false; + } + + /** + * 判断是否为简单的聚合查询 + * + * @param param 存储参数对象 + * @return 如果是简单聚合查询,则返回 true,否则返回 false + */ + private boolean isSimpleCountQuery(StoreQueryParam param) { + if (param.getQueryType() == StoreQueryType.TABLE_SCAN && (!isEmpty(param.getFuncList()))) { + return param.getFuncList() + .stream() + .allMatch(f -> f.getFunctionType() == AggregationFunctionParam.AggregationFunctionType.COUNT) + && isEmpty(param.getGroupBy()) + && isEmpty(param.getOrderBy()) + && param.getConditionQuery() == null + && ! param.isGroupBySchemaLabel() + && ! param.isCheckTTL(); + } + + return false; + } + + /** + * 从查询参数中获取简单迭代器。 + * + * @param query 查询参数 + * @return 包含 BaseElement 元素的列表 + * @throws PDException 如果查询失败 + */ + private List> getSimpleIterator(StoreQueryParam query) throws PDException { + + return getNodeTasks(query).parallelStream() + .map( entry -> { + var stub = client.getQueryServiceBlockingStub(entry.getV1()); + var response = stub.query0(entry.getV2().build()); + if (! response.getIsOk()) { + throw new RuntimeException(response.getMessage()); + } + + var data = response.getDataList().iterator(); + return new HgKvIterator() { + @Override + public boolean hasNext() { + return data.hasNext(); + } + + @Override + public BaseElement next() { + return fromKv(query.getTable(), data.next(), false); + } + }; + }) + .collect(Collectors.toList()); + } + + /** + * 获取查询结果的元素数量迭代器 + * + * @param query 查询参数 + * @return 包含元素数量迭代器的列表 + * @throws PDException 当发生异常时抛出 + */ + private List> getCountIterator(StoreQueryParam query) throws PDException { + + return getNodeTasks(query).parallelStream() + .map( entry -> { + var stub = client.getQueryServiceBlockingStub(entry.getV1()) + .withDeadlineAfter(3600, TimeUnit.SECONDS); + var response = stub.count(entry.getV2().build()); + if (! response.getIsOk()) { + throw new RuntimeException(response.getMessage()); + } + + var data = response.getDataList().iterator(); + return new HgKvIterator() { + @Override + public boolean hasNext() { + return data.hasNext(); + } + + @Override + public BaseElement next() { + return fromKv(query.getTable(), data.next(), true); + } + }; + }) + .collect(Collectors.toList()); + } +} diff --git a/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/query/QueryV2Client.java b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/query/QueryV2Client.java new file mode 100644 index 0000000000..7c26a80903 --- /dev/null +++ b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/query/QueryV2Client.java @@ -0,0 +1,67 @@ +/* + * 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.store.client.query; + +import java.util.concurrent.atomic.AtomicInteger; + +import org.apache.hugegraph.store.client.grpc.AbstractGrpcClient; +import org.apache.hugegraph.store.grpc.query.QueryServiceGrpc; +import io.grpc.ManagedChannel; +import io.grpc.ManagedChannelBuilder; +import io.grpc.stub.AbstractAsyncStub; +import io.grpc.stub.AbstractBlockingStub; + +public class QueryV2Client extends AbstractGrpcClient { + + private static ManagedChannel channel = null; + + private volatile AtomicInteger seq = new AtomicInteger(0); + + @Override + public AbstractBlockingStub getBlockingStub(ManagedChannel channel) { + return QueryServiceGrpc.newBlockingStub(channel); + } + + @Override + public AbstractAsyncStub getAsyncStub(ManagedChannel channel) { + return QueryServiceGrpc.newStub(channel); + } + + public QueryServiceGrpc.QueryServiceBlockingStub getQueryServiceBlockingStub(String target) { + return (QueryServiceGrpc.QueryServiceBlockingStub) getBlockingStub(target); + } + + public QueryServiceGrpc.QueryServiceStub getQueryServiceStub(String target) { + return (QueryServiceGrpc.QueryServiceStub) setStubOption(QueryServiceGrpc.newStub(getManagedChannel(target))); + // return (QueryServiceGrpc.QueryServiceStub) getAsyncStub(target); + } + + private ManagedChannel getManagedChannel(String target) { + return getChannels(target)[Math.abs(seq.getAndIncrement() % concurrency)]; + } + + public static void setTestChannel(ManagedChannel directChannel) { + channels.clear(); + channel = directChannel; + } + + @Override + protected ManagedChannel createChannel(String target) { + return channel == null ? ManagedChannelBuilder.forTarget(target).usePlaintext().build() : channel; + } +} diff --git a/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/query/ResultState.java b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/query/ResultState.java new file mode 100644 index 0000000000..6aaa066d51 --- /dev/null +++ b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/query/ResultState.java @@ -0,0 +1,52 @@ +/* + * 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.store.client.query; + +/** + * |---(has more result) --> IDLE + * | + * IDLE --(send req)--> WAITING --(onNext)--> INNER_BUSY |---(onCompleted)--> FINISHED + * | + * |---(error)----> ERROR + * | + * |---(processing)-> BUSY (ERROR) + */ +public enum ResultState { + // 初始化的状态,可以读取新数据 + IDLE, + // 发送数据到等待server返回中间的状态 + WAITING, + // 开始读取数据,后的状态 + INNER_BUSY, + // 没更多数据了 + FINISHED, + // 写入数据的状态 + // 错误 + ERROR; + + private String message; + + public String getMessage() { + return message; + } + + public ResultState setMessage(String message) { + this.message = message; + return this; + } +} diff --git a/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/query/StreamFinalAggregationIterator.java b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/query/StreamFinalAggregationIterator.java new file mode 100644 index 0000000000..89bb5bb562 --- /dev/null +++ b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/query/StreamFinalAggregationIterator.java @@ -0,0 +1,167 @@ +/* + * 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.store.client.query; + +import org.apache.hugegraph.store.HgKvIterator; +import org.apache.hugegraph.store.query.func.AggregationFunction; +import org.apache.hugegraph.store.query.func.AggregationFunctionParam; +import org.apache.hugegraph.store.query.func.AggregationFunctions; +import org.apache.hugegraph.structure.KvElement; + +import java.util.List; +import java.util.function.Function; +import java.util.function.Supplier; +import java.util.stream.Collectors; + +public class StreamFinalAggregationIterator implements HgKvIterator { + + private HgKvIterator iterator; + + private List aggregationParams; + + private List functions; + + private KvElement prev = null; + + private KvElement data = null; + + public StreamFinalAggregationIterator(HgKvIterator iterator, List aggregations) { + this.iterator = iterator; + this.aggregationParams = aggregations; + } + + @Override + public byte[] key() { + return this.iterator.key(); + } + + @Override + public byte[] value() { + return this.iterator.value(); + } + + @Override + public void close() { + this.iterator.close(); + } + + @Override + public byte[] position() { + return this.iterator.position(); + } + + @Override + public void seek(byte[] position) { + this.iterator.seek(position); + } + + @Override + public boolean hasNext() { + + while (iterator.hasNext()) { + var next = (KvElement) iterator.next(); + if (prev == null) { + // first element, initial + prev = next; + functions = getAggregationList(); + merge(prev.getValues()); + continue; + } + + if (keyEquals(next.getKeys(), prev.getKeys())) { + merge(next.getValues()); + } else { + // 产生结果 + data = KvElement.of(prev.getKeys(), term()); + prev = next; + functions = getAggregationList(); + merge(prev.getValues()); + break; + } + } + + // 消费完最后一个prev + if (! iterator.hasNext() && prev != null && data == null) { + data = KvElement.of(prev.getKeys(), term()); + prev = null; + } + + return data != null; + } + + private void merge(List values){ + for (int i = 0 ; i < functions.size(); i ++) { + functions.get(i).merge(values.get(i)); + } + } + + private List term() { + var values = functions.stream().map(f -> f.reduce()).collect(Collectors.toList()); + functions.clear(); + return values; + } + + @Override + public E next() { + var rst = data; + data = null; + return (E) rst; + } + + private boolean keyEquals(List l1, List l2) { + if (l1 == null && l2 == null) { + return true; + } + if (l1 != null && l2 == null || l1 == null || l1.size() != l2.size()) { + return false; + } + + for (int i = 0; i < l1.size(); i ++) { + if (! l1.get(i).equals(l2.get(i))) { + return false; + } + } + return true; + } + + private List getAggregationList() { + return this.aggregationParams.stream() + .map((Function) param -> { + var filedType = param.getFiledType().getGenericType(); + switch (param.getFunctionType()) { + case SUM: + return new AggregationFunctions.SumFunction(param.getField(), getSupplier(filedType)); + case MIN: + return new AggregationFunctions.MinFunction(param.getField(), getSupplier(filedType)); + case MAX: + return new AggregationFunctions.MaxFunction(param.getField(), getSupplier(filedType)); + case AVG: + return new AggregationFunctions.AvgFunction(getSupplier(filedType)); + case COUNT: + return new AggregationFunctions.CountFunction(); + default: + throw new RuntimeException("unsupported function type: " + param.getFunctionType()); + } + }) + .collect(Collectors.toList()); + } + + private Supplier getSupplier(String type) { + return AggregationFunctions.getAggregationBufferSupplier(type); + } +} diff --git a/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/query/StreamKvIterator.java b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/query/StreamKvIterator.java new file mode 100644 index 0000000000..6f72e78a9b --- /dev/null +++ b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/query/StreamKvIterator.java @@ -0,0 +1,106 @@ +/* + * 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.store.client.query; + +import org.apache.hugegraph.store.HgKvIterator; +import org.apache.hugegraph.store.client.type.HgStoreClientException; + +import lombok.Getter; +import lombok.Setter; +import lombok.extern.slf4j.Slf4j; + +import java.util.Iterator; +import java.util.function.Consumer; +import java.util.function.Supplier; + +/** + * 对批量获取 stream response 封装的 iterator,可以做类型的转换,依靠 T -> E 的 mapper + * 通过调用 invoker,将 server 的结果放到一个队列中。iterator 读取队列的结果 + * iterator <-------read --------- queue + * | ↑ + * invoke Observer.onNext + * | | + * ↓ | + * req ---- send req ----------server + * + */ +@Slf4j +public class StreamKvIterator implements HgKvIterator { + + /** + * 关闭 iterator 的操作,清理服务器的缓存等 + */ + private final Consumer closeOp; + + @Setter + @Getter + private String address; + + /** + * observer 返回的 iterator + */ + private Iterator iterator = null; + + private final Supplier> iteratorSupplier; + + /** + * 带 close 功能,需要传递 + */ + public StreamKvIterator(Consumer closeOp, Supplier> supplier) { + this.closeOp = closeOp; + this.iteratorSupplier = supplier; + } + + @Override + public byte[] key() { + throw new HgStoreClientException("position function not supported"); + } + + @Override + public byte[] value() { + throw new HgStoreClientException("position function not supported"); + } + + @Override + public void close() { + this.closeOp.accept(true); + } + + @Override + public byte[] position() { + throw new HgStoreClientException("position function not supported"); + } + + @Override + public void seek(byte[] position) { + throw new HgStoreClientException("seek function not supported"); + } + + @Override + public boolean hasNext() { + if (this.iterator == null || !this.iterator.hasNext()) { + this.iterator = this.iteratorSupplier.get(); + } + return this.iterator != null && this.iterator.hasNext(); + } + + @Override + public E next() { + return this.iterator.next(); + } +} diff --git a/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/query/StreamLimitIterator.java b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/query/StreamLimitIterator.java new file mode 100644 index 0000000000..93e46f8bb1 --- /dev/null +++ b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/query/StreamLimitIterator.java @@ -0,0 +1,68 @@ +/* + * 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.store.client.query; + +import org.apache.hugegraph.store.HgKvIterator; + +public class StreamLimitIterator implements HgKvIterator { + private final HgKvIterator iterator; + private final int limit; + + private int count = 0; + + public StreamLimitIterator(HgKvIterator iterator, Integer limit) { + this.iterator = iterator; + this.limit = limit; + } + + @Override + public byte[] key() { + return iterator.key(); + } + + @Override + public byte[] value() { + return iterator.value(); + } + + @Override + public void close() { + iterator.close(); + } + + @Override + public byte[] position() { + return iterator.position(); + } + + @Override + public void seek(byte[] position) { + iterator.seek(position); + } + + @Override + public boolean hasNext() { + return count < limit && iterator.hasNext(); + } + + @Override + public E next() { + count += 1; + return iterator.next(); + } +} diff --git a/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/query/StreamSampleIterator.java b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/query/StreamSampleIterator.java new file mode 100644 index 0000000000..50ddbc9fce --- /dev/null +++ b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/query/StreamSampleIterator.java @@ -0,0 +1,80 @@ +/* + * 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.store.client.query; + +import org.apache.hugegraph.store.HgKvIterator; + +import java.util.Random; + +public class StreamSampleIterator implements HgKvIterator { + + private final HgKvIterator iterator; + private final double sampleFactor; + + private final Random random = new Random(); + + private E current; + + public StreamSampleIterator(HgKvIterator iterator, double sampleFactor) { + this.iterator = iterator; + this.sampleFactor = sampleFactor; + } + + @Override + public byte[] key() { + return iterator.key(); + } + + @Override + public byte[] value() { + return iterator.value(); + } + + @Override + public void close() { + iterator.close(); + } + + @Override + public byte[] position() { + return iterator.position(); + } + + @Override + public void seek(byte[] position) { + iterator.seek(position); + } + + @Override + public boolean hasNext() { + while (iterator.hasNext()) { + current = iterator.next(); + if (random.nextDouble() < sampleFactor) { + return true; + } + } + + return false; + } + + @Override + public E next() { + return current; + } + +} diff --git a/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/query/StreamSortedIterator.java b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/query/StreamSortedIterator.java new file mode 100644 index 0000000000..8de1694870 --- /dev/null +++ b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/query/StreamSortedIterator.java @@ -0,0 +1,133 @@ +/* + * 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.store.client.query; + +import org.apache.hugegraph.store.HgKvIterator; +import org.apache.hugegraph.store.query.Tuple2; + +import java.util.Comparator; +import java.util.List; +import java.util.concurrent.PriorityBlockingQueue; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.Collectors; + +public class StreamSortedIterator implements HgKvIterator { + private List> iterators; + private PriorityBlockingQueue> pq; + + private AtomicBoolean initialized = new AtomicBoolean(false); + + + public StreamSortedIterator(List> iterators, Comparator comparator) { + this.iterators = iterators; + // this.pq = new PriorityQueue<>((o1, o2) -> comparator.compare(o1.getV1(), o2.getV1())); + this.pq = new PriorityBlockingQueue<>(iterators.size(), (o1, o2) -> comparator.compare(o1.getV1(), o2.getV1())); + } + + /** + * 初始化优先队列,取每个iterator的第一个, + * 并行化处理,向每个iterator发送请求 + */ + private void initializeQueue() { + if (this.initialized.get()) { + return ; + } + AtomicInteger index = new AtomicInteger(0); + this.iterators.stream() + .map(itr -> new Tuple2<>(itr, index.getAndIncrement())) + .collect(Collectors.toList()) + .parallelStream() + .forEach(tuple -> { + var itr = tuple.getV1(); + if (itr.hasNext()) { + pq.offer(new Tuple2<>(itr.next(), tuple.getV2())); + } + }); + + this.initialized.set(true); + } + + private HgKvIterator getTopIterator() { + var entry = pq.peek(); + if (entry != null) { + return iterators.get(entry.getV2()); + } + return null; + } + + @Override + public byte[] key() { + initializeQueue(); + var itr = getTopIterator(); + if (itr != null) { + return itr.key(); + } + return null; + } + + @Override + public byte[] value() { + initializeQueue(); + var itr = getTopIterator(); + if (itr != null) { + return itr.value(); + } + return null; + } + + @Override + public void close() { + iterators.forEach(HgKvIterator::close); + } + + @Override + public byte[] position() { + initializeQueue(); + var itr = getTopIterator(); + if (itr != null) { + return itr.position(); + } + return null; + } + + @Override + public void seek(byte[] position) { + initializeQueue(); + var itr = getTopIterator(); + if (itr != null) { + itr.seek(position); + } + } + + @Override + public boolean hasNext() { + initializeQueue(); + return ! this.pq.isEmpty(); + } + + @Override + public E next() { + var pair = this.pq.poll(); + assert pair != null; + if (iterators.get(pair.getV2()).hasNext()) { + this.pq.offer(Tuple2.of(iterators.get(pair.getV2()).next(), pair.getV2())); + } + return pair.getV1(); + } +} diff --git a/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/query/StreamStrictOrderIterator.java b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/query/StreamStrictOrderIterator.java new file mode 100644 index 0000000000..2d7f6090ac --- /dev/null +++ b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/query/StreamStrictOrderIterator.java @@ -0,0 +1,117 @@ +/* + * 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.store.client.query; + +import org.apache.hugegraph.pd.common.PDException; +import org.apache.hugegraph.store.HgKvIterator; +import org.apache.hugegraph.store.client.HgStoreNodePartitioner; +import org.apache.hugegraph.store.query.QueryTypeParam; +import org.apache.hugegraph.structure.BaseElement; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.NoSuchElementException; + +/** + * 严格按照id scan的顺序排序 + * 要处理好错位的问题 + * + * @param + */ +public class StreamStrictOrderIterator implements HgKvIterator { + + private Iterator ids; + + private Map> iteratorMap = new HashMap<>(); + + private E data; + + private String graph; + + private HgStoreNodePartitioner nodePartitioner; + + public StreamStrictOrderIterator(List ids, List> iteratorList, + String graph, HgStoreNodePartitioner nodePartitioner) { + this.ids = ids.iterator(); + for (var itr : iteratorList) { + var itr2 = (StreamKvIterator) itr; + iteratorMap.put(itr2.getAddress(), itr2); + } + this.graph = graph; + this.nodePartitioner = nodePartitioner; + } + + @Override + public byte[] key() { + return new byte[0]; + } + + @Override + public byte[] value() { + return new byte[0]; + } + + @Override + public void close() { + + } + + @Override + public byte[] position() { + return new byte[0]; + } + + @Override + public void seek(byte[] position) { + + } + + @Override + public boolean hasNext() { + data = null; + + while (ids.hasNext()) { + try { + var param = ids.next(); + var addr = this.nodePartitioner.partition(graph, param.getCode()); + var itr = iteratorMap.get(addr); + if (itr.hasNext()) { + var t = (BaseElement) itr.next(); + if (Arrays.equals(t.id().asBytes(), param.getStart())) { + this.data = (E) t; + return true; + } + } + } catch (PDException e) { + throw new RuntimeException(e); + } + } + return false; + } + + @Override + public E next() { + if (data == null) { + throw new NoSuchElementException(); + } + return data; + } +} diff --git a/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/util/Base58.java b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/util/Base58.java new file mode 100644 index 0000000000..8c346e6505 --- /dev/null +++ b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/util/Base58.java @@ -0,0 +1,179 @@ +/* + * 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.store.client.util; + +import java.io.UnsupportedEncodingException; +import java.math.BigInteger; + +/** + * Created with IntelliJ IDEA. + * User: noah + * Date: 8/2/13 + * Time: 10:36 AM + * To change this template use File | Settings | File Templates. + */ +public class Base58 { + + public static final char[] ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz".toCharArray(); + private static final int[] INDEXES = new int[128]; + + static { + for (int i = 0; i < INDEXES.length; i++) { + INDEXES[i] = -1; + } + for (int i = 0; i < ALPHABET.length; i++) { + INDEXES[ALPHABET[i]] = i; + } + } + + /** + * Encodes the given bytes in base58. No checksum is appended. + */ + public static String encode(byte[] input) { + if (input.length == 0) { + return ""; + } + input = copyOfRange(input, 0, input.length); + // Count leading zeroes. + int zeroCount = 0; + while (zeroCount < input.length && input[zeroCount] == 0) { + ++zeroCount; + } + // The actual encoding. + byte[] temp = new byte[input.length * 2]; + int j = temp.length; + + int startAt = zeroCount; + while (startAt < input.length) { + byte mod = divmod58(input, startAt); + if (input[startAt] == 0) { + ++startAt; + } + temp[--j] = (byte) ALPHABET[mod]; + } + + // Strip extra '1' if there are some after decoding. + while (j < temp.length && temp[j] == ALPHABET[0]) { + ++j; + } + // Add as many leading '1' as there were leading zeros. + while (--zeroCount >= 0) { + temp[--j] = (byte) ALPHABET[0]; + } + + byte[] output = copyOfRange(temp, j, temp.length); + try { + return new String(output, "US-ASCII"); + } catch (UnsupportedEncodingException e) { + throw new RuntimeException(e); // Cannot happen. + } + } + + public static byte[] decode(String input) throws IllegalArgumentException { + if (input.length() == 0) { + return new byte[0]; + } + byte[] input58 = new byte[input.length()]; + // Transform the String to a base58 byte sequence + for (int i = 0; i < input.length(); ++i) { + char c = input.charAt(i); + + int digit58 = -1; + if (c >= 0 && c < 128) { + digit58 = INDEXES[c]; + } + if (digit58 < 0) { + throw new IllegalArgumentException("Illegal character " + c + " at " + i); + } + + input58[i] = (byte) digit58; + } + // Count leading zeroes + int zeroCount = 0; + while (zeroCount < input58.length && input58[zeroCount] == 0) { + ++zeroCount; + } + // The encoding + byte[] temp = new byte[input.length()]; + int j = temp.length; + + int startAt = zeroCount; + while (startAt < input58.length) { + byte mod = divmod256(input58, startAt); + if (input58[startAt] == 0) { + ++startAt; + } + + temp[--j] = mod; + } + // Do no add extra leading zeroes, move j to first non null byte. + while (j < temp.length && temp[j] == 0) { + ++j; + } + + return copyOfRange(temp, j - zeroCount, temp.length); + } + + public static BigInteger decodeToBigInteger(String input) throws IllegalArgumentException { + return new BigInteger(1, decode(input)); + } + + // + // number -> number / 58, returns number % 58 + // + private static byte divmod58(byte[] number, int startAt) { + int remainder = 0; + for (int i = startAt; i < number.length; i++) { + int digit256 = (int) number[i] & 0xFF; + int temp = remainder * 256 + digit256; + + number[i] = (byte) (temp / 58); + + remainder = temp % 58; + } + + return (byte) remainder; + } + + // + // number -> number / 256, returns number % 256 + // + private static byte divmod256(byte[] number58, int startAt) { + int remainder = 0; + for (int i = startAt; i < number58.length; i++) { + int digit58 = (int) number58[i] & 0xFF; + int temp = remainder * 58 + digit58; + + number58[i] = (byte) (temp / 256); + + remainder = temp % 256; + } + + return (byte) remainder; + } + + private static byte[] copyOfRange(byte[] source, int from, int to) { + byte[] range = new byte[to - from]; + System.arraycopy(source, from, range, 0, range.length); + + return range; + } + + +} + diff --git a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/controller/HgTestController.java b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/controller/HgTestController.java index dec310abb0..1add6f67cf 100644 --- a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/controller/HgTestController.java +++ b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/controller/HgTestController.java @@ -148,18 +148,18 @@ public String transferLeaders() { } } - @GetMapping(value = "/no_vote", produces = MediaType.APPLICATION_JSON_VALUE) - public String noVote() { - try { - nodeService.getStoreEngine().getPartitionEngines().values().forEach(engine -> { - engine.getRaftNode().disableVote(); - }); - return "OK"; - } catch (Exception e) { - log.error("pulse reset error: ", e); - return e.getMessage(); - } - } + //@GetMapping(value = "/no_vote", produces = MediaType.APPLICATION_JSON_VALUE) + //public String noVote() { + // try { + // nodeService.getStoreEngine().getPartitionEngines().values().forEach(engine -> { + // engine.getRaftNode().disableVote(); + // }); + // return "OK"; + // } catch (Exception e) { + // log.error("pulse reset error: ", e); + // return e.getMessage(); + // } + //} @GetMapping(value = "/restart_raft", produces = MediaType.APPLICATION_JSON_VALUE) public String restartRaft() { diff --git a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/listener/ContextClosedListener.java b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/listener/ContextClosedListener.java index bcca6111b0..501c809ebb 100644 --- a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/listener/ContextClosedListener.java +++ b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/listener/ContextClosedListener.java @@ -26,6 +26,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationListener; import org.springframework.context.event.ContextClosedEvent; +import org.springframework.stereotype.Service; import com.alipay.sofa.jraft.Status; import com.alipay.sofa.jraft.entity.PeerId; @@ -33,6 +34,7 @@ import lombok.extern.slf4j.Slf4j; @Slf4j +@Service public class ContextClosedListener implements ApplicationListener { @Autowired @@ -110,9 +112,14 @@ private void transferLeaders() { } }); - HgStoreEngine.getInstance().getPartitionEngines() - .forEach((integer, partitionEngine) -> partitionEngine.getRaftNode() - .disableVote()); + //HgStoreEngine.getInstance().getPartitionEngines() + // .forEach((integer, partitionEngine) -> partitionEngine.getRaftNode() + // .disableVote()); + //todo soya is this right? + HgStoreEngine.getInstance().getPartitionEngines().forEach( + ((integer, partitionEngine) -> partitionEngine.getRaftNode() + .shutdown()) + ); } catch (Exception e) { log.error("transfer leader failed: " + e.getMessage()); } From fce73e2f9488c7a43a8a54fba057c45e11b733c2 Mon Sep 17 00:00:00 2001 From: JisoLya <523420504@qq.com> Date: Thu, 28 Aug 2025 16:33:38 +0800 Subject: [PATCH 27/35] refactor: integrate cli module --- hugegraph-store/hg-store-cli/pom.xml | 5 + .../hugegraph/store/cli/CliApplication.java | 80 +++ .../store/cli/StoreConsoleApplication.java | 109 ---- .../apache/hugegraph/store/cli/cmd/Load.java | 189 ++++++ .../hugegraph/store/cli/cmd/MultiQuery.java | 155 +++++ .../GrpcShardScanner.java => cmd/Scan.java} | 93 +-- .../hugegraph/store/cli/cmd/ScanShard.java | 75 +++ .../store/cli/cmd/ScanSingleShard.java | 56 ++ .../hugegraph/store/cli/cmd/ScanTable.java | 100 +++ .../store/cli/loader/HgThread2DB.java | 568 ------------------ .../store/cli/scan/HgStoreCommitter.java | 85 --- .../store/cli/scan/HgStoreScanner.java | 237 -------- 12 files changed, 680 insertions(+), 1072 deletions(-) create mode 100644 hugegraph-store/hg-store-cli/src/main/java/org/apache/hugegraph/store/cli/CliApplication.java delete mode 100644 hugegraph-store/hg-store-cli/src/main/java/org/apache/hugegraph/store/cli/StoreConsoleApplication.java create mode 100644 hugegraph-store/hg-store-cli/src/main/java/org/apache/hugegraph/store/cli/cmd/Load.java create mode 100644 hugegraph-store/hg-store-cli/src/main/java/org/apache/hugegraph/store/cli/cmd/MultiQuery.java rename hugegraph-store/hg-store-cli/src/main/java/org/apache/hugegraph/store/cli/{scan/GrpcShardScanner.java => cmd/Scan.java} (54%) create mode 100644 hugegraph-store/hg-store-cli/src/main/java/org/apache/hugegraph/store/cli/cmd/ScanShard.java create mode 100644 hugegraph-store/hg-store-cli/src/main/java/org/apache/hugegraph/store/cli/cmd/ScanSingleShard.java create mode 100644 hugegraph-store/hg-store-cli/src/main/java/org/apache/hugegraph/store/cli/cmd/ScanTable.java delete mode 100644 hugegraph-store/hg-store-cli/src/main/java/org/apache/hugegraph/store/cli/loader/HgThread2DB.java delete mode 100644 hugegraph-store/hg-store-cli/src/main/java/org/apache/hugegraph/store/cli/scan/HgStoreCommitter.java delete mode 100644 hugegraph-store/hg-store-cli/src/main/java/org/apache/hugegraph/store/cli/scan/HgStoreScanner.java diff --git a/hugegraph-store/hg-store-cli/pom.xml b/hugegraph-store/hg-store-cli/pom.xml index 84de815696..ab9999e56c 100644 --- a/hugegraph-store/hg-store-cli/pom.xml +++ b/hugegraph-store/hg-store-cli/pom.xml @@ -56,6 +56,11 @@ hg-pd-client ${revision} + + org.apache.hugegraph + hg-pd-cli + ${revision} + org.projectlombok lombok diff --git a/hugegraph-store/hg-store-cli/src/main/java/org/apache/hugegraph/store/cli/CliApplication.java b/hugegraph-store/hg-store-cli/src/main/java/org/apache/hugegraph/store/cli/CliApplication.java new file mode 100644 index 0000000000..4e5587b445 --- /dev/null +++ b/hugegraph-store/hg-store-cli/src/main/java/org/apache/hugegraph/store/cli/CliApplication.java @@ -0,0 +1,80 @@ +/* + * 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.store.cli; + +import org.apache.hugegraph.store.cli.cmd.Load; +import org.apache.hugegraph.store.cli.cmd.MultiQuery; +import org.apache.hugegraph.store.cli.cmd.ScanShard; +import org.apache.hugegraph.store.cli.cmd.ScanSingleShard; +import org.apache.hugegraph.store.cli.cmd.ScanTable; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +import org.apache.hugegraph.pd.cli.cmd.ChangeRaft; +import org.apache.hugegraph.pd.cli.cmd.CheckPeers; +import org.apache.hugegraph.pd.cli.cmd.Command; +import org.apache.hugegraph.pd.cli.cmd.Parameter; +import lombok.extern.slf4j.Slf4j; + +; + +/** + * 2022/2/14 + */ +@SpringBootApplication +@Slf4j +public class CliApplication { + + public static void main(String[] args) { + Parameter parameter; + try { + parameter = Command.toParameter(args); + Command command; + switch (parameter.getCmd()) { + case "load": + command = new Load(parameter.getPd()); + break; + case "change_raft": + command = new ChangeRaft(parameter.getPd()); + break; + case "check_peers": + command = new CheckPeers(parameter.getPd()); + break; + case "query": + command = new MultiQuery(parameter.getPd()); + break; + case "scan": + command = new ScanTable(parameter.getPd()); + break; + case "shard": + command = new ScanShard(parameter.getPd()); + break; + case "shard-single": + command = new ScanSingleShard(parameter.getPd()); + break; + default: + log.error("无效的指令"); + return; + } + command.action(parameter.getParams()); + } catch (Exception e) { + log.error("run cli command with error:", e); + } + System.exit(0); + + } +} diff --git a/hugegraph-store/hg-store-cli/src/main/java/org/apache/hugegraph/store/cli/StoreConsoleApplication.java b/hugegraph-store/hg-store-cli/src/main/java/org/apache/hugegraph/store/cli/StoreConsoleApplication.java deleted file mode 100644 index 51e3c09b7e..0000000000 --- a/hugegraph-store/hg-store-cli/src/main/java/org/apache/hugegraph/store/cli/StoreConsoleApplication.java +++ /dev/null @@ -1,109 +0,0 @@ -/* - * 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.store.cli; - -import java.io.IOException; - -import org.apache.hugegraph.pd.client.PDConfig; -import org.apache.hugegraph.pd.common.PDException; -import org.apache.hugegraph.store.HgStoreClient; -import org.apache.hugegraph.store.cli.loader.HgThread2DB; -import org.apache.hugegraph.store.cli.scan.GrpcShardScanner; -import org.apache.hugegraph.store.cli.scan.HgStoreScanner; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.CommandLineRunner; -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; - -import lombok.extern.slf4j.Slf4j; - -/** - * 2022/2/14 - */ -@SpringBootApplication -@Slf4j -public class StoreConsoleApplication implements CommandLineRunner { - - // TODO: this package seems to have many useless class and code, need to be updated. - @Autowired - private AppConfig appConfig; - - public static void main(String[] args) { - log.info("Starting StoreConsoleApplication"); - SpringApplication.run(StoreConsoleApplication.class, args); - log.info("StoreConsoleApplication finished."); - } - - @Override - public void run(String... args) throws IOException, InterruptedException, PDException { - if (args.length <= 0) { - log.warn("Parameter type cmd[-load, -query, -scan]"); - } else { - switch (args[0]) { - case "-load": - HgThread2DB hgThread2DB = new HgThread2DB(args[1]); - if (!args[3].isEmpty()) { - hgThread2DB.setGraphName(args[3]); - } - try { - if ("order".equals(args[2])) { - hgThread2DB.testOrder(args[4]); - } else { - hgThread2DB.startMultiprocessInsert(args[2]); - } - } catch (IOException e) { - e.printStackTrace(); - } - break; - case "-query": - HgThread2DB hgDB = new HgThread2DB(args[1]); - try { - hgDB.startMultiprocessQuery("12", args[2]); - } catch (IOException e) { - e.printStackTrace(); - } - break; - case "-scan": - if (args.length < 4) { - log.warn("Parameter type -scan pd graphName tableName"); - } else { - doScan(args[1], args[2], args[3]); - } - break; - case "-shard": - GrpcShardScanner scanner = new GrpcShardScanner(); - scanner.getData(); - break; - case "-shard-single": - scanner = new GrpcShardScanner(); - scanner.getDataSingle(); - break; - default: - log.warn("Parameter type error, no program executed"); - } - } - } - - private void doScan(String pd, String graphName, String tableName) throws PDException { - HgStoreClient storeClient = HgStoreClient.create(PDConfig.of(pd) - .setEnableCache(true)); - - HgStoreScanner storeScanner = HgStoreScanner.of(storeClient, graphName); - storeScanner.scanTable2(tableName); - } -} diff --git a/hugegraph-store/hg-store-cli/src/main/java/org/apache/hugegraph/store/cli/cmd/Load.java b/hugegraph-store/hg-store-cli/src/main/java/org/apache/hugegraph/store/cli/cmd/Load.java new file mode 100644 index 0000000000..04f88a5c97 --- /dev/null +++ b/hugegraph-store/hg-store-cli/src/main/java/org/apache/hugegraph/store/cli/cmd/Load.java @@ -0,0 +1,189 @@ +/* + * 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.store.cli.cmd; + +import java.io.BufferedReader; +import java.io.FileInputStream; +import java.io.InputStreamReader; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.Semaphore; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicLong; + +import org.apache.hugegraph.pd.cli.cmd.Command; +import org.apache.hugegraph.store.HgOwnerKey; +import org.apache.hugegraph.store.HgStoreClient; +import org.apache.hugegraph.store.HgStoreSession; +import org.apache.hugegraph.store.cli.util.HgCliUtil; + +import lombok.extern.slf4j.Slf4j; + +/** + * @date 2023/10/18 + **/ +@Slf4j +public class Load extends Command { + + private static int batchSize = 100000; + private static int readerSize = 5; + private static long printSize = 10000000; + private static long printCount = printSize * 1000; + private int pc = Runtime.getRuntime().availableProcessors(); + private int size = pc * 2; + private Semaphore semaphore = new Semaphore(size); + private ThreadPoolExecutor executor = new ThreadPoolExecutor(size, size, + 0L, TimeUnit.MILLISECONDS, + new LinkedBlockingQueue()); + private LinkedBlockingQueue> queue = new LinkedBlockingQueue<>(size * 2); + private HgStoreClient storeClient; + private AtomicLong insertCount = new AtomicLong(); + private AtomicLong startTime = new AtomicLong(); + private String table = "unknown"; + private AtomicBoolean completed = new AtomicBoolean(false); + private String graph; + protected Runnable r = () -> { + long start = System.currentTimeMillis(); + try { + while (!completed.get() || queue.peek() != null) { + semaphore.acquire(); + List data = queue.take(); + Runnable task = () -> { + try { + put(table, data); + } catch (Exception e) { + log.error("put data with error:", e); + } finally { + semaphore.release(); + } + }; + executor.submit(task); + } + semaphore.acquire(size); + semaphore.release(size); + log.info("*************************************************"); + long all = insertCount.get(); + long end = System.currentTimeMillis(); + log.info("Load data: {}s,total: {} entries,average:{} entries/s", (end - start) / 1000, + all, all * 1000 / (end - start)); + log.info("*************************************************"); + } catch (Exception e) { + log.error("submit task with error:", e); + } finally { + try { + executor.shutdownNow(); + } catch (Exception e) { + + } finally { + + } + } + }; + + public Load(String pd) { + super(pd); + storeClient = HgStoreClient.create(config); + } + + @Override + public void action(String[] params) throws InterruptedException { + graph = params[0]; + new Thread(r, "load").start(); + String path = params[1]; + String[] split = path.split(","); + readerSize = split.length; + CountDownLatch latch = new CountDownLatch(readerSize); + log.info("--- start data loading---"); + for (int i = 0; i < readerSize; i++) { + int fi = i; + new Thread(() -> { + try { + InputStreamReader isr = + new InputStreamReader(new FileInputStream(split[fi]), "UTF-8"); + BufferedReader reader = new BufferedReader(isr); + long count = 0; + String line; + try { + List keys = new ArrayList<>(batchSize); + while ((line = reader.readLine()) != null) { + keys.add(line); + count++; + if (count % batchSize == 0) { + List data = keys; + if (!data.isEmpty()) { + queue.put(keys); + keys = new ArrayList<>(batchSize); + } + continue; + } + } + if (count % batchSize != 0) { + queue.put(keys); + } + } catch (Exception e) { + throw new RuntimeException(e); + } finally { + isr.close(); + reader.close(); + } + } catch (Exception e) { + log.error("send data with error:", e); + } finally { + latch.countDown(); + } + }).start(); + } + latch.await(); + completed.set(true); + } + + public boolean put(String table, List keys) { + HgStoreSession session = storeClient.openSession(graph); + session.beginTx(); + keys.forEach((key) -> { + int j = key.indexOf("\t"); + String owner = key.substring(0, j); + HgOwnerKey hgKey = HgCliUtil.toOwnerKey(owner, owner); + byte[] value = HgCliUtil.toBytes(key.substring(j + 1)); + session.put(table, hgKey, value); + }); + if (!keys.isEmpty()) { + if (session.isTx()) { + session.commit(); + } else { + session.rollback(); + } + } + long sum; + if ((sum = insertCount.addAndGet(keys.size())) % printSize == 0) { + long c = System.currentTimeMillis(); + long start = startTime.getAndSet(c); + if (c > start) { + log.info("count: {}, tps: {}, worker: {},task queue: {}", sum, + printCount / (c - start), + executor.getActiveCount(), queue.size()); + } + } + return true; + } + +} diff --git a/hugegraph-store/hg-store-cli/src/main/java/org/apache/hugegraph/store/cli/cmd/MultiQuery.java b/hugegraph-store/hg-store-cli/src/main/java/org/apache/hugegraph/store/cli/cmd/MultiQuery.java new file mode 100644 index 0000000000..8bf2001c8d --- /dev/null +++ b/hugegraph-store/hg-store-cli/src/main/java/org/apache/hugegraph/store/cli/cmd/MultiQuery.java @@ -0,0 +1,155 @@ +/* + * 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.store.cli.cmd; + +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicLong; + +import org.apache.hugegraph.pd.cli.cmd.Command; +import org.apache.hugegraph.store.HgKvEntry; +import org.apache.hugegraph.store.HgKvIterator; +import org.apache.hugegraph.store.HgOwnerKey; +import org.apache.hugegraph.store.HgScanQuery; +import org.apache.hugegraph.store.HgStoreClient; +import org.apache.hugegraph.store.HgStoreSession; +import org.apache.hugegraph.store.cli.util.HgCliUtil; +import org.apache.hugegraph.store.client.grpc.KvCloseableIterator; +import org.apache.hugegraph.store.client.util.MetricX; + +import lombok.extern.slf4j.Slf4j; + +/** + * Multi-thread query + * point 起始查询点,后续根据这个点查询到的value做为下一次的查询条件进行迭代 + * scanCount 允许启动的线程数量 + * + * @date 2023/10/20 + **/ +@Slf4j +public class MultiQuery extends Command { + + private static AtomicLong total = new AtomicLong(); + private static int batchLimit = 100; + private final HgStoreClient storeClient; + public String graphName = "hugegraphtest"; + volatile long startTime = System.currentTimeMillis(); + + public MultiQuery(String pd) { + super(pd); + storeClient = HgStoreClient.create(config); + } + + @Override + public void action(String[] params) throws Exception { + String point = params[0]; + String scanCount = params[1]; + log.info("--- start startMultiprocessQuery---"); + startTime = System.currentTimeMillis(); + MetricX metrics = MetricX.ofStart(); + batchLimit = Integer.parseInt(scanCount); + CountDownLatch latch = new CountDownLatch(batchLimit); + HgStoreSession session = storeClient.openSession(graphName); + final AtomicLong[] counter = {new AtomicLong()}; + final long[] start = {System.currentTimeMillis()}; + LinkedBlockingQueue[] queue = new LinkedBlockingQueue[batchLimit]; + for (int i = 0; i < batchLimit; i++) { + queue[i] = new LinkedBlockingQueue(); + } + List strKey = + Arrays.asList(new String[]{"20727483", "50329304", "26199460", "1177521", + "27960125", + "30440025", "15833920", "15015183", "33153097", + "21250581"}); + strKey.forEach(key -> { + log.info("newkey:{}", key); + HgOwnerKey hgKey = HgCliUtil.toOwnerKey(key, key); + queue[0].add(hgKey); + }); + + for (int i = 0; i < batchLimit; i++) { + int finalI = i; + KvCloseableIterator> iterators = + session.scanBatch2( + HgScanQuery.prefixIteratorOf(HgCliUtil.TABLE_NAME, new Iterator<>() { + HgOwnerKey current = null; + + @Override + public boolean hasNext() { + while (current == null) { + try { + current = (HgOwnerKey) queue[finalI].poll(1, + TimeUnit.SECONDS); + } catch (InterruptedException e) { + // + } + } + if (current == null) { + log.info("===== current is null =========="); + } + return current != null; + } + + @Override + public HgOwnerKey next() { + return current; + } + }) + ); + + new Thread(() -> { + while (iterators.hasNext()) { + HgKvIterator iterator = iterators.next(); + long c = 0; + while (iterator.hasNext()) { + String newPoint = HgCliUtil.toStr(iterator.next().value()); + HgOwnerKey newHgKey = HgCliUtil.toOwnerKey(newPoint, newPoint); + if (queue[(int) (c % batchLimit)].size() < 1000000) { + queue[(int) (c % batchLimit)].add(newHgKey); + } + c++; + } + if (counter[0].addAndGet(c) > 1000000) { + synchronized (counter) { + if (counter[0].get() > 10000000) { + log.info("count {}, qps {}", counter[0].get(), + counter[0].get() * 1000 / + (System.currentTimeMillis() - start[0])); + start[0] = System.currentTimeMillis(); + counter[0].set(0); + } + } + } + } + }, "client query thread:" + i).start(); + log.info("===== read thread exit =========="); + } + latch.await(); + + metrics.end(); + log.info("*************************************************"); + log.info(" 主进程执行时间:" + metrics.past() / 1000 + "秒; 查询:" + total.get() + + "次,qps:" + total.get() * 1000 / metrics.past()); + log.info("*************************************************"); + System.out.println("-----主进程执行结束---------"); + } +} diff --git a/hugegraph-store/hg-store-cli/src/main/java/org/apache/hugegraph/store/cli/scan/GrpcShardScanner.java b/hugegraph-store/hg-store-cli/src/main/java/org/apache/hugegraph/store/cli/cmd/Scan.java similarity index 54% rename from hugegraph-store/hg-store-cli/src/main/java/org/apache/hugegraph/store/cli/scan/GrpcShardScanner.java rename to hugegraph-store/hg-store-cli/src/main/java/org/apache/hugegraph/store/cli/cmd/Scan.java index e9e10829f0..4d7cb36153 100644 --- a/hugegraph-store/hg-store-cli/src/main/java/org/apache/hugegraph/store/cli/scan/GrpcShardScanner.java +++ b/hugegraph-store/hg-store-cli/src/main/java/org/apache/hugegraph/store/cli/cmd/Scan.java @@ -15,87 +15,48 @@ * limitations under the License. */ -package org.apache.hugegraph.store.cli.scan; +package org.apache.hugegraph.store.cli.cmd; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CountDownLatch; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.LinkedBlockingQueue; -import java.util.concurrent.ThreadPoolExecutor; -import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import org.apache.hugegraph.store.grpc.GraphStoreGrpc; import org.apache.hugegraph.store.grpc.GraphStoreGrpc.GraphStoreStub; import org.apache.hugegraph.store.grpc.Graphpb.ScanPartitionRequest; import org.apache.hugegraph.store.grpc.Graphpb.ScanPartitionRequest.Reply; +import org.apache.hugegraph.store.grpc.Graphpb.ScanPartitionRequest.ScanType; import org.apache.hugegraph.store.grpc.Graphpb.ScanResponse; import io.grpc.ManagedChannel; import io.grpc.ManagedChannelBuilder; import io.grpc.stub.StreamObserver; -import lombok.extern.slf4j.Slf4j; -@Slf4j -public class GrpcShardScanner { +/** + * @date 2023/10/20 + **/ - private final boolean closed = false; - private final AtomicInteger sum = new AtomicInteger(); - private final ConcurrentHashMap> - observers = new ConcurrentHashMap<>(); +public interface Scan { - public void getData() { - ExecutorService service = new ThreadPoolExecutor(500, Integer.MAX_VALUE, - 0L, - TimeUnit.MILLISECONDS, - new LinkedBlockingQueue<>()); - long start = System.currentTimeMillis(); + AtomicInteger sum = new AtomicInteger(); + ConcurrentHashMap> observers = + new ConcurrentHashMap<>(); - String[] addresses = new String[]{"10.14.139.71:8500", - "10.14.139.70:8500", - "10.14.139.69:8500"}; - int pSize = 72; - int size = pSize * addresses.length; - CountDownLatch latch = new CountDownLatch(size); - for (int j = 0; j < pSize; j++) { - for (int i = 0; i < addresses.length; i++) { - String address = addresses[i]; - int finalJ = j; - service.execute(() -> getData(finalJ, latch, address)); - } - } - try { - latch.await(); - } catch (InterruptedException e) { - e.printStackTrace(); - } - long end = System.currentTimeMillis(); - long cost = end - start; - log.info("all rows are: {}, cost: {},avg: {}", sum.get(), - cost, sum.get() / cost * 1000); - } - - public void getData(int pId, CountDownLatch latch, String address) { + default void getData(int pId, CountDownLatch latch, String address) { try { - ScanPartitionRequest.Builder builder = - ScanPartitionRequest.newBuilder(); - ScanPartitionRequest.Request.Builder srb = - ScanPartitionRequest.Request.newBuilder(); + ScanPartitionRequest.Builder builder = ScanPartitionRequest.newBuilder(); + ScanPartitionRequest.Request.Builder srb = ScanPartitionRequest.Request.newBuilder(); ScanPartitionRequest.Request request = - srb.setGraphName("DEFAULT/hugegraph2/g") - .setScanType( - ScanPartitionRequest.ScanType.SCAN_EDGE) + srb.setGraphName("DEFAULT/hugegraph2/g").setScanType( + ScanType.SCAN_EDGE) .setTable("g+oe").setBoundary(0x10) .setPartitionId(pId).build(); - ManagedChannel c = - ManagedChannelBuilder.forTarget(address) - .usePlaintext().build(); + ManagedChannel c = ManagedChannelBuilder.forTarget(address) + .usePlaintext().build(); int maxSize = 1024 * 1024 * 1024; GraphStoreStub stub; - stub = GraphStoreGrpc.newStub(c) - .withMaxInboundMessageSize(maxSize) + stub = GraphStoreGrpc.newStub(c).withMaxInboundMessageSize(maxSize) .withMaxOutboundMessageSize(maxSize); - AtomicInteger count = new AtomicInteger(); long start = System.currentTimeMillis(); long id = Thread.currentThread().getId(); @@ -107,7 +68,7 @@ public void onNext(ScanResponse value) { int edgeSize = value.getEdgeCount(); int vertexSize = value.getVertexCount(); if (request.getScanType().equals( - ScanPartitionRequest.ScanType.SCAN_VERTEX)) { + ScanType.SCAN_VERTEX)) { count.getAndAdd(vertexSize); } else { count.getAndAdd(edgeSize); @@ -130,38 +91,24 @@ public void onNext(ScanResponse value) { @Override public void onError(Throwable t) { - log.warn("Calling grpc interface encountered an error", t); latch.countDown(); } @Override public void onCompleted() { long time = System.currentTimeMillis() - start; - log.info("scan id : {}, complete: {} ,time:{}", - pId, count.get(), time); sum.addAndGet(count.get()); latch.countDown(); } }; - StreamObserver observer = - stub.scanPartition(ro); + StreamObserver observer = stub.scanPartition(ro); observers.put(id, observer); builder.setScanRequest(request); observer.onNext(builder.build()); } catch (Exception e) { e.printStackTrace(); - } - } + } finally { - public void getDataSingle() { - CountDownLatch latch = new CountDownLatch(1); - new Thread(() -> getData(58, latch, "10.14.139.71:8500")).start(); - try { - latch.await(); - } catch (InterruptedException e) { - e.printStackTrace(); } - log.info("all rows are: {}", sum.get()); } - } diff --git a/hugegraph-store/hg-store-cli/src/main/java/org/apache/hugegraph/store/cli/cmd/ScanShard.java b/hugegraph-store/hg-store-cli/src/main/java/org/apache/hugegraph/store/cli/cmd/ScanShard.java new file mode 100644 index 0000000000..be362f1a5f --- /dev/null +++ b/hugegraph-store/hg-store-cli/src/main/java/org/apache/hugegraph/store/cli/cmd/ScanShard.java @@ -0,0 +1,75 @@ +/* + * 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.store.cli.cmd; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +import org.apache.hugegraph.pd.cli.cmd.Command; + +import lombok.extern.slf4j.Slf4j; + +/** + * @author zhangyingjie + * @date 2023/10/18 + **/ +@Slf4j +public class ScanShard extends Command implements Scan { + + private AtomicInteger sum = new AtomicInteger(); + + public ScanShard(String pd) { + super(pd); + } + + @Override + public void action(String[] params) { + ExecutorService service = new ThreadPoolExecutor(500, Integer.MAX_VALUE, + 0L, + TimeUnit.MILLISECONDS, + new LinkedBlockingQueue<>()); + long start = System.currentTimeMillis(); + String[] addresses = new String[]{"10.14.139.71:8500", + "10.14.139.70:8500", + "10.14.139.69:8500"}; + int pSize = 72; + int size = pSize * addresses.length; + CountDownLatch latch = new CountDownLatch(size); + for (int j = 0; j < pSize; j++) { + for (int i = 0; i < addresses.length; i++) { + String address = addresses[i]; + int finalJ = j; + service.execute(() -> getData(finalJ, latch, address)); + } + } + try { + latch.await(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + long end = System.currentTimeMillis(); + long cost = end - start; + log.info("all rows are: {}, cost: {},avg: {}", sum.get(), + cost, sum.get() / cost * 1000); + service.shutdownNow(); + } +} diff --git a/hugegraph-store/hg-store-cli/src/main/java/org/apache/hugegraph/store/cli/cmd/ScanSingleShard.java b/hugegraph-store/hg-store-cli/src/main/java/org/apache/hugegraph/store/cli/cmd/ScanSingleShard.java new file mode 100644 index 0000000000..5eee161a6f --- /dev/null +++ b/hugegraph-store/hg-store-cli/src/main/java/org/apache/hugegraph/store/cli/cmd/ScanSingleShard.java @@ -0,0 +1,56 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.hugegraph.store.cli.cmd; + +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.atomic.AtomicInteger; + +import org.apache.hugegraph.pd.cli.cmd.Command; +import org.apache.hugegraph.store.grpc.Graphpb.ScanPartitionRequest; + +import io.grpc.stub.StreamObserver; +import lombok.extern.slf4j.Slf4j; + +/** + * @date 2023/10/18 + **/ +@Slf4j +public class ScanSingleShard extends Command implements Scan { + + private volatile boolean closed = false; + private AtomicInteger sum = new AtomicInteger(); + private ConcurrentHashMap> + observers = new ConcurrentHashMap<>(); + + public ScanSingleShard(String pd) { + super(pd); + } + + @Override + public void action(String[] params) { + CountDownLatch latch = new CountDownLatch(1); + new Thread(() -> getData(58, latch, "10.14.139.71:8500")).start(); + try { + latch.await(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + log.info("all rows are: {}", sum.get()); + } +} diff --git a/hugegraph-store/hg-store-cli/src/main/java/org/apache/hugegraph/store/cli/cmd/ScanTable.java b/hugegraph-store/hg-store-cli/src/main/java/org/apache/hugegraph/store/cli/cmd/ScanTable.java new file mode 100644 index 0000000000..0bea59d9ae --- /dev/null +++ b/hugegraph-store/hg-store-cli/src/main/java/org/apache/hugegraph/store/cli/cmd/ScanTable.java @@ -0,0 +1,100 @@ +/* + * 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.store.cli.cmd; + +import java.util.List; + +import org.apache.hugegraph.pd.cli.cmd.Command; +import org.apache.hugegraph.pd.client.PDClient; +import org.apache.hugegraph.pd.common.PDException; +import org.apache.hugegraph.pd.grpc.Metapb; +import org.apache.hugegraph.store.HgKvEntry; +import org.apache.hugegraph.store.HgKvIterator; +import org.apache.hugegraph.store.HgKvStore; +import org.apache.hugegraph.store.HgStoreClient; +import org.apache.hugegraph.store.HgStoreSession; +import org.apache.hugegraph.store.cli.util.HgMetricX; + +import lombok.extern.slf4j.Slf4j; + +/** + * @date 2023/10/18 + **/ +@Slf4j +public class ScanTable extends Command { + + public static final byte[] EMPTY_BYTES = new byte[0]; + private HgStoreClient storeClient; + + public ScanTable(String pd) { + super(pd); + storeClient = HgStoreClient.create(config); + } + + @Override + public void action(String[] params) throws PDException { + String graphName = params[0]; + String tableName = params[1]; + PDClient pdClient = storeClient.getPdClient(); + List partitions = pdClient.getPartitions(0, graphName); + HgStoreSession session = storeClient.openSession(graphName); + int count = 0; + byte[] position = null; + HgMetricX metricX = HgMetricX.ofStart(); + for (Metapb.Partition partition : partitions) { + while (true) { + try (HgKvIterator iterator = session.scanIterator(tableName, + (int) (partition.getStartKey()), + (int) (partition.getEndKey()), + HgKvStore.SCAN_HASHCODE, + EMPTY_BYTES)) { + if (position != null) { + iterator.seek(position); + } + while (iterator.hasNext()) { + iterator.next(); + count++; + if (count % 3000 == 0) { + if (iterator.hasNext()) { + iterator.next(); + position = iterator.position(); + System.out.println("count is " + count); + } else { + position = null; + } + break; + } + } + if (!iterator.hasNext()) { + position = null; + break; + } + } + } + } + metricX.end(); + log.info("*************************************************"); + log.info("************* Scanning Completed **************"); + log.info("Graph: {}", graphName); + log.info("Table: {}", tableName); + log.info("Keys: {}", count); + log.info("Total: {} seconds.", metricX.past() / 1000); + log.info("*************************************************"); + } + +} diff --git a/hugegraph-store/hg-store-cli/src/main/java/org/apache/hugegraph/store/cli/loader/HgThread2DB.java b/hugegraph-store/hg-store-cli/src/main/java/org/apache/hugegraph/store/cli/loader/HgThread2DB.java deleted file mode 100644 index eab9c195fa..0000000000 --- a/hugegraph-store/hg-store-cli/src/main/java/org/apache/hugegraph/store/cli/loader/HgThread2DB.java +++ /dev/null @@ -1,568 +0,0 @@ -/* - * 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.store.cli.loader; - -import java.io.BufferedReader; -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStreamReader; -import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashSet; -import java.util.Iterator; -import java.util.List; -import java.util.concurrent.ArrayBlockingQueue; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.LinkedBlockingQueue; -import java.util.concurrent.ThreadPoolExecutor; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.concurrent.atomic.AtomicLong; - -import org.apache.hugegraph.pd.client.PDClient; -import org.apache.hugegraph.pd.client.PDConfig; -import org.apache.hugegraph.store.HgKvEntry; -import org.apache.hugegraph.store.HgKvIterator; -import org.apache.hugegraph.store.HgOwnerKey; -import org.apache.hugegraph.store.HgScanQuery; -import org.apache.hugegraph.store.HgStoreClient; -import org.apache.hugegraph.store.HgStoreSession; -import org.apache.hugegraph.store.cli.util.HgCliUtil; -import org.apache.hugegraph.store.client.grpc.KvCloseableIterator; -import org.apache.hugegraph.store.client.util.MetricX; - -import lombok.extern.slf4j.Slf4j; - -/** - * Use pd, support raft - * Read files and perform multi-threaded storage processing. - */ -@Slf4j -public class HgThread2DB { - - /* Total number of tasks in progress and in queue */ - private static final AtomicInteger taskTotal = new AtomicInteger(0); - private static final AtomicInteger queryTaskTotal = new AtomicInteger(0); - private static final AtomicLong insertDataCount = new AtomicLong(); - private static final AtomicLong queryCount = new AtomicLong(); - private static final AtomicLong totalQueryCount = new AtomicLong(); - private static final AtomicLong longId = new AtomicLong(); - private static final CountDownLatch countDownLatch = null; - private static PDClient pdClient; - private static ThreadPoolExecutor threadPool = null; - private static ThreadPoolExecutor queryThreadPool = null; - private static int limitScanBatchCount = 100; - private static ArrayBlockingQueue listQueue = null; - private final HgStoreClient storeClient; - public String graphName = "hugegraphtest"; - volatile long startTime = System.currentTimeMillis(); - - public HgThread2DB(String pdAddr) { - int threadCount = Runtime.getRuntime().availableProcessors(); - - listQueue = new ArrayBlockingQueue>(100000000); - queryThreadPool = new ThreadPoolExecutor(500, 1000, - 200, TimeUnit.SECONDS, - new ArrayBlockingQueue<>(1000)); - threadPool = new ThreadPoolExecutor(threadCount * 2, threadCount * 3, - 200, TimeUnit.SECONDS, - new ArrayBlockingQueue<>(threadCount + 100)); - storeClient = HgStoreClient.create(PDConfig.of(pdAddr) - .setEnableCache(true)); - pdClient = storeClient.getPdClient(); - } - - public void setGraphName(String graphName) { - this.graphName = graphName; - log.info("setGraphName {}", graphName); - } - - public boolean singlePut(String tableName - , List keys) throws InterruptedException { - HgStoreSession session = storeClient.openSession(graphName); - session.beginTx(); - - keys.forEach((strKey) -> { - insertDataCount.getAndIncrement(); - int j = strKey.indexOf("\t"); -// byte[] key = HgCliUtil.toBytes(strKey.substring(0, j)); - HgOwnerKey hgKey = HgCliUtil.toOwnerKey(strKey.substring(0, j), strKey); - byte[] value = HgCliUtil.toBytes(strKey.substring(j + 1)); - session.put(tableName, hgKey, value); - - }); - if (insertDataCount.get() > 10000000) { - synchronized (insertDataCount) { - long count = insertDataCount.get(); - insertDataCount.set(0); - if (count > 10000000) { - log.info("count : " + count + " qps : " + - count * 1000 / (System.currentTimeMillis() - startTime) - + " threadCount : " + taskTotal); - startTime = System.currentTimeMillis(); - } - } - } - if (!keys.isEmpty()) { - if (session.isTx()) { - session.commit(); - } else { - session.rollback(); - } - } - - return true; - } - - public boolean singlePut(String tableName) throws InterruptedException { - HgStoreSession session = storeClient.openSession(graphName); - session.beginTx(); - - int maxlist = 100; - - for (int y = 0; y < maxlist; y++) { - insertDataCount.getAndIncrement(); - String strLine = getLong() + getLong() + getLong() + getLong(); - HgOwnerKey hgKey = HgCliUtil.toOwnerKey(strLine, strLine); - byte[] value = HgCliUtil.toBytes(strLine); - session.put(tableName, hgKey, value); - } - - if (insertDataCount.get() > 10000000) { - synchronized (insertDataCount) { - long count = insertDataCount.get(); - insertDataCount.set(0); - if (count > 10000000) { - log.info("count : " + count + " qps : " + - count * 1000 / (System.currentTimeMillis() - startTime) - + " threadCount : " + taskTotal); - startTime = System.currentTimeMillis(); - } - } - } - - if (session.isTx()) { - session.commit(); - } else { - session.rollback(); - } - - return true; - } - - public boolean testOrder(String input) { - String tableName = "hugegraph02"; - HgStoreSession session = storeClient.openSession(graphName); - session.beginTx(); - int loop = Integer.parseInt(input); - if (loop == 0) { - loop = 2000; - } - for (int i = 0; i < loop; i++) { - long startTime = System.currentTimeMillis(); - HgOwnerKey hgOwnerKey = - HgCliUtil.toOwnerKey(startTime + "owner:" + i, startTime + "k:" + i); - session.put(tableName, hgOwnerKey, HgCliUtil.toBytes(i)); - } - - if (session.isTx()) { - session.commit(); - } else { - session.rollback(); - } - - try { - HgKvIterator iterable = session.scanIterator(tableName); - int x = 0; - while (iterable.hasNext()) { - HgKvEntry entry = iterable.next(); - x++; - } - log.info("x={}", x); - } catch (Exception e) { - log.error("query error, message: {}", e.getMessage()); - } - - return true; - } - - /** - * Multithreaded file reading and storage into database - * - * @throws IOException - * @throws InterruptedException - */ - public void startMultiprocessInsert(String filepath) throws IOException { - log.info("--- start startMultiprocessInsert---"); - startTime = System.currentTimeMillis(); - File readfile = new File(filepath); - MetricX metrics = null; - long dataCount = 0; - if (readfile.exists()) { - // Read file - InputStreamReader isr = new InputStreamReader(new FileInputStream(readfile), - StandardCharsets.UTF_8); - BufferedReader reader = new BufferedReader(isr); - - String strLine = null; - String tableName = HgCliUtil.TABLE_NAME; - // Accumulate to how many threads before executing thread storage, 100,000 - int maxlist = 100000; - List keys = new ArrayList<>(maxlist); - metrics = MetricX.ofStart(); - try { - while ((strLine = reader.readLine()) != null) { - keys.add(strLine); - dataCount++; - - // Read 10000 pieces of data from the file, start a thread for data storage. - if (dataCount % maxlist == 0) { - List finalKeys = keys; - Runnable task = () -> { - try { - if (!finalKeys.isEmpty()) { - boolean ret = singlePut(tableName, finalKeys); - } - } catch (Exception e) { - e.printStackTrace(); - } - taskTotal.decrementAndGet(); - synchronized (taskTotal) { - taskTotal.notifyAll(); - } - }; - taskTotal.getAndIncrement(); - threadPool.execute(task); - - while (taskTotal.get() > 100) { - synchronized (taskTotal) { - taskTotal.wait(); - } - } - // keys.remove(0); - keys = new ArrayList<>(maxlist); - } - } - } catch (InterruptedException e) { - throw new RuntimeException(e); - } - - isr.close(); - reader.close(); - // Move the remaining items into storage - if (!keys.isEmpty()) { - List finalKeys1 = keys; - Runnable task = () -> { - try { - boolean ret = singlePut(tableName, finalKeys1); - } catch (Exception e) { - e.printStackTrace(); - } - taskTotal.decrementAndGet(); - synchronized (taskTotal) { - taskTotal.notifyAll(); - } - }; - threadPool.execute(task); - taskTotal.getAndIncrement(); - } - while (taskTotal.get() > 0) { - synchronized (taskTotal) { - try { - taskTotal.wait(1000); - if (taskTotal.get() > 0) { - System.out.println("wait thread exit " + taskTotal.get()); - } - } catch (InterruptedException e) { - throw new RuntimeException(e); - } - } - } - - threadPool.shutdown(); - - } else { - System.out.println("Sample file does not exist: " + filepath); - } - metrics.end(); - log.info("*************************************************"); - log.info(" Main process execution time: " + metrics.past() / 1000 + " seconds, total executed: " + dataCount + " items"); - log.info("*************************************************"); - System.out.println(" Main process execution time " + metrics.past() / 1000 + " seconds"); - System.out.println("-----Main process execution ends---------"); - } - - /** - * Multithreaded file reading and storage into database - * - * @throws IOException - * @throws InterruptedException - */ - public void autoMultiprocessInsert() throws IOException { - log.info("--- start autoMultiprocessInsert---"); - startTime = System.currentTimeMillis(); - - MetricX metrics = null; - long dataCount = 0; - - String strLine = null; - String tableName = HgCliUtil.TABLE_NAME; - // Accumulate to how many to execute thread storage, 100,000 - int maxlist = 100000; - List keys = new ArrayList<>(maxlist); - for (int x = 0; x < 10000000; x++) { - metrics = MetricX.ofStart(); - try { - Runnable task = () -> { - try { - boolean ret = singlePut(tableName); - } catch (Exception e) { - e.printStackTrace(); - } - taskTotal.decrementAndGet(); - synchronized (taskTotal) { - taskTotal.notifyAll(); - } - }; - taskTotal.getAndIncrement(); - threadPool.execute(task); - - while (taskTotal.get() > 100) { - synchronized (taskTotal) { - taskTotal.wait(); - } - } - } catch (InterruptedException e) { - throw new RuntimeException(e); - } - } - - while (taskTotal.get() > 0) { - synchronized (taskTotal) { - try { - taskTotal.wait(1000); - if (taskTotal.get() > 0) { - System.out.println("wait thread exit " + taskTotal.get()); - } - } catch (InterruptedException e) { - throw new RuntimeException(e); - } - } - } - - threadPool.shutdown(); - - metrics.end(); - log.info("*************************************************"); - log.info(" Main process execution time: " + metrics.past() / 1000 + " seconds, total executed: " + dataCount + " items"); - log.info("*************************************************"); - System.out.println(" Main process execution time " + metrics.past() / 1000 + " seconds"); - System.out.println("-----Main process ends---------"); - } - - public String getLong() { - // If needed longer or more redundant space, just use time * 10^n - //Currently guaranteed to generate 10000 unique items in 1 millisecond. - return String.format("%019x", longId.getAndIncrement()); - } - - /** - * Execute the query, and put the results of the query into the queue as the point for the next iteration. - */ - private void queryAnd2Queue() { - try { - HgStoreSession session = storeClient.openSession(graphName); - HashSet hashSet = new HashSet<>(); - while (!listQueue.isEmpty()) { - - log.info(" ====== start scanBatch2 count:{} list:{}=============", - queryThreadPool.getActiveCount(), listQueue.size()); - List keys = (List) listQueue.take(); - List newQueryList = new ArrayList<>(); - - KvCloseableIterator> iterators = - session.scanBatch2( - HgScanQuery.prefixIteratorOf(HgCliUtil.TABLE_NAME, keys.iterator()) - ); - - while (iterators.hasNext()) { - HgKvIterator iterator = iterators.next(); - int insertQueueCount = 0; - while (iterator.hasNext()) { - HgKvEntry entry = iterator.next(); - String newPoint = HgCliUtil.toStr(entry.value()); -// log.info("query_key =" + newPoint); - // Statistical query times - if (!newPoint.isEmpty() && hashSet.add(newPoint)) { - queryCount.getAndIncrement(); - totalQueryCount.getAndIncrement(); - - HgOwnerKey hgKey = HgCliUtil.toOwnerKey(newPoint, newPoint); - newQueryList.add(hgKey); - - if (queryCount.get() > 1000000) { - synchronized (queryCount) { - long count = queryCount.get(); - queryCount.set(0); - if (count > 1000000) { - log.info("count : " + count + " qps : " + count * 1000 / - (System.currentTimeMillis() - - startTime) - + " threadCount : " + - queryThreadPool.getActiveCount() + " queueSize:" - + listQueue.size()); - startTime = System.currentTimeMillis(); - } - } - } - // After reaching 10,000 points, query once. - if (newQueryList.size() > 10000 && listQueue.size() < 10000) { - listQueue.put(newQueryList); - insertQueueCount++; - newQueryList = new ArrayList<>(); - if (insertQueueCount > 2) { - break; - } - } - } - } - } - // If a query is less than 10,000, submit a separate query to ensure that all results can execute the query. - if (!newQueryList.isEmpty() && listQueue.size() < 1000) { - listQueue.put(newQueryList); - } - - iterators.close(); - } - } catch (InterruptedException e) { - e.printStackTrace(); - } - log.info("============= thread done =============="); - countDownLatch.countDown(); - } - - /** - * Multithreaded query - * - * @param point Starting query point, subsequent queries will use the value obtained from this point as the next query condition for iteration. - * @param scanCount The number of threads allowed to start - * @throws IOException - * @throws InterruptedException - */ - public void startMultiprocessQuery(String point, String scanCount) throws IOException, - InterruptedException { - log.info("--- start startMultiprocessQuery---"); - startTime = System.currentTimeMillis(); - MetricX metrics = MetricX.ofStart(); - limitScanBatchCount = Integer.parseInt(scanCount); - - CountDownLatch latch = new CountDownLatch(limitScanBatchCount); - HgStoreSession session = storeClient.openSession(graphName); - - final AtomicLong[] counter = {new AtomicLong()}; - final long[] start = {System.currentTimeMillis()}; - - LinkedBlockingQueue[] queue = new LinkedBlockingQueue[limitScanBatchCount]; - for (int i = 0; i < limitScanBatchCount; i++) { - queue[i] = new LinkedBlockingQueue(); - } - List strKey = Arrays.asList( - "20727483", "50329304", "26199460", "1177521", "27960125", - "30440025", "15833920", "15015183", "33153097", "21250581"); - strKey.forEach(key -> { - log.info("newkey:{}", key); - HgOwnerKey hgKey = HgCliUtil.toOwnerKey(key, key); - queue[0].add(hgKey); - }); - - for (int i = 0; i < limitScanBatchCount; i++) { - int finalI = i; - KvCloseableIterator> iterators = - session.scanBatch2( - HgScanQuery.prefixIteratorOf(HgCliUtil.TABLE_NAME, - new Iterator() { - HgOwnerKey current = null; - - @Override - public boolean hasNext() { - while (current == null) { - try { - current = - (HgOwnerKey) queue[finalI].poll( - 1, - TimeUnit.SECONDS); - } catch ( - InterruptedException e) { - // - } - } - if (current == null) { - log.warn( - "===== current is " + - "null =========="); - } - return current != null; - } - - @Override - public HgOwnerKey next() { - return current; - } - }) - ); - - new Thread(() -> { - while (iterators.hasNext()) { - HgKvIterator iterator = iterators.next(); - long c = 0; - while (iterator.hasNext()) { - String newPoint = HgCliUtil.toStr(iterator.next().value()); - HgOwnerKey newHgKey = HgCliUtil.toOwnerKey(newPoint, newPoint); - if (queue[(int) (c % limitScanBatchCount)].size() < 1000000) { - queue[(int) (c % limitScanBatchCount)].add(newHgKey); - } - c++; - } - if (counter[0].addAndGet(c) > 1000000) { - synchronized (counter) { - if (counter[0].get() > 10000000) { - log.info("count {}, qps {}", counter[0].get(), - counter[0].get() * 1000 / - (System.currentTimeMillis() - start[0])); - start[0] = System.currentTimeMillis(); - counter[0].set(0); - } - } - } - } - }, "client query thread:" + i).start(); - log.info("===== read thread exit =========="); - } - latch.await(); - - metrics.end(); - log.info("*************************************************"); - log.info(" Main process execution time: " + metrics.past() / 1000 + " seconds; Queries: " + totalQueryCount.get() - + "times, qps:" + totalQueryCount.get() * 1000 / metrics.past()); - log.info("*************************************************"); - System.out.println("-----Main process ends---------"); - } - -} diff --git a/hugegraph-store/hg-store-cli/src/main/java/org/apache/hugegraph/store/cli/scan/HgStoreCommitter.java b/hugegraph-store/hg-store-cli/src/main/java/org/apache/hugegraph/store/cli/scan/HgStoreCommitter.java deleted file mode 100644 index cf31e779f9..0000000000 --- a/hugegraph-store/hg-store-cli/src/main/java/org/apache/hugegraph/store/cli/scan/HgStoreCommitter.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * 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.store.cli.scan; - -import org.apache.hugegraph.store.HgOwnerKey; -import org.apache.hugegraph.store.HgSessionManager; -import org.apache.hugegraph.store.HgStoreSession; -import org.apache.hugegraph.store.cli.util.HgCliUtil; -import org.apache.hugegraph.store.client.HgStoreNodeManager; - -/** - * 2022/2/28 - */ -public class HgStoreCommitter { - - protected final static HgStoreNodeManager nodeManager = HgStoreNodeManager.getInstance(); - - private final String graph; - - private HgStoreCommitter(String graph) { - this.graph = graph; - } - - public static HgStoreCommitter of(String graph) { - return new HgStoreCommitter(graph); - } - - protected HgStoreSession getStoreSession() { - return HgSessionManager.getInstance().openSession(this.graph); - } - - protected HgStoreSession getStoreSession(String graphName) { - return HgSessionManager.getInstance().openSession(graphName); - } - - public void put(String tableName, int amount) { - //*************** Put Benchmark **************//* - String keyPrefix = "PUT-BENCHMARK"; - HgStoreSession session = getStoreSession(); - - int length = String.valueOf(amount).length(); - - session.beginTx(); - - long start = System.currentTimeMillis(); - for (int i = 0; i < amount; i++) { - HgOwnerKey key = HgCliUtil.toOwnerKey( - keyPrefix + "-" + HgCliUtil.padLeftZeros(String.valueOf(i), length)); - byte[] value = HgCliUtil.toBytes(keyPrefix + "-V-" + i); - - session.put(tableName, key, value); - - if ((i + 1) % 100_000 == 0) { - HgCliUtil.println("---------- " + (i + 1) + " --------"); - HgCliUtil.println( - "Preparing took: " + (System.currentTimeMillis() - start) + " ms."); - session.commit(); - HgCliUtil.println( - "Committing took: " + (System.currentTimeMillis() - start) + " ms."); - start = System.currentTimeMillis(); - session.beginTx(); - } - } - - if (session.isTx()) { - session.commit(); - } - - } -} diff --git a/hugegraph-store/hg-store-cli/src/main/java/org/apache/hugegraph/store/cli/scan/HgStoreScanner.java b/hugegraph-store/hg-store-cli/src/main/java/org/apache/hugegraph/store/cli/scan/HgStoreScanner.java deleted file mode 100644 index bbc40ca867..0000000000 --- a/hugegraph-store/hg-store-cli/src/main/java/org/apache/hugegraph/store/cli/scan/HgStoreScanner.java +++ /dev/null @@ -1,237 +0,0 @@ -/* - * 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.store.cli.scan; - -import java.util.Arrays; -import java.util.List; - -import org.apache.hugegraph.pd.client.PDClient; -import org.apache.hugegraph.pd.common.PDException; -import org.apache.hugegraph.pd.grpc.Metapb; -import org.apache.hugegraph.store.HgKvEntry; -import org.apache.hugegraph.store.HgKvIterator; -import org.apache.hugegraph.store.HgKvStore; -import org.apache.hugegraph.store.HgScanQuery; -import org.apache.hugegraph.store.HgSessionManager; -import org.apache.hugegraph.store.HgStoreClient; -import org.apache.hugegraph.store.HgStoreSession; -import org.apache.hugegraph.store.cli.util.HgCliUtil; -import org.apache.hugegraph.store.cli.util.HgMetricX; -import org.apache.hugegraph.store.client.grpc.KvCloseableIterator; -import org.apache.hugegraph.store.client.util.HgStoreClientConfig; -import org.apache.hugegraph.store.client.util.MetricX; - -import lombok.extern.slf4j.Slf4j; - -/** - * 2022/2/14 - */ -@Slf4j -public class HgStoreScanner { - - public static final byte[] EMPTY_BYTES = new byte[0]; - private final HgStoreClient storeClient; - private final String graphName; - private long modNumber = 1_000_000; - private int max = 10_000_000; - - private HgStoreScanner(HgStoreClient storeClient, String graph) { - this.storeClient = storeClient; - this.graphName = graph; - } - - public static HgStoreScanner of(HgStoreClient storeClient, String graph) { - return new HgStoreScanner(storeClient, graph); - } - - public long getModNumber() { - return modNumber; - } - - public void setModNumber(int modNumber) { - if (modNumber <= 0) { - return; - } - this.modNumber = modNumber; - } - - public int getMax() { - return max; - } - - public void setMax(int max) { - if (modNumber <= 0) { - return; - } - this.max = max; - } - - protected HgStoreSession getStoreSession() { - return HgSessionManager.getInstance().openSession(this.graphName); - } - - protected HgStoreSession getStoreSession(String graphName) { - return HgSessionManager.getInstance().openSession(graphName); - } - - public void scanTable(String tableName) { - log.info("Starting scan table [{}] of graph [{}] ...", tableName, graphName); - HgMetricX hgMetricX = HgMetricX.ofStart(); - HgStoreSession session = getStoreSession(); - int count = 0; - KvCloseableIterator> iterator = - session.scanBatch2(HgScanQuery.tableOf(tableName)); - - long start = System.currentTimeMillis(); - while (iterator.hasNext()) { - HgKvIterator iterator2 = iterator.next(); - while (iterator2.hasNext()) { - - count++; - iterator2.next(); - if (count % (modNumber) == 0) { - log.info("Scanning keys: " + count + " time is " + modNumber * 1000 - / - (System.currentTimeMillis() - - start)); - start = System.currentTimeMillis(); - } - if (count == max) { - break; - } - - } - } - iterator.close(); - - hgMetricX.end(); - log.info("*************************************************"); - log.info("************* Scanning Completed **************"); - log.info("Graph: {}", graphName); - log.info("Table: {}", tableName); - log.info("Keys: {}", count); - log.info("Max: {}", max); - log.info("Waiting: {} seconds.", MetricX.getIteratorWait() / 1000); - log.info("Total: {} seconds.", hgMetricX.past() / 1000); - log.info("Iterator: [{}]", iterator.getClass().getSimpleName()); - log.info("Page: {}", HgStoreClientConfig.of().getNetKvScannerPageSize()); - log.info("*************************************************"); - } - - public void scanHash() { - - String tableName = "g+i"; - HgMetricX hgMetricX = HgMetricX.ofStart(); - String graphName = "/DEFAULT/graphs/hugegraph1/"; - HgStoreSession session = getStoreSession(graphName); - int count = 0; - String query = - "{\"conditions\":[{\"cls\":\"S\",\"el\":{\"key\":\"ID\",\"relation\":\"SCAN\"," + - "\"value\"" + - ":{\"start\":\"61180\",\"end\":\"63365\",\"length\":0}}}]," + - "\"optimizedType\":\"NONE\",\"ids\":[]," + - "\"mustSortByInput\":true,\"resultType\":\"EDGE\",\"offset\":0," + - "\"actualOffset\":0,\"actualStoreOffset\":" + - "0,\"limit\":9223372036854775807,\"capacity\":-1,\"showHidden\":false," + - "\"showDeleting\":false," + - "\"showExpired\":false,\"olap\":false,\"withProperties\":false,\"olapPks\":[]}"; - //HgKvIterator iterator = session.scanIterator(tableName,0,715827883, - // HgKvStore.SCAN_ANY,null); - - //HgKvIterator iterator = session.scanIterator(tableName,61180,63365, 348, null); - //HgKvIterator iterator = session.scanIterator(tableName,0,65535, 348, null); - HgKvIterator iterator = session.scanIterator(tableName); - while (iterator.hasNext()) { - - count++; - //iterator.next(); - // if (count % (modNumber) == 0) { - // log.info("Scanning keys: " + count); - HgCliUtil.println(Arrays.toString(iterator.next().key())); - // } - if (count == max) { - break; - } - - } - - hgMetricX.end(); - log.info("*************************************************"); - log.info("************* Scanning Completed **************"); - log.info("Graph: {}", this.graphName); - log.info("Table: {}", tableName); - log.info("Keys: {}", count); - log.info("Max: {}", max); - log.info("Waiting: {} seconds.", MetricX.getIteratorWait() / 1000); - log.info("Total: {} seconds.", hgMetricX.past() / 1000); - log.info("Iterator: [{}]", iterator.getClass().getSimpleName()); - log.info("Page: {}", HgStoreClientConfig.of().getNetKvScannerPageSize()); - log.info("*************************************************"); - } - - public void scanTable2(String tableName) throws PDException { - // java -jar hg-store-cli-3.6.0-SNAPSHOT.jar -scan 10.45.30.212:8989 "DEFAULT/case_112/g" - // g+ie - PDClient pdClient = storeClient.getPdClient(); - List partitions = pdClient.getPartitions(0, graphName); - HgStoreSession session = storeClient.openSession(graphName); - int count = 0; - byte[] position = null; - HgMetricX hgMetricX = HgMetricX.ofStart(); - for (Metapb.Partition partition : partitions) { - while (true) { - try (HgKvIterator iterator = session.scanIterator(tableName, - (int) (partition.getStartKey()), - (int) (partition.getEndKey()), - HgKvStore.SCAN_HASHCODE, - EMPTY_BYTES)) { - if (position != null) { - iterator.seek(position); - } - while (iterator.hasNext()) { - iterator.next(); - count++; - if (count % 3000 == 0) { - if (iterator.hasNext()) { - iterator.next(); - position = iterator.position(); - System.out.println("count is " + count); - } else { - position = null; - } - break; - } - } - if (!iterator.hasNext()) { - position = null; - break; - } - } - } - } - hgMetricX.end(); - log.info("*************************************************"); - log.info("************* Scanning Completed **************"); - log.info("Graph: {}", graphName); - log.info("Table: {}", tableName); - log.info("Keys: {}", count); - log.info("Total: {} seconds.", hgMetricX.past() / 1000); - log.info("*************************************************"); - } - -} From 05d17698a85936b3f8daf1c1fdd3f774a53254df Mon Sep 17 00:00:00 2001 From: JisoLya <523420504@qq.com> Date: Thu, 28 Aug 2025 16:39:32 +0800 Subject: [PATCH 28/35] this commit is used to resolve local conflict --- hugegraph-pd/hg-pd-cli/pom.xml | 73 +++++++++++++ .../hugegraph/pd/cli/CliApplication.java | 58 ++++++++++ .../hugegraph/pd/cli/cmd/ChangeRaft.java | 36 ++++++ .../hugegraph/pd/cli/cmd/CheckPeers.java | 103 ++++++++++++++++++ .../apache/hugegraph/pd/cli/cmd/Command.java | 59 ++++++++++ .../apache/hugegraph/pd/cli/cmd/Config.java | 66 +++++++++++ .../hugegraph/pd/cli/cmd/Parameter.java | 32 ++++++ .../hugegraph/pd/client/ClientCache.java | 4 + .../apache/hugegraph/pd/client/PDClient.java | 15 +++ .../apache/hugegraph/pd/client/PDConfig.java | 82 +++++++++----- hugegraph-pd/pom.xml | 1 + 11 files changed, 502 insertions(+), 27 deletions(-) create mode 100644 hugegraph-pd/hg-pd-cli/pom.xml create mode 100644 hugegraph-pd/hg-pd-cli/src/main/java/org/apache/hugegraph/pd/cli/CliApplication.java create mode 100644 hugegraph-pd/hg-pd-cli/src/main/java/org/apache/hugegraph/pd/cli/cmd/ChangeRaft.java create mode 100644 hugegraph-pd/hg-pd-cli/src/main/java/org/apache/hugegraph/pd/cli/cmd/CheckPeers.java create mode 100644 hugegraph-pd/hg-pd-cli/src/main/java/org/apache/hugegraph/pd/cli/cmd/Command.java create mode 100644 hugegraph-pd/hg-pd-cli/src/main/java/org/apache/hugegraph/pd/cli/cmd/Config.java create mode 100644 hugegraph-pd/hg-pd-cli/src/main/java/org/apache/hugegraph/pd/cli/cmd/Parameter.java diff --git a/hugegraph-pd/hg-pd-cli/pom.xml b/hugegraph-pd/hg-pd-cli/pom.xml new file mode 100644 index 0000000000..d570072a0f --- /dev/null +++ b/hugegraph-pd/hg-pd-cli/pom.xml @@ -0,0 +1,73 @@ + + + + + 4.0.0 + + org.apache.hugegraph + hugegraph + 1.5.0 + ../../pom.xml + + + hg-pd-cli + + + 11 + 11 + UTF-8 + + + + org.projectlombok + lombok + + + org.apache.hugegraph + hg-pd-common + 1.5.0 + compile + + + org.apache.commons + commons-lang3 + 3.13.0 + compile + + + com.alipay.sofa + jraft-core + 1.3.13 + compile + + + org.apache.hugegraph + hg-store-client + 1.5.0 + compile + + + org.apache.hugegraph + hg-pd-client + ${revision} + + + + diff --git a/hugegraph-pd/hg-pd-cli/src/main/java/org/apache/hugegraph/pd/cli/CliApplication.java b/hugegraph-pd/hg-pd-cli/src/main/java/org/apache/hugegraph/pd/cli/CliApplication.java new file mode 100644 index 0000000000..be50764dc6 --- /dev/null +++ b/hugegraph-pd/hg-pd-cli/src/main/java/org/apache/hugegraph/pd/cli/CliApplication.java @@ -0,0 +1,58 @@ +/* + * 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.pd.cli; + +import org.apache.hugegraph.pd.cli.cmd.ChangeRaft; +import org.apache.hugegraph.pd.cli.cmd.CheckPeers; +import org.apache.hugegraph.pd.cli.cmd.Command; +import org.apache.hugegraph.pd.cli.cmd.Config; +import org.apache.hugegraph.pd.cli.cmd.Parameter; + +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class CliApplication { + + public static void main(String[] args) { + try { + Parameter parameter = Command.toParameter(args); + Command command; + switch (parameter.getCmd()) { + case "config": + command = new Config(parameter.getPd()); + break; + case "change_raft": + command = new ChangeRaft(parameter.getPd()); + break; + case "check_peers": + command = new CheckPeers(parameter.getPd()); + break; + default: + log.error("无效的指令"); + return; + } + command.action(parameter.getParams()); + } catch (Exception e) { + log.error("main thread error:", e); + System.exit(0); + } finally { + + } + + } +} diff --git a/hugegraph-pd/hg-pd-cli/src/main/java/org/apache/hugegraph/pd/cli/cmd/ChangeRaft.java b/hugegraph-pd/hg-pd-cli/src/main/java/org/apache/hugegraph/pd/cli/cmd/ChangeRaft.java new file mode 100644 index 0000000000..991ebdae79 --- /dev/null +++ b/hugegraph-pd/hg-pd-cli/src/main/java/org/apache/hugegraph/pd/cli/cmd/ChangeRaft.java @@ -0,0 +1,36 @@ +/* + * 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.pd.cli.cmd; + +import org.apache.hugegraph.pd.common.PDException; + +/** + * @author zhangyingjie + * @date 2023/10/17 + **/ +public class ChangeRaft extends Command { + + public ChangeRaft(String pd) { + super(pd); + } + + @Override + public void action(String[] params) throws PDException { + pdClient.updatePdRaft(params[0]); + } +} diff --git a/hugegraph-pd/hg-pd-cli/src/main/java/org/apache/hugegraph/pd/cli/cmd/CheckPeers.java b/hugegraph-pd/hg-pd-cli/src/main/java/org/apache/hugegraph/pd/cli/cmd/CheckPeers.java new file mode 100644 index 0000000000..73f293225f --- /dev/null +++ b/hugegraph-pd/hg-pd-cli/src/main/java/org/apache/hugegraph/pd/cli/cmd/CheckPeers.java @@ -0,0 +1,103 @@ +/* + * 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.pd.cli.cmd; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import org.apache.commons.lang3.StringUtils; +import org.apache.hugegraph.pd.client.MetaClient; +import org.apache.hugegraph.pd.common.PDException; +import org.apache.hugegraph.pd.grpc.Metapb; +import org.apache.hugegraph.pd.grpc.Metapb.Store; +import org.apache.hugegraph.pd.grpc.ShardGroups; +import org.apache.hugegraph.store.client.grpc.GrpcStoreStateClient; + +import com.alipay.sofa.jraft.entity.PeerId; + +import lombok.extern.slf4j.Slf4j; + +/** + * @date 2023/10/17 + **/ +@Slf4j +public class CheckPeers extends Command { + + private MetaClient metaClient; + + public CheckPeers(String pd) { + super(pd); + metaClient = new MetaClient(config); + } + + @Override + public void action(String[] params) throws PDException { + GrpcStoreStateClient stateClient = new GrpcStoreStateClient(config); + try { + ConcurrentHashMap> result = new ConcurrentHashMap<>(); + List stores = pdClient.getActiveStores(); + ShardGroups shardGroups = metaClient.getShardGroups(); + stores.parallelStream().forEach(s -> { + for (Metapb.ShardGroup sg : shardGroups.getDataList()) { + String groupId = "hg_" + sg.getId(); + PeerId leader = new PeerId(); + result.computeIfAbsent(groupId, (key) -> new ConcurrentHashMap<>()); + try { + String peers = stateClient.getPeers(s.getAddress(), sg.getId()); + if (StringUtils.isEmpty(peers)){ + continue; + } + Map nodePeers = result.get(groupId); + nodePeers.put(s.getRaftAddress(), peers.split(",")); + } catch (Exception e) { + if (e.getMessage() != null && + (e.getMessage().contains("Fail to get leader of group") || + e.getMessage().contains("Fail to find node"))) { + continue; + } + log.error(String.format("got %s: %s with error:", groupId, leader), e); + } + } + }); + result.entrySet().parallelStream().forEach(entry -> { + String[] record = null; + String groupId = entry.getKey(); + Map nodePeers = entry.getValue(); + for (Map.Entry e : nodePeers.entrySet()) { + if (record == null) { + record = e.getValue(); + continue; + } + if (!Arrays.equals(record, e.getValue())) { + log.error("group: {} ,got error peers: {}", groupId, nodePeers); + break; + } + + } + }); + log.info("got all node info:{}", result); + } catch (Exception e) { + log.error("check peers with error:", e); + throw e; + } finally { + stateClient.close(); + } + } +} diff --git a/hugegraph-pd/hg-pd-cli/src/main/java/org/apache/hugegraph/pd/cli/cmd/Command.java b/hugegraph-pd/hg-pd-cli/src/main/java/org/apache/hugegraph/pd/cli/cmd/Command.java new file mode 100644 index 0000000000..f218f6416b --- /dev/null +++ b/hugegraph-pd/hg-pd-cli/src/main/java/org/apache/hugegraph/pd/cli/cmd/Command.java @@ -0,0 +1,59 @@ +/* + * 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.pd.cli.cmd; + +import org.apache.hugegraph.pd.client.PDClient; +import org.apache.hugegraph.pd.client.PDConfig; +import org.apache.hugegraph.pd.common.PDException; + +/** + * @author zhangyingjie + * @date 2023/10/17 + **/ +public abstract class Command { + + protected static String error = "启动参数: 命令, pd地址, 命令参数, 参数分隔符(非必须)"; + protected PDClient pdClient; + protected PDConfig config; + + public Command(String pd) { + config = PDConfig.of(pd).setAuthority("store", ""); + pdClient = PDClient.create(config); + } + + public static Parameter toParameter(String[] args) throws PDException { + if (args.length < 3) { + throw new PDException(-1, error); + } + Parameter parameter = new Parameter(); + parameter.setCmd(args[0]); + parameter.setPd(args[1]); + if (args.length == 3) { + parameter.setParams(new String[]{args[2]}); + } else { + String t = args[3]; + if (t != null && t.length() > 0) { + parameter.setParams(args[2].split(t)); + parameter.setSeparator(t); + } + } + return parameter; + } + + public abstract void action(String[] params) throws Exception; +} diff --git a/hugegraph-pd/hg-pd-cli/src/main/java/org/apache/hugegraph/pd/cli/cmd/Config.java b/hugegraph-pd/hg-pd-cli/src/main/java/org/apache/hugegraph/pd/cli/cmd/Config.java new file mode 100644 index 0000000000..8e3f65df03 --- /dev/null +++ b/hugegraph-pd/hg-pd-cli/src/main/java/org/apache/hugegraph/pd/cli/cmd/Config.java @@ -0,0 +1,66 @@ +/* + * 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.pd.cli.cmd; + +import org.apache.hugegraph.pd.common.PDException; +import org.apache.hugegraph.pd.grpc.Metapb; + +/** + * @author zhangyingjie + * @date 2023/10/17 + **/ +public class Config extends Command { + + public Config(String pd) { + super(pd); + } + + @Override + public void action(String[] params) throws PDException { + String param = params[0]; + String[] pair = param.split("="); + String key = pair[0].trim(); + Object value = null; + if (pair.length > 1) { + value = pair[1].trim(); + } + if (value == null) { + Metapb.PDConfig pdConfig = pdClient.getPDConfig(); + switch (key) { + case "enableBatchLoad": + // value = pdConfig.getEnableBatchLoad(); + break; + case "shardCount": + value = pdConfig.getShardCount(); + break; + } + + System.out.println("Get config " + key + "=" + value); + } else { + Metapb.PDConfig.Builder builder = Metapb.PDConfig.newBuilder(); + switch (key) { + case "enableBatchLoad": + // builder.setEnableBatchLoad(Boolean.valueOf((String)value)); + case "shardCount": + builder.setShardCount(Integer.valueOf((String) value)); + } + pdClient.setPDConfig(builder.build()); + System.out.println("Set config " + key + "=" + value); + } + } +} diff --git a/hugegraph-pd/hg-pd-cli/src/main/java/org/apache/hugegraph/pd/cli/cmd/Parameter.java b/hugegraph-pd/hg-pd-cli/src/main/java/org/apache/hugegraph/pd/cli/cmd/Parameter.java new file mode 100644 index 0000000000..170dd83c88 --- /dev/null +++ b/hugegraph-pd/hg-pd-cli/src/main/java/org/apache/hugegraph/pd/cli/cmd/Parameter.java @@ -0,0 +1,32 @@ +/* + * 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.pd.cli.cmd; + +import lombok.Data; + +/** + * @author zhangyingjie + * @date 2023/10/20 + **/ +@Data +public class Parameter { + String cmd; + String pd; + String[] params; + String separator; +} diff --git a/hugegraph-pd/hg-pd-client/src/main/java/org/apache/hugegraph/pd/client/ClientCache.java b/hugegraph-pd/hg-pd-client/src/main/java/org/apache/hugegraph/pd/client/ClientCache.java index 9e584583a9..c04e194242 100644 --- a/hugegraph-pd/hg-pd-client/src/main/java/org/apache/hugegraph/pd/client/ClientCache.java +++ b/hugegraph-pd/hg-pd-client/src/main/java/org/apache/hugegraph/pd/client/ClientCache.java @@ -328,4 +328,8 @@ public void updateLeader(int partitionId, Shard leader) { } } } + + public List getLeaderStoreAddresses() { + return null; + } } diff --git a/hugegraph-pd/hg-pd-client/src/main/java/org/apache/hugegraph/pd/client/PDClient.java b/hugegraph-pd/hg-pd-client/src/main/java/org/apache/hugegraph/pd/client/PDClient.java index 200a35ee87..787dc6ae49 100644 --- a/hugegraph-pd/hg-pd-client/src/main/java/org/apache/hugegraph/pd/client/PDClient.java +++ b/hugegraph-pd/hg-pd-client/src/main/java/org/apache/hugegraph/pd/client/PDClient.java @@ -1284,6 +1284,21 @@ public void updatePdRaft(String raftConfig) throws PDException { handleResponseError(response.getHeader()); } + public void forceReconnect() { + //todo soya + } + + public PDPulse getPulse() { + //todo soya + return null; + } + + public ShardGroup getShardGroupDirect(int partId) throws PDException { + //todo soya + return null; + //return this.pdApi.getShardGroupDirect(partId); + } + public interface PDEventListener { void onStoreChanged(NodeEvent event); diff --git a/hugegraph-pd/hg-pd-client/src/main/java/org/apache/hugegraph/pd/client/PDConfig.java b/hugegraph-pd/hg-pd-client/src/main/java/org/apache/hugegraph/pd/client/PDConfig.java index 822eda3d5a..82d9827bdc 100644 --- a/hugegraph-pd/hg-pd-client/src/main/java/org/apache/hugegraph/pd/client/PDConfig.java +++ b/hugegraph-pd/hg-pd-client/src/main/java/org/apache/hugegraph/pd/client/PDConfig.java @@ -1,34 +1,30 @@ -/* - * 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.pd.client; -public final class PDConfig { +import static java.nio.charset.StandardCharsets.UTF_8; - // TODO: multi-server - private String serverHost = "localhost:9000"; +import java.util.Base64; +import org.apache.commons.lang3.StringUtils; - // The timeout period for grpc call is 10 seconds - private long grpcTimeOut = 60000; +import com.baidu.hugegraph.pd.client.interceptor.AuthenticationException; - // Whether to receive asynchronous PD notifications - private boolean enablePDNotify = false; +import lombok.Getter; +import lombok.Setter; +public final class PDConfig { + //TODO multi-server + private String serverHost = "localhost:9000"; + private long grpcTimeOut = 60000; // grpc调用超时时间 10秒 + private boolean enablePDNotify = false; // 是否接收PD异步通知 private boolean enableCache = false; + private String authority; + private String userName = ""; + private static final int GRPC_DEFAULT_MAX_INBOUND_MESSAGE_SIZE = 1024 * 1024 * 1024; + private static final int GRPC_DEFAULT_MAX_OUTBOUND_MESSAGE_SIZE = 1024 * 1024 * 1024; + private static int inboundMessageSize = GRPC_DEFAULT_MAX_INBOUND_MESSAGE_SIZE; + private static int outboundMessageSize = GRPC_DEFAULT_MAX_OUTBOUND_MESSAGE_SIZE; + @Getter + @Setter + private boolean autoGetPdServers = false; private PDConfig() { } @@ -61,6 +57,7 @@ public long getGrpcTimeOut() { @Deprecated public PDConfig setEnablePDNotify(boolean enablePDNotify) { this.enablePDNotify = enablePDNotify; + // TODO 临时代码,hugegraph修改完后删除 this.enableCache = enablePDNotify; return this; } @@ -76,8 +73,39 @@ public PDConfig setEnableCache(boolean enableCache) { @Override public String toString() { - return "PDConfig{" + - "serverHost='" + serverHost + '\'' + - '}'; + return "PDConfig{ serverHost='" + serverHost + '\'' + '}'; + } + + public PDConfig setAuthority(String userName, String pwd) { + this.userName = userName; + String auth = userName + ':' + pwd; + this.authority = new String(Base64.getEncoder().encode(auth.getBytes(UTF_8))); + return this; + } + + public String getUserName() { + return userName; + } + + public String getAuthority() { + if (StringUtils.isEmpty(this.authority)){ + throw new AuthenticationException("invalid basic authentication info"); + } + return authority; + } + public static int getInboundMessageSize() { + return inboundMessageSize; + } + + public static void setInboundMessageSize(int inboundMessageSize) { + PDConfig.inboundMessageSize = inboundMessageSize; + } + + public static int getOutboundMessageSize() { + return outboundMessageSize; + } + + public static void setOutboundMessageSize(int outboundMessageSize) { + PDConfig.outboundMessageSize = outboundMessageSize; } } diff --git a/hugegraph-pd/pom.xml b/hugegraph-pd/pom.xml index b2547a7dc4..d1135c991b 100644 --- a/hugegraph-pd/pom.xml +++ b/hugegraph-pd/pom.xml @@ -39,6 +39,7 @@ hg-pd-core hg-pd-service hg-pd-dist + hg-pd-cli From 0be7aad56011c1484617af42ea681faa16377bf5 Mon Sep 17 00:00:00 2001 From: JisoLya <523420504@qq.com> Date: Mon, 8 Sep 2025 17:08:53 +0800 Subject: [PATCH 29/35] update: add tag --- .../apache/hugegraph/rocksdb/access/SessionOperatorImpl.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/hugegraph-store/hg-store-rocksdb/src/main/java/org/apache/hugegraph/rocksdb/access/SessionOperatorImpl.java b/hugegraph-store/hg-store-rocksdb/src/main/java/org/apache/hugegraph/rocksdb/access/SessionOperatorImpl.java index d8e668391e..50293008c9 100644 --- a/hugegraph-store/hg-store-rocksdb/src/main/java/org/apache/hugegraph/rocksdb/access/SessionOperatorImpl.java +++ b/hugegraph-store/hg-store-rocksdb/src/main/java/org/apache/hugegraph/rocksdb/access/SessionOperatorImpl.java @@ -370,7 +370,10 @@ public T next() { iterator.seekToFirst(); } } - if (iterator == null) return null; + //FIXME Is this right? + if (iterator == null){ + return null; + } String key = getIteratorKey(); var newIterator = getScanRawIterator(iterator, readOptions, startSeqNum, key); session.addIterator(key, newIterator); From 0b4ea562936f926d2263671c90b626593505d93d Mon Sep 17 00:00:00 2001 From: JisoLya <523420504@qq.com> Date: Wed, 10 Sep 2025 15:29:37 +0800 Subject: [PATCH 30/35] refactor: add missing method in store-client --- .../client/grpc/GrpcStoreNodeSessionImpl.java | 47 +++++++++---------- 1 file changed, 22 insertions(+), 25 deletions(-) diff --git a/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/grpc/GrpcStoreNodeSessionImpl.java b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/grpc/GrpcStoreNodeSessionImpl.java index 73e95515c7..9c160640f7 100644 --- a/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/grpc/GrpcStoreNodeSessionImpl.java +++ b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/grpc/GrpcStoreNodeSessionImpl.java @@ -20,12 +20,13 @@ import java.util.Collections; import java.util.LinkedList; import java.util.List; -import java.util.concurrent.ConcurrentHashMap; import java.util.function.Supplier; import java.util.stream.Collectors; import javax.annotation.concurrent.NotThreadSafe; +import org.apache.hugegraph.HugeGraphSupplier; +import org.apache.hugegraph.pd.common.PDException; import org.apache.hugegraph.store.HgKvEntry; import org.apache.hugegraph.store.HgKvIterator; import org.apache.hugegraph.store.HgKvStore; @@ -39,6 +40,7 @@ import org.apache.hugegraph.store.client.util.HgStoreClientConst; import org.apache.hugegraph.store.client.util.HgStoreClientUtil; import org.apache.hugegraph.store.client.util.HgUuid; +import org.apache.hugegraph.store.constant.HugeServerTables; import org.apache.hugegraph.store.grpc.common.GraphMethod; import org.apache.hugegraph.store.grpc.common.Key; import org.apache.hugegraph.store.grpc.common.OpType; @@ -46,6 +48,8 @@ import org.apache.hugegraph.store.grpc.session.BatchEntry; import org.apache.hugegraph.store.grpc.stream.HgStoreStreamGrpc.HgStoreStreamStub; import org.apache.hugegraph.store.grpc.stream.ScanStreamReq; +import org.apache.hugegraph.store.query.StoreQueryParam; +import org.apache.hugegraph.structure.BaseElement; import com.google.protobuf.ByteString; import com.google.protobuf.UnsafeByteOperations; @@ -61,26 +65,16 @@ @NotThreadSafe class GrpcStoreNodeSessionImpl implements HgStoreNodeSession { - private static final HgStoreClientConfig hgStoreClientConfig = HgStoreClientConfig.of(); - private static final ConcurrentHashMap tables = new ConcurrentHashMap<>() {{ - put("unknown", 0); - put("g+v", 1); - put("g+oe", 2); - put("g+ie", 3); - put("g+index", 4); - put("g+task", 5); - put("g+olap", 6); - put("g+server", 7); - }}; - private final HgStoreNode storeNode; - private final String graphName; - private final GrpcStoreSessionClient storeSessionClient; - private final GrpcStoreStreamClient storeStreamClient; - private final HgStoreNodeManager nodeManager; - private final NotifyingExecutor notifier; - private final SwitchingExecutor switcher; - private final BatchEntry.Builder batchEntryBuilder = BatchEntry.newBuilder(); - private final Key.Builder builder = Key.newBuilder(); + private static HgStoreClientConfig hgStoreClientConfig = HgStoreClientConfig.of(); + private HgStoreNode storeNode; + private String graphName; + private GrpcStoreSessionClient storeSessionClient; + private GrpcStoreStreamClient storeStreamClient; + private HgStoreNodeManager nodeManager; + private NotifyingExecutor notifier; + private SwitchingExecutor switcher; + private BatchEntry.Builder batchEntryBuilder = BatchEntry.newBuilder(); + private Key.Builder builder = Key.newBuilder(); private boolean isAutoCommit = true; private String batchId; private LinkedList batchEntries = new LinkedList<>(); @@ -220,10 +214,7 @@ public boolean merge(String table, HgOwnerKey key, byte[] value) { private boolean prepareBatchEntry(OpType opType, String table , HgOwnerKey startKey, HgOwnerKey endKey, byte[] value) { this.batchEntryBuilder.clear().setOpType(opType); - Integer tableCode = tables.get(table); - if (tableCode != null) { - this.batchEntryBuilder.setTable(tableCode); - } + this.batchEntryBuilder.setTable(HugeServerTables.TABLES_MAP.get(table)); if (startKey != null) { this.batchEntryBuilder.setStartKey(toKey(startKey)); } @@ -545,4 +536,10 @@ private Supplier getSwitcherSupplier(long limit) { public String toString() { return "storeNodeSession: {" + storeNode + ", graphName: \"" + graphName + "\"}"; } + + @Override + public List> query(StoreQueryParam query, + HugeGraphSupplier supplier) throws PDException { + return null; + } } From 625c1545c4e9de33085a0a4d5f98fbc6d1be0cd3 Mon Sep 17 00:00:00 2001 From: JisoLya <523420504@qq.com> Date: Wed, 10 Sep 2025 16:12:22 +0800 Subject: [PATCH 31/35] fix: fix conflict in store-client --- .../org/apache/hugegraph/store/HgKvStore.java | 3 - .../store/client/NodeTxSessionProxy.java | 17 +++++- .../store/client/SequencedIterator.java | 2 +- .../store/client/grpc/AbstractGrpcClient.java | 4 +- .../client/grpc/GrpcStoreNodeSessionImpl.java | 5 -- .../client/grpc/GrpcStoreSessionClient.java | 61 +++++-------------- .../client/grpc/GrpcStoreStateClient.java | 43 +++++++++++-- .../store/client/grpc/KvBatchScanner5.java | 4 +- .../client/grpc/KvBatchScannerMerger.java | 2 +- .../store/client/util/HgStoreClientUtil.java | 9 +-- .../hugegraph/store/client/util/HgUuid.java | 4 +- 11 files changed, 82 insertions(+), 72 deletions(-) diff --git a/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/HgKvStore.java b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/HgKvStore.java index 21016a287c..dcce95ba1e 100644 --- a/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/HgKvStore.java +++ b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/HgKvStore.java @@ -102,9 +102,6 @@ HgKvIterator scanIterator(String table, int codeFrom, int codeTo, int HgKvIterator scanIterator(ScanStreamReq.Builder scanReqBuilder); - //todo soya to be removed in the future - long count(String table); - List> query(StoreQueryParam query, HugeGraphSupplier supplier) throws PDException; diff --git a/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/NodeTxSessionProxy.java b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/NodeTxSessionProxy.java index 324b7a0263..9d9f516c50 100644 --- a/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/NodeTxSessionProxy.java +++ b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/NodeTxSessionProxy.java @@ -45,6 +45,7 @@ import org.apache.hugegraph.store.HgKvOrderedIterator; import org.apache.hugegraph.store.HgOwnerKey; import org.apache.hugegraph.store.HgScanQuery; +import org.apache.hugegraph.store.HgSessionConfig; import org.apache.hugegraph.store.HgStoreSession; import org.apache.hugegraph.store.client.grpc.KvBatchScanner; import org.apache.hugegraph.store.client.grpc.KvCloseableIterator; @@ -68,12 +69,13 @@ @NotThreadSafe class NodeTxSessionProxy implements HgStoreSession { + private final HgSessionConfig sessionConfig; private final HgStoreNodeManager nodeManager; private final HgStoreNodePartitioner nodePartitioner; private final String graphName; private final NodeTxExecutor txExecutor; - NodeTxSessionProxy(String graphName, HgStoreNodeManager nodeManager) { + public NodeTxSessionProxy(String graphName, HgStoreNodeManager nodeManager) { this.nodeManager = nodeManager; this.graphName = graphName; this.nodePartitioner = this.nodeManager.getNodePartitioner(); @@ -81,6 +83,19 @@ class NodeTxSessionProxy implements HgStoreSession { isFalse(this.nodePartitioner == null, "Failed to retrieve the node-partitioner from node-manager."); + sessionConfig = new HgSessionConfig(); + } + + public NodeTxSessionProxy(String graphName, HgStoreNodeManager nodeManager, + HgSessionConfig config) { + this.nodeManager = nodeManager; + this.graphName = graphName; + this.nodePartitioner = this.nodeManager.getNodePartitioner(); + this.txExecutor = NodeTxExecutor.graphOf(this.graphName, this); + + isFalse(this.nodePartitioner == null, + "Failed to retrieve the node-partitioner from node-manager."); + sessionConfig = config; } @Override diff --git a/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/SequencedIterator.java b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/SequencedIterator.java index aca7bb70b3..aae66b61d7 100644 --- a/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/SequencedIterator.java +++ b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/SequencedIterator.java @@ -51,7 +51,7 @@ public class SequencedIterator implements HgKvIterator { SequencedIterator(List iterators, long limit) { Collections.sort(iterators); this.queue = new LinkedList(iterators); - this.limit = limit <= 0 ? Integer.MAX_VALUE : limit; + this.limit = limit <= 0 ? Long.MAX_VALUE : limit; } private HgKvOrderedIterator getIterator() { diff --git a/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/grpc/AbstractGrpcClient.java b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/grpc/AbstractGrpcClient.java index 20aa54b39a..03d4858109 100644 --- a/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/grpc/AbstractGrpcClient.java +++ b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/grpc/AbstractGrpcClient.java @@ -69,7 +69,7 @@ public ManagedChannel[] getChannels(String target) { int fi = i; executor.execute(() -> { try { - value[fi] = getManagedChannel(target); + value[fi] = createChannel(target); } catch (Exception e) { throw new RuntimeException(e); } finally { @@ -169,7 +169,7 @@ private AbstractStub setStubOption(AbstractStub value) { config.getGrpcMaxOutboundMessageSize()); } - private ManagedChannel getManagedChannel(String target) { + protected ManagedChannel createChannel(String target) { return ManagedChannelBuilder.forTarget(target).usePlaintext().build(); } diff --git a/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/grpc/GrpcStoreNodeSessionImpl.java b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/grpc/GrpcStoreNodeSessionImpl.java index 9c160640f7..18f7464b88 100644 --- a/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/grpc/GrpcStoreNodeSessionImpl.java +++ b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/grpc/GrpcStoreNodeSessionImpl.java @@ -362,11 +362,6 @@ public HgKvIterator scanIterator(ScanStreamReq.Builder builder) { return GrpcKvIteratorImpl.of(this, scanner); } - @Override - public long count(String table) { - return this.storeSessionClient.count(this, table); - } - @Override public HgKvIterator scanIterator(String table, byte[] query) { return GrpcKvIteratorImpl.of(this, this.storeStreamClient.doScan(this, table, 0, query)); diff --git a/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/grpc/GrpcStoreSessionClient.java b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/grpc/GrpcStoreSessionClient.java index 794a7c1286..90288e4dfd 100644 --- a/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/grpc/GrpcStoreSessionClient.java +++ b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/grpc/GrpcStoreSessionClient.java @@ -17,19 +17,14 @@ package org.apache.hugegraph.store.client.grpc; -import static org.apache.hugegraph.store.client.grpc.KvBatchUtil.getHeader; - import java.util.List; -import java.util.concurrent.TimeUnit; import javax.annotation.concurrent.ThreadSafe; import org.apache.hugegraph.store.HgOwnerKey; import org.apache.hugegraph.store.client.HgStoreNodeSession; import org.apache.hugegraph.store.grpc.common.GraphMethod; -import org.apache.hugegraph.store.grpc.common.ScanMethod; import org.apache.hugegraph.store.grpc.common.TableMethod; -import org.apache.hugegraph.store.grpc.session.Agg; import org.apache.hugegraph.store.grpc.session.BatchEntry; import org.apache.hugegraph.store.grpc.session.BatchGetReq; import org.apache.hugegraph.store.grpc.session.BatchReq; @@ -41,12 +36,13 @@ import org.apache.hugegraph.store.grpc.session.HgStoreSessionGrpc; import org.apache.hugegraph.store.grpc.session.HgStoreSessionGrpc.HgStoreSessionBlockingStub; import org.apache.hugegraph.store.grpc.session.TableReq; -import org.apache.hugegraph.store.grpc.stream.ScanStreamReq; -import io.grpc.Deadline; import io.grpc.ManagedChannel; import lombok.extern.slf4j.Slf4j; +import static org.apache.hugegraph.store.client.grpc.GrpcUtil.getHeader; +import static org.apache.hugegraph.store.client.grpc.GrpcUtil.toTk; + /** * created on 2021/11/18 * @@ -58,41 +54,25 @@ class GrpcStoreSessionClient extends AbstractGrpcClient { @Override public HgStoreSessionBlockingStub getBlockingStub(ManagedChannel channel) { - HgStoreSessionBlockingStub stub; - stub = HgStoreSessionGrpc.newBlockingStub(channel); - return stub; + return HgStoreSessionGrpc.newBlockingStub(channel); } private HgStoreSessionBlockingStub getBlockingStub(HgStoreNodeSession nodeSession) { - HgStoreSessionBlockingStub stub = - (HgStoreSessionBlockingStub) getBlockingStub( - nodeSession.getStoreNode().getAddress()); - return stub; + return (HgStoreSessionBlockingStub) getBlockingStub( + nodeSession.getStoreNode().getAddress()); } FeedbackRes doGet(HgStoreNodeSession nodeSession, String table, HgOwnerKey ownerKey) { - if (log.isDebugEnabled()) { - log.debug("doGet: {}-{}-{}-{}", nodeSession, table, ownerKey, GetReq.newBuilder() - .setHeader( - GrpcUtil.getHeader( - nodeSession)) - .setTk(GrpcUtil.toTk( - table, - ownerKey)) - .build()); - } return this.getBlockingStub(nodeSession) - .get2(GetReq.newBuilder() - .setHeader(GrpcUtil.getHeader(nodeSession)) - .setTk(GrpcUtil.toTk(table, ownerKey)) - .build() - ); + .get(GetReq.newBuilder().setHeader(getHeader(nodeSession)) + .setTk(toTk(table, ownerKey)) + .build()); } FeedbackRes doClean(HgStoreNodeSession nodeSession, int partId) { return this.getBlockingStub(nodeSession) .clean(CleanReq.newBuilder() - .setHeader(GrpcUtil.getHeader(nodeSession)) + .setHeader(getHeader(nodeSession)) .setPartition(partId) .build() ); @@ -100,7 +80,7 @@ FeedbackRes doClean(HgStoreNodeSession nodeSession, int partId) { FeedbackRes doBatchGet(HgStoreNodeSession nodeSession, String table, List keyList) { BatchGetReq.Builder builder = BatchGetReq.newBuilder(); - builder.setHeader(GrpcUtil.getHeader(nodeSession)).setTable(table); + builder.setHeader(getHeader(nodeSession)).setTable(table); for (HgOwnerKey key : keyList) { builder.addKey(GrpcUtil.toKey(key)); @@ -109,7 +89,7 @@ FeedbackRes doBatchGet(HgStoreNodeSession nodeSession, String table, List channels = new ConcurrentHashMap<>(); private final PDConfig pdConfig; private final PDClient pdClient; @@ -63,13 +68,43 @@ public Set getScanState() throws Exception { } catch (Exception e) { throw e; } + } + public String getPeers(String address, int partitionId) { + ManagedChannel channel = channels.get(address); + try { + if (channel == null) { + synchronized (channels) { + if ((channel = channels.get(address)) == null) { + channel = createChannel(address); + channels.put(address, channel); + } + } + } + HgStoreStateBlockingStub stub = (HgStoreStateBlockingStub) getBlockingStub(channel); + PeersResponse peers = + stub.getPeers(PartitionRequest.newBuilder().setId(partitionId).build()); + return peers.getPeers(); + } catch (Exception e) { + throw e; + } finally { + } } @Override public AbstractBlockingStub getBlockingStub(ManagedChannel channel) { - HgStoreStateBlockingStub stub; - stub = HgStoreStateGrpc.newBlockingStub(channel); - return stub; + return HgStoreStateGrpc.newBlockingStub(channel); + } + + @Override + public synchronized void close() { + for (ManagedChannel c : channels.values()) { + try { + c.shutdown(); + } catch (Exception e) { + + } + } + channels.clear(); } } diff --git a/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/grpc/KvBatchScanner5.java b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/grpc/KvBatchScanner5.java index 9f95eeb510..2ee91f62b7 100644 --- a/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/grpc/KvBatchScanner5.java +++ b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/grpc/KvBatchScanner5.java @@ -35,7 +35,7 @@ import org.apache.hugegraph.store.HgScanQuery; import org.apache.hugegraph.store.client.HgStoreNodeSession; import org.apache.hugegraph.store.client.type.HgStoreClientException; -import org.apache.hugegraph.store.util.Base58Encoder; +import org.apache.hugegraph.store.client.util.Base58; import org.apache.hugegraph.store.client.util.HgStoreClientConfig; import org.apache.hugegraph.store.grpc.common.Kv; import org.apache.hugegraph.store.grpc.stream.HgStoreStreamGrpc; @@ -107,7 +107,7 @@ private static class OrderBroker { if (log.isDebugEnabled()) { if (scanQuery.getPrefixList() != null && scanQuery.getPrefixList().size() > 0) { - brokerId = Base58Encoder.convertToBase58(scanQuery.getPrefixList().get(0).getKey()); + brokerId = Base58.encode(scanQuery.getPrefixList().get(0).getKey()); log.debug( "[ANALYSIS START] [{}] firstKey: {}, keyLength: {}, table: {}, node: {}" diff --git a/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/grpc/KvBatchScannerMerger.java b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/grpc/KvBatchScannerMerger.java index 4f89a275c6..a47c37ab42 100644 --- a/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/grpc/KvBatchScannerMerger.java +++ b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/grpc/KvBatchScannerMerger.java @@ -233,7 +233,7 @@ public SortedScannerMerger(KvBatchScanner.TaskSplitter splitter) { super(splitter); queue.add(() -> { // Perform merge sort on the store's return result - return new HgKvIterator<>() { + return new HgKvIterator() { private ScannerDataQueue iterator; private int currentSN = 0; private HgKvEntry entry; diff --git a/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/util/HgStoreClientUtil.java b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/util/HgStoreClientUtil.java index 5032d5aad6..7dc28b7149 100644 --- a/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/util/HgStoreClientUtil.java +++ b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/util/HgStoreClientUtil.java @@ -89,10 +89,11 @@ public static String toStr(HgOwnerKey ownerKey) { if (ownerKey == null) { return ""; } - return "{ " + - "owner: " + Arrays.toString(ownerKey.getOwner()) + - ", key: " + toStr(ownerKey.getKey()) + - " }"; + return new StringBuilder() + .append("{ ") + .append("owner: ").append(Arrays.toString(ownerKey.getOwner())) + .append(", key: ").append(toStr(ownerKey.getKey())) + .append(" }").toString(); } public static byte[] toBytes(String str) { diff --git a/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/util/HgUuid.java b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/util/HgUuid.java index 0933837a13..fd83fef20a 100644 --- a/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/util/HgUuid.java +++ b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/util/HgUuid.java @@ -20,15 +20,13 @@ import java.nio.ByteBuffer; import java.util.UUID; -import org.apache.hugegraph.store.util.Base58Encoder; - public final class HgUuid { private static String encode(UUID uuid) { ByteBuffer bb = ByteBuffer.wrap(new byte[16]); bb.putLong(uuid.getMostSignificantBits()); bb.putLong(uuid.getLeastSignificantBits()); - return Base58Encoder.convertToBase58(bb.array()); + return Base58.encode(bb.array()); } /** From 18b64ace9962e65bd271c10a3aff1f318dd5da52 Mon Sep 17 00:00:00 2001 From: JisoLya <523420504@qq.com> Date: Wed, 10 Sep 2025 17:03:06 +0800 Subject: [PATCH 32/35] fix: fix conflict in store-client --- .../store/client/grpc/GrpcStoreSessionClient.java | 10 +++++----- .../store/client/grpc/GrpcStoreStateClient.java | 1 + 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/grpc/GrpcStoreSessionClient.java b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/grpc/GrpcStoreSessionClient.java index 90288e4dfd..bd91d3147e 100644 --- a/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/grpc/GrpcStoreSessionClient.java +++ b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/grpc/GrpcStoreSessionClient.java @@ -17,6 +17,9 @@ package org.apache.hugegraph.store.client.grpc; +import static org.apache.hugegraph.store.client.grpc.GrpcUtil.getHeader; +import static org.apache.hugegraph.store.client.grpc.GrpcUtil.toTk; + import java.util.List; import javax.annotation.concurrent.ThreadSafe; @@ -40,9 +43,6 @@ import io.grpc.ManagedChannel; import lombok.extern.slf4j.Slf4j; -import static org.apache.hugegraph.store.client.grpc.GrpcUtil.getHeader; -import static org.apache.hugegraph.store.client.grpc.GrpcUtil.toTk; - /** * created on 2021/11/18 * @@ -64,7 +64,7 @@ private HgStoreSessionBlockingStub getBlockingStub(HgStoreNodeSession nodeSessio FeedbackRes doGet(HgStoreNodeSession nodeSession, String table, HgOwnerKey ownerKey) { return this.getBlockingStub(nodeSession) - .get(GetReq.newBuilder().setHeader(getHeader(nodeSession)) + .get2(GetReq.newBuilder().setHeader(getHeader(nodeSession)) .setTk(toTk(table, ownerKey)) .build()); } @@ -89,7 +89,7 @@ FeedbackRes doBatchGet(HgStoreNodeSession nodeSession, String table, List Date: Wed, 10 Sep 2025 18:06:11 +0800 Subject: [PATCH 33/35] fix: fix conflict in store-client --- .../hugegraph/store/client/grpc/AbstractGrpcClient.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/grpc/AbstractGrpcClient.java b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/grpc/AbstractGrpcClient.java index 03d4858109..92229079f2 100644 --- a/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/grpc/AbstractGrpcClient.java +++ b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/grpc/AbstractGrpcClient.java @@ -37,9 +37,9 @@ public abstract class AbstractGrpcClient { - private static Map channels = new ConcurrentHashMap<>(); + protected static Map channels = new ConcurrentHashMap<>(); private static int n = 5; - private static int concurrency = 1 << n; + protected static int concurrency = 1 << n; private static AtomicLong counter = new AtomicLong(0); private static long limit = Long.MAX_VALUE >> 1; private static HgStoreClientConfig config = HgStoreClientConfig.of(); @@ -162,7 +162,7 @@ public AbstractAsyncStub getAsyncStub(String target) { } - private AbstractStub setStubOption(AbstractStub value) { + protected AbstractStub setStubOption(AbstractStub value) { return value.withMaxInboundMessageSize( config.getGrpcMaxInboundMessageSize()) .withMaxOutboundMessageSize( From ddd6e250f8addc7d9e932c0934be65c8f6098736 Mon Sep 17 00:00:00 2001 From: JisoLya <523420504@qq.com> Date: Thu, 11 Sep 2025 16:48:00 +0800 Subject: [PATCH 34/35] fix: fix conflict in store-client --- .../apache/hugegraph/store/client/NodeTxSessionProxy.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/NodeTxSessionProxy.java b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/NodeTxSessionProxy.java index 9d9f516c50..30b7617e57 100644 --- a/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/NodeTxSessionProxy.java +++ b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/NodeTxSessionProxy.java @@ -40,6 +40,7 @@ import javax.annotation.concurrent.NotThreadSafe; import org.apache.hugegraph.HugeGraphSupplier; +import org.apache.hugegraph.pd.common.PDException; import org.apache.hugegraph.store.HgKvEntry; import org.apache.hugegraph.store.HgKvIterator; import org.apache.hugegraph.store.HgKvOrderedIterator; @@ -49,6 +50,7 @@ import org.apache.hugegraph.store.HgStoreSession; import org.apache.hugegraph.store.client.grpc.KvBatchScanner; import org.apache.hugegraph.store.client.grpc.KvCloseableIterator; +import org.apache.hugegraph.store.client.query.QueryExecutor; import org.apache.hugegraph.store.client.util.HgAssert; import org.apache.hugegraph.store.client.util.HgStoreClientConst; import org.apache.hugegraph.store.client.util.HgStoreClientUtil; @@ -893,7 +895,8 @@ private List> nodeTkv2Node(Collection node @Override public List> query(StoreQueryParam query, - HugeGraphSupplier supplier) throws PDException { + HugeGraphSupplier supplier) throws + PDException { long current = System.nanoTime(); QueryExecutor planner = new QueryExecutor(this.nodePartitioner, supplier, this.sessionConfig.getQueryPushDownTimeout()); From 50550aa6e70c2d0a42bded182a0cac73c7cb2d6b Mon Sep 17 00:00:00 2001 From: JisoLya <523420504@qq.com> Date: Sun, 14 Sep 2025 16:01:06 +0800 Subject: [PATCH 35/35] fix(store): add null pointer check --- .../apache/hugegraph/store/business/GraphStoreIterator.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/business/GraphStoreIterator.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/business/GraphStoreIterator.java index 8418ff23e2..b90d1ec704 100644 --- a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/business/GraphStoreIterator.java +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/business/GraphStoreIterator.java @@ -188,6 +188,9 @@ public T select(RocksDBSession.BackendColumn current) { } public ArrayList convert() { + if (data == null || data.isEmpty()) { + return new ArrayList<>(0); + } ArrayList result = new ArrayList(data.size()); for (int i = 0; i < data.size(); i++) { result.add(select(data.get(i)));