diff --git a/README-CN.md b/README-CN.md
index ad668e7432e..7c7d1061d1b 100644
--- a/README-CN.md
+++ b/README-CN.md
@@ -1,5 +1,5 @@
-
+
中文 | English
世界上唯一能够容纳千亿个顶点和万亿条边,并提供毫秒级查询延时的图数据库解决方案
diff --git a/README.md b/README.md
index 33c03fde5de..84b9fafa24a 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,5 @@
-
+
English | 中文
A distributed, scalable, lightning-fast graph database
diff --git a/src/clients/storage/StorageClient.cpp b/src/clients/storage/StorageClient.cpp
index 462dcefc367..5cc70ecde01 100644
--- a/src/clients/storage/StorageClient.cpp
+++ b/src/clients/storage/StorageClient.cpp
@@ -558,15 +558,15 @@ StorageRpcRespFuture StorageClient::lookupAndTravers
});
}
-StorageRpcRespFuture StorageClient::scanEdge(
+StorageRpcRespFuture StorageClient::scanEdge(
const CommonRequestParam& param,
- const cpp2::EdgeProp& edgeProp,
+ const std::vector& edgeProp,
int64_t limit,
const Expression* filter) {
std::unordered_map requests;
auto status = getHostPartsWithCursor(param.space);
if (!status.ok()) {
- return folly::makeFuture>(
+ return folly::makeFuture>(
std::runtime_error(status.status().toString()));
}
auto& clusters = status.value();
@@ -589,7 +589,7 @@ StorageRpcRespFuture StorageClient::scanEdge(
const cpp2::ScanEdgeRequest& r) { return client->future_scanEdge(r); });
}
-StorageRpcRespFuture StorageClient::scanVertex(
+StorageRpcRespFuture StorageClient::scanVertex(
const CommonRequestParam& param,
const std::vector& vertexProp,
int64_t limit,
@@ -597,7 +597,7 @@ StorageRpcRespFuture StorageClient::scanVertex(
std::unordered_map requests;
auto status = getHostPartsWithCursor(param.space);
if (!status.ok()) {
- return folly::makeFuture>(
+ return folly::makeFuture>(
std::runtime_error(status.status().toString()));
}
auto& clusters = status.value();
diff --git a/src/clients/storage/StorageClient.h b/src/clients/storage/StorageClient.h
index 2261f548b53..49c333fe715 100644
--- a/src/clients/storage/StorageClient.h
+++ b/src/clients/storage/StorageClient.h
@@ -129,12 +129,12 @@ class StorageClient : public StorageClientBase lookupAndTraverse(
const CommonRequestParam& param, cpp2::IndexSpec indexSpec, cpp2::TraverseSpec traverseSpec);
- StorageRpcRespFuture scanEdge(const CommonRequestParam& param,
- const cpp2::EdgeProp& vertexProp,
- int64_t limit,
- const Expression* filter);
+ StorageRpcRespFuture scanEdge(const CommonRequestParam& param,
+ const std::vector& vertexProp,
+ int64_t limit,
+ const Expression* filter);
- StorageRpcRespFuture scanVertex(
+ StorageRpcRespFuture scanVertex(
const CommonRequestParam& param,
const std::vector& vertexProp,
int64_t limit,
diff --git a/src/common/graph/ExecutionResponseOps-inl.h b/src/common/graph/ExecutionResponseOps-inl.h
index ee46d3f197f..e5b1987b408 100644
--- a/src/common/graph/ExecutionResponseOps-inl.h
+++ b/src/common/graph/ExecutionResponseOps-inl.h
@@ -37,7 +37,7 @@ struct TccStructTraits<::nebula::ExecutionResponse> {
_ftype = apache::thrift::protocol::T_I32;
} else if (_fname == "latency_in_us") {
fid = 2;
- _ftype = apache::thrift::protocol::T_I32;
+ _ftype = apache::thrift::protocol::T_I64;
} else if (_fname == "data") {
fid = 3;
_ftype = apache::thrift::protocol::T_STRUCT;
@@ -75,9 +75,9 @@ uint32_t Cpp2Ops<::nebula::ExecutionResponse>::write(Protocol* proto,
::nebula::ErrorCode>::write(*proto,
obj->errorCode);
xfer += proto->writeFieldEnd();
- xfer += proto->writeFieldBegin("latency_in_us", apache::thrift::protocol::T_I32, 2);
+ xfer += proto->writeFieldBegin("latency_in_us", apache::thrift::protocol::T_I64, 2);
xfer += ::apache::thrift::detail::pm::protocol_methods<::apache::thrift::type_class::integral,
- int32_t>::write(*proto, obj->latencyInUs);
+ int64_t>::write(*proto, obj->latencyInUs);
xfer += proto->writeFieldEnd();
if (obj->data != nullptr) {
xfer += proto->writeFieldBegin("data", apache::thrift::protocol::T_STRUCT, 3);
@@ -134,7 +134,7 @@ _readField_error_code : {
}
_readField_latency_in_us : {
::apache::thrift::detail::pm::protocol_methods<::apache::thrift::type_class::integral,
- int32_t>::read(*proto, obj->latencyInUs);
+ int64_t>::read(*proto, obj->latencyInUs);
isset_latency_in_us = true;
}
@@ -216,7 +216,7 @@ _readField_comment : {
}
}
case 2: {
- if (LIKELY(_readState.fieldType == apache::thrift::protocol::T_I32)) {
+ if (LIKELY(_readState.fieldType == apache::thrift::protocol::T_I64)) {
goto _readField_latency_in_us;
} else {
goto _skip;
@@ -276,9 +276,9 @@ uint32_t Cpp2Ops<::nebula::ExecutionResponse>::serializedSize(
xfer += ::apache::thrift::detail::pm::protocol_methods<
::apache::thrift::type_class::enumeration,
::nebula::ErrorCode>::serializedSize(*proto, obj->errorCode);
- xfer += proto->serializedFieldSize("latency_in_us", apache::thrift::protocol::T_I32, 2);
+ xfer += proto->serializedFieldSize("latency_in_us", apache::thrift::protocol::T_I64, 2);
xfer += ::apache::thrift::detail::pm::
- protocol_methods<::apache::thrift::type_class::integral, int32_t>::serializedSize(
+ protocol_methods<::apache::thrift::type_class::integral, int64_t>::serializedSize(
*proto, obj->latencyInUs);
if (obj->data != nullptr) {
xfer += proto->serializedFieldSize("data", apache::thrift::protocol::T_STRUCT, 3);
@@ -314,9 +314,9 @@ uint32_t Cpp2Ops<::nebula::ExecutionResponse>::serializedSizeZC(
xfer += ::apache::thrift::detail::pm::protocol_methods<
::apache::thrift::type_class::enumeration,
::nebula::ErrorCode>::serializedSize(*proto, obj->errorCode);
- xfer += proto->serializedFieldSize("latency_in_us", apache::thrift::protocol::T_I32, 2);
+ xfer += proto->serializedFieldSize("latency_in_us", apache::thrift::protocol::T_I64, 2);
xfer += ::apache::thrift::detail::pm::
- protocol_methods<::apache::thrift::type_class::integral, int32_t>::serializedSize(
+ protocol_methods<::apache::thrift::type_class::integral, int64_t>::serializedSize(
*proto, obj->latencyInUs);
if (obj->data != nullptr) {
xfer += proto->serializedFieldSize("data", apache::thrift::protocol::T_STRUCT, 3);
diff --git a/src/common/graph/Response.h b/src/common/graph/Response.h
index 51ad260240d..ba97cb29ce2 100644
--- a/src/common/graph/Response.h
+++ b/src/common/graph/Response.h
@@ -468,7 +468,7 @@ struct ExecutionResponse {
}
ErrorCode errorCode{ErrorCode::SUCCEEDED};
- int32_t latencyInUs{0};
+ int64_t latencyInUs{0};
std::unique_ptr data{nullptr};
std::unique_ptr spaceName{nullptr};
std::unique_ptr errorMsg{nullptr};
diff --git a/src/common/memory/MemoryUtils.cpp b/src/common/memory/MemoryUtils.cpp
index 43477149eff..e2803a15d75 100644
--- a/src/common/memory/MemoryUtils.cpp
+++ b/src/common/memory/MemoryUtils.cpp
@@ -32,18 +32,25 @@ StatusOr MemoryUtils::hitsHighWatermark() {
}
double available = 0.0, total = 0.0;
if (FLAGS_containerized) {
- FileUtils::FileLineIterator iter("/sys/fs/cgroup/memory/memory.stat", &reTotalCache);
+ bool cgroupsv2 = FileUtils::exist("/sys/fs/cgroup/cgroup.controllers");
+ std::string statPath =
+ cgroupsv2 ? "/sys/fs/cgroup/memory.stat" : "/sys/fs/cgroup/memory/memory.stat";
+ FileUtils::FileLineIterator iter(statPath, &reTotalCache);
uint64_t cacheSize = 0;
for (; iter.valid(); ++iter) {
auto& sm = iter.matched();
cacheSize += std::stoul(sm[2].str(), NULL);
}
- auto limitStatus = MemoryUtils::readSysContents("/sys/fs/cgroup/memory/memory.limit_in_bytes");
+ std::string limitPath =
+ cgroupsv2 ? "/sys/fs/cgroup/memory.max" : "/sys/fs/cgroup/memory/memory.limit_in_bytes";
+ auto limitStatus = MemoryUtils::readSysContents(limitPath);
NG_RETURN_IF_ERROR(limitStatus);
uint64_t limitInBytes = std::move(limitStatus).value();
- auto usageStatus = MemoryUtils::readSysContents("/sys/fs/cgroup/memory/memory.usage_in_bytes");
+ std::string usagePath =
+ cgroupsv2 ? "/sys/fs/cgroup/memory.current" : "/sys/fs/cgroup/memory/memory.usage_in_bytes";
+ auto usageStatus = MemoryUtils::readSysContents(usagePath);
NG_RETURN_IF_ERROR(usageStatus);
uint64_t usageInBytes = std::move(usageStatus).value();
diff --git a/src/common/meta/SchemaManager.cpp b/src/common/meta/SchemaManager.cpp
index f50642b60cc..1252e418d51 100644
--- a/src/common/meta/SchemaManager.cpp
+++ b/src/common/meta/SchemaManager.cpp
@@ -23,5 +23,17 @@ StatusOr> SchemaManager::getSchemaIDByName(GraphSpaceID
return Status::Error("Schema not exist: %s", schemaName.str().c_str());
}
+StatusOr> SchemaManager::getAllTags(GraphSpaceID space) {
+ std::unordered_map tags;
+ auto tagSchemas = getAllLatestVerTagSchema(space);
+ NG_RETURN_IF_ERROR(tagSchemas);
+ for (auto& tagSchema : tagSchemas.value()) {
+ auto tagName = toTagName(space, tagSchema.first);
+ NG_RETURN_IF_ERROR(tagName);
+ tags.emplace(tagSchema.first, tagName.value());
+ }
+ return tags;
+}
+
} // namespace meta
} // namespace nebula
diff --git a/src/common/meta/SchemaManager.h b/src/common/meta/SchemaManager.h
index ab7fe7fea3e..23fa8c68dec 100644
--- a/src/common/meta/SchemaManager.h
+++ b/src/common/meta/SchemaManager.h
@@ -68,6 +68,8 @@ class SchemaManager {
virtual StatusOr> getAllEdge(GraphSpaceID space) = 0;
+ StatusOr> getAllTags(GraphSpaceID space);
+
// get all version of all tag schema
virtual StatusOr getAllVerTagSchema(GraphSpaceID space) = 0;
diff --git a/src/common/utils/MetaKeyUtils.cpp b/src/common/utils/MetaKeyUtils.cpp
index 051ce19d025..88f4c5a5b19 100644
--- a/src/common/utils/MetaKeyUtils.cpp
+++ b/src/common/utils/MetaKeyUtils.cpp
@@ -1174,9 +1174,31 @@ GraphSpaceID MetaKeyUtils::parseLocalIdSpace(folly::StringPiece rawData) {
return *reinterpret_cast(rawData.data() + offset);
}
-GraphSpaceID MetaKeyUtils::parseDiskPartsSpace(folly::StringPiece rawData) {
+/**
+ * diskPartsKey = kDiskPartsTable + len(serialized(hostAddr)) + serialized(hostAddr) + path
+ */
+
+HostAddr MetaKeyUtils::parseDiskPartsHost(const folly::StringPiece& rawData) {
auto offset = kDiskPartsTable.size();
- return *reinterpret_cast(rawData.data() + offset);
+ auto hostAddrLen = *reinterpret_cast(rawData.begin() + offset);
+ offset += sizeof(size_t);
+ std::string hostAddrStr(rawData.data() + offset, hostAddrLen);
+ return deserializeHostAddr(hostAddrStr);
+}
+
+GraphSpaceID MetaKeyUtils::parseDiskPartsSpace(const folly::StringPiece& rawData) {
+ auto offset = kDiskPartsTable.size();
+ size_t hostAddrLen = *reinterpret_cast(rawData.begin() + offset);
+ offset += sizeof(size_t) + hostAddrLen;
+ return *reinterpret_cast(rawData.begin() + offset);
+}
+
+std::string MetaKeyUtils::parseDiskPartsPath(const folly::StringPiece& rawData) {
+ auto offset = kDiskPartsTable.size();
+ size_t hostAddrLen = *reinterpret_cast(rawData.begin() + offset);
+ offset += sizeof(size_t) + hostAddrLen + sizeof(GraphSpaceID);
+ std::string path(rawData.begin() + offset, rawData.size() - offset);
+ return path;
}
std::string MetaKeyUtils::diskPartsPrefix() { return kDiskPartsTable; }
@@ -1184,8 +1206,11 @@ std::string MetaKeyUtils::diskPartsPrefix() { return kDiskPartsTable; }
std::string MetaKeyUtils::diskPartsPrefix(HostAddr addr) {
std::string key;
std::string hostStr = serializeHostAddr(addr);
- key.reserve(kDiskPartsTable.size() + hostStr.size());
- key.append(kDiskPartsTable.data(), kDiskPartsTable.size()).append(hostStr.data(), hostStr.size());
+ size_t hostAddrLen = hostStr.size();
+ key.reserve(kDiskPartsTable.size() + sizeof(size_t) + hostStr.size());
+ key.append(kDiskPartsTable.data(), kDiskPartsTable.size())
+ .append(reinterpret_cast(&hostAddrLen), sizeof(size_t))
+ .append(hostStr.data(), hostStr.size());
return key;
}
@@ -1198,7 +1223,9 @@ std::string MetaKeyUtils::diskPartsPrefix(HostAddr addr, GraphSpaceID spaceId) {
return key;
}
-std::string MetaKeyUtils::diskPartsKey(HostAddr addr, GraphSpaceID spaceId, std::string path) {
+std::string MetaKeyUtils::diskPartsKey(HostAddr addr,
+ GraphSpaceID spaceId,
+ const std::string& path) {
std::string key;
std::string prefix = diskPartsPrefix(addr, spaceId);
key.reserve(prefix.size() + path.size());
diff --git a/src/common/utils/MetaKeyUtils.h b/src/common/utils/MetaKeyUtils.h
index daf934599ea..7ebce2d009a 100644
--- a/src/common/utils/MetaKeyUtils.h
+++ b/src/common/utils/MetaKeyUtils.h
@@ -382,7 +382,11 @@ class MetaKeyUtils final {
static std::unordered_map> getSystemTableMaps();
- static GraphSpaceID parseDiskPartsSpace(folly::StringPiece rawData);
+ static GraphSpaceID parseDiskPartsSpace(const folly::StringPiece& rawData);
+
+ static HostAddr parseDiskPartsHost(const folly::StringPiece& rawData);
+
+ static std::string parseDiskPartsPath(const folly::StringPiece& rawData);
static std::string diskPartsPrefix();
@@ -390,7 +394,7 @@ class MetaKeyUtils final {
static std::string diskPartsPrefix(HostAddr addr, GraphSpaceID spaceId);
- static std::string diskPartsKey(HostAddr addr, GraphSpaceID spaceId, std::string path);
+ static std::string diskPartsKey(HostAddr addr, GraphSpaceID spaceId, const std::string& path);
static std::string diskPartsVal(const meta::cpp2::PartitionList& partList);
diff --git a/src/common/utils/NebulaKeyUtils.cpp b/src/common/utils/NebulaKeyUtils.cpp
index 9e7cc2585f7..0323269cbf0 100644
--- a/src/common/utils/NebulaKeyUtils.cpp
+++ b/src/common/utils/NebulaKeyUtils.cpp
@@ -72,7 +72,20 @@ std::string NebulaKeyUtils::edgeKey(size_t vIdLen,
.append(1, ev);
return key;
}
-
+// static
+std::string NebulaKeyUtils::vertexKey(size_t vIdLen,
+ PartitionID partId,
+ const VertexID& vId,
+ char pad) {
+ CHECK_GE(vIdLen, vId.size());
+ int32_t item = (partId << kPartitionOffset) | static_cast(NebulaKeyType::kVertex);
+ std::string key;
+ key.reserve(kTagLen + vIdLen);
+ key.append(reinterpret_cast(&item), sizeof(int32_t))
+ .append(vId.data(), vId.size())
+ .append(vIdLen - vId.size(), pad);
+ return key;
+}
// static
std::string NebulaKeyUtils::systemCommitKey(PartitionID partId) {
int32_t item = (partId << kPartitionOffset) | static_cast(NebulaKeyType::kSystem);
diff --git a/src/common/utils/NebulaKeyUtils.h b/src/common/utils/NebulaKeyUtils.h
index 0d0ba17ea24..2cb53d54af5 100644
--- a/src/common/utils/NebulaKeyUtils.h
+++ b/src/common/utils/NebulaKeyUtils.h
@@ -53,7 +53,7 @@ class NebulaKeyUtils final {
static std::string lastKey(const std::string& prefix, size_t count);
/**
- * Generate vertex key for kv store
+ * Generate tag key for kv store
* */
static std::string tagKey(
size_t vIdLen, PartitionID partId, const VertexID& vId, TagID tagId, char pad = '\0');
@@ -65,7 +65,10 @@ class NebulaKeyUtils final {
EdgeRanking rank,
const VertexID& dstId,
EdgeVerPlaceHolder ev = 1);
-
+ static std::string vertexKey(size_t vIdLen,
+ PartitionID partId,
+ const VertexID& vId,
+ char pad = '\0');
static std::string systemCommitKey(PartitionID partId);
static std::string systemPartKey(PartitionID partId);
@@ -73,7 +76,7 @@ class NebulaKeyUtils final {
static std::string kvKey(PartitionID partId, const folly::StringPiece& name);
/**
- * Prefix for vertex
+ * Prefix for tag
* */
static std::string tagPrefix(size_t vIdLen, PartitionID partId, const VertexID& vId, TagID tagId);
diff --git a/src/common/utils/Types.h b/src/common/utils/Types.h
index 8012ac91305..88e74106edc 100644
--- a/src/common/utils/Types.h
+++ b/src/common/utils/Types.h
@@ -18,7 +18,7 @@ enum class NebulaKeyType : uint32_t {
kSystem = 0x00000004,
kOperation = 0x00000005,
kKeyValue = 0x00000006,
- // kVertex = 0x00000007,
+ kVertex = 0x00000007,
};
enum class NebulaSystemKeyType : uint32_t {
@@ -41,10 +41,10 @@ static typename std::enable_if::value, T>::type readInt(cons
return *reinterpret_cast(data);
}
-// size of vertex key except vertexId
+// size of tag key except vertexId
static constexpr int32_t kTagLen = sizeof(PartitionID) + sizeof(TagID);
-// size of vertex key except srcId and dstId
+// size of tag key except srcId and dstId
static constexpr int32_t kEdgeLen =
sizeof(PartitionID) + sizeof(EdgeType) + sizeof(EdgeRanking) + sizeof(EdgeVerPlaceHolder);
diff --git a/src/common/utils/test/MetaKeyUtilsTest.cpp b/src/common/utils/test/MetaKeyUtilsTest.cpp
index 26945701112..a3bc910c80d 100644
--- a/src/common/utils/test/MetaKeyUtilsTest.cpp
+++ b/src/common/utils/test/MetaKeyUtilsTest.cpp
@@ -201,6 +201,17 @@ TEST(MetaKeyUtilsTest, ZoneTest) {
ASSERT_EQ(nodes, MetaKeyUtils::parseZoneHosts(zoneValue));
}
+TEST(MetaKeyUtilsTest, DiskPathsTest) {
+ HostAddr addr{"192.168.0.1", 1234};
+ GraphSpaceID spaceId = 1;
+ std::string path = "/data/storage/test_part1";
+
+ auto diskPartsKey = MetaKeyUtils::diskPartsKey(addr, spaceId, path);
+ ASSERT_EQ(addr, MetaKeyUtils::parseDiskPartsHost(diskPartsKey));
+ ASSERT_EQ(spaceId, MetaKeyUtils::parseDiskPartsSpace(diskPartsKey));
+ ASSERT_EQ(path, MetaKeyUtils::parseDiskPartsPath(diskPartsKey));
+}
+
} // namespace nebula
int main(int argc, char** argv) {
diff --git a/src/graph/context/ast/CypherAstContext.h b/src/graph/context/ast/CypherAstContext.h
index f6d4e775a57..31b2bde72b1 100644
--- a/src/graph/context/ast/CypherAstContext.h
+++ b/src/graph/context/ast/CypherAstContext.h
@@ -63,6 +63,8 @@ struct ScanInfo {
std::vector indexIds;
// use for seek by edge only
MatchEdge::Direction direction{MatchEdge::Direction::OUT_EDGE};
+ // use for scan seek
+ bool anyLabel{false};
};
struct CypherClauseContextBase : AstContext {
diff --git a/src/graph/executor/CMakeLists.txt b/src/graph/executor/CMakeLists.txt
index 5bb8064ce3c..4be67ba0c25 100644
--- a/src/graph/executor/CMakeLists.txt
+++ b/src/graph/executor/CMakeLists.txt
@@ -34,6 +34,8 @@ nebula_add_library(
query/InnerJoinExecutor.cpp
query/IndexScanExecutor.cpp
query/AssignExecutor.cpp
+ query/ScanVerticesExecutor.cpp
+ query/ScanEdgesExecutor.cpp
query/TraverseExecutor.cpp
query/AppendVerticesExecutor.cpp
algo/ConjunctPathExecutor.cpp
diff --git a/src/graph/executor/Executor.cpp b/src/graph/executor/Executor.cpp
index 447e25acb31..78cbaf24573 100644
--- a/src/graph/executor/Executor.cpp
+++ b/src/graph/executor/Executor.cpp
@@ -81,6 +81,8 @@
#include "graph/executor/query/MinusExecutor.h"
#include "graph/executor/query/ProjectExecutor.h"
#include "graph/executor/query/SampleExecutor.h"
+#include "graph/executor/query/ScanEdgesExecutor.h"
+#include "graph/executor/query/ScanVerticesExecutor.h"
#include "graph/executor/query/SortExecutor.h"
#include "graph/executor/query/TopNExecutor.h"
#include "graph/executor/query/TraverseExecutor.h"
@@ -170,6 +172,12 @@ Executor *Executor::makeExecutor(QueryContext *qctx, const PlanNode *node) {
case PlanNode::Kind::kGetVertices: {
return pool->add(new GetVerticesExecutor(node, qctx));
}
+ case PlanNode::Kind::kScanEdges: {
+ return pool->add(new ScanEdgesExecutor(node, qctx));
+ }
+ case PlanNode::Kind::kScanVertices: {
+ return pool->add(new ScanVerticesExecutor(node, qctx));
+ }
case PlanNode::Kind::kGetNeighbors: {
return pool->add(new GetNeighborsExecutor(node, qctx));
}
diff --git a/src/graph/executor/query/GetPropExecutor.h b/src/graph/executor/query/GetPropExecutor.h
index 9db3be0d20c..313e3f2bef2 100644
--- a/src/graph/executor/query/GetPropExecutor.h
+++ b/src/graph/executor/query/GetPropExecutor.h
@@ -18,7 +18,8 @@ class GetPropExecutor : public StorageAccessExecutor {
GetPropExecutor(const std::string &name, const PlanNode *node, QueryContext *qctx)
: StorageAccessExecutor(name, node, qctx) {}
- Status handleResp(storage::StorageRpcResponse &&rpcResp,
+ template
+ Status handleResp(storage::StorageRpcResponse &&rpcResp,
const std::vector &colNames) {
auto result = handleCompleteness(rpcResp, FLAGS_accept_partial_success);
NG_RETURN_IF_ERROR(result);
diff --git a/src/graph/executor/query/ScanEdgesExecutor.cpp b/src/graph/executor/query/ScanEdgesExecutor.cpp
new file mode 100644
index 00000000000..fd70ad6e86b
--- /dev/null
+++ b/src/graph/executor/query/ScanEdgesExecutor.cpp
@@ -0,0 +1,50 @@
+/* Copyright (c) 2021 vesoft inc. All rights reserved.
+ *
+ * This source code is licensed under Apache 2.0 License.
+ */
+
+#include "graph/executor/query/ScanEdgesExecutor.h"
+
+#include "common/time/ScopedTimer.h"
+#include "graph/context/QueryContext.h"
+#include "graph/planner/plan/Query.h"
+#include "graph/util/SchemaUtil.h"
+
+using nebula::storage::StorageClient;
+using nebula::storage::StorageRpcResponse;
+using nebula::storage::cpp2::ScanResponse;
+
+namespace nebula {
+namespace graph {
+
+folly::Future ScanEdgesExecutor::execute() { return scanEdges(); }
+
+folly::Future ScanEdgesExecutor::scanEdges() {
+ SCOPED_TIMER(&execTime_);
+ StorageClient *client = qctx()->getStorageClient();
+ auto *se = asNode(node());
+ if (se->limit() < 0) {
+ return Status::Error("Scan edges must specify limit number.");
+ }
+
+ time::Duration scanEdgesTime;
+ StorageClient::CommonRequestParam param(se->space(),
+ qctx()->rctx()->session()->id(),
+ qctx()->plan()->id(),
+ qctx()->plan()->isProfileEnabled());
+ return DCHECK_NOTNULL(client)
+ ->scanEdge(param, *DCHECK_NOTNULL(se->props()), se->limit(), se->filter())
+ .via(runner())
+ .ensure([this, scanEdgesTime]() {
+ SCOPED_TIMER(&execTime_);
+ otherStats_.emplace("total_rpc", folly::sformat("{}(us)", scanEdgesTime.elapsedInUSec()));
+ })
+ .thenValue([this, se](StorageRpcResponse &&rpcResp) {
+ SCOPED_TIMER(&execTime_);
+ addStats(rpcResp, otherStats_);
+ return handleResp(std::move(rpcResp), se->colNames());
+ });
+}
+
+} // namespace graph
+} // namespace nebula
diff --git a/src/graph/executor/query/ScanEdgesExecutor.h b/src/graph/executor/query/ScanEdgesExecutor.h
new file mode 100644
index 00000000000..c2385182e29
--- /dev/null
+++ b/src/graph/executor/query/ScanEdgesExecutor.h
@@ -0,0 +1,22 @@
+/* Copyright (c) 2021 vesoft inc. All rights reserved.
+ *
+ * This source code is licensed under Apache 2.0 License.
+ */
+
+#include "graph/executor/query/GetPropExecutor.h"
+
+namespace nebula {
+namespace graph {
+class ScanEdgesExecutor final : public GetPropExecutor {
+ public:
+ ScanEdgesExecutor(const PlanNode *node, QueryContext *qctx)
+ : GetPropExecutor("ScanEdgesExecutor", node, qctx) {}
+
+ folly::Future execute() override;
+
+ private:
+ folly::Future scanEdges();
+};
+
+} // namespace graph
+} // namespace nebula
diff --git a/src/graph/executor/query/ScanVerticesExecutor.cpp b/src/graph/executor/query/ScanVerticesExecutor.cpp
new file mode 100644
index 00000000000..2ff896002b2
--- /dev/null
+++ b/src/graph/executor/query/ScanVerticesExecutor.cpp
@@ -0,0 +1,50 @@
+/* Copyright (c) 2021 vesoft inc. All rights reserved.
+ *
+ * This source code is licensed under Apache 2.0 License.
+ */
+
+#include "graph/executor/query/ScanVerticesExecutor.h"
+
+#include "common/time/ScopedTimer.h"
+#include "graph/context/QueryContext.h"
+#include "graph/util/SchemaUtil.h"
+
+using nebula::storage::StorageClient;
+using nebula::storage::StorageRpcResponse;
+using nebula::storage::cpp2::ScanResponse;
+
+namespace nebula {
+namespace graph {
+
+folly::Future ScanVerticesExecutor::execute() { return scanVertices(); }
+
+folly::Future ScanVerticesExecutor::scanVertices() {
+ SCOPED_TIMER(&execTime_);
+
+ auto *sv = asNode(node());
+ if (sv->limit() < 0) {
+ return Status::Error("Scan vertices must specify limit number.");
+ }
+ StorageClient *storageClient = qctx()->getStorageClient();
+
+ time::Duration scanVertexTime;
+ StorageClient::CommonRequestParam param(sv->space(),
+ qctx()->rctx()->session()->id(),
+ qctx()->plan()->id(),
+ qctx()->plan()->isProfileEnabled());
+ return DCHECK_NOTNULL(storageClient)
+ ->scanVertex(param, *DCHECK_NOTNULL(sv->props()), sv->limit(), sv->filter())
+ .via(runner())
+ .ensure([this, scanVertexTime]() {
+ SCOPED_TIMER(&execTime_);
+ otherStats_.emplace("total_rpc", folly::sformat("{}(us)", scanVertexTime.elapsedInUSec()));
+ })
+ .thenValue([this, sv](StorageRpcResponse &&rpcResp) {
+ SCOPED_TIMER(&execTime_);
+ addStats(rpcResp, otherStats_);
+ return handleResp(std::move(rpcResp), sv->colNames());
+ });
+}
+
+} // namespace graph
+} // namespace nebula
diff --git a/src/graph/executor/query/ScanVerticesExecutor.h b/src/graph/executor/query/ScanVerticesExecutor.h
new file mode 100644
index 00000000000..1b46cc84549
--- /dev/null
+++ b/src/graph/executor/query/ScanVerticesExecutor.h
@@ -0,0 +1,26 @@
+/* Copyright (c) 2021 vesoft inc. All rights reserved.
+ *
+ * This source code is licensed under Apache 2.0 License.
+ */
+
+#pragma once
+
+#include "graph/executor/query/GetPropExecutor.h"
+#include "graph/planner/plan/Query.h"
+
+namespace nebula {
+namespace graph {
+
+class ScanVerticesExecutor final : public GetPropExecutor {
+ public:
+ ScanVerticesExecutor(const PlanNode *node, QueryContext *qctx)
+ : GetPropExecutor("ScanVerticesExecutor", node, qctx) {}
+
+ folly::Future execute() override;
+
+ private:
+ folly::Future scanVertices();
+};
+
+} // namespace graph
+} // namespace nebula
diff --git a/src/graph/optimizer/CMakeLists.txt b/src/graph/optimizer/CMakeLists.txt
index 0329f9c60b7..d7326f5bb01 100644
--- a/src/graph/optimizer/CMakeLists.txt
+++ b/src/graph/optimizer/CMakeLists.txt
@@ -26,6 +26,8 @@ nebula_add_library(
rule/PushFilterDownAggregateRule.cpp
rule/PushFilterDownProjectRule.cpp
rule/PushFilterDownLeftJoinRule.cpp
+ rule/PushFilterDownScanVerticesRule.cpp
+ rule/PushVFilterDownScanVerticesRule.cpp
rule/OptimizeEdgeIndexScanByFilterRule.cpp
rule/OptimizeTagIndexScanByFilterRule.cpp
rule/UnionAllIndexScanBaseRule.cpp
@@ -45,6 +47,9 @@ nebula_add_library(
rule/PushLimitDownEdgeIndexPrefixScanRule.cpp
rule/PushLimitDownEdgeIndexRangeScanRule.cpp
rule/PushLimitDownProjectRule.cpp
+ rule/PushLimitDownScanAppendVerticesRule.cpp
+ rule/GetEdgesTransformRule.cpp
+ rule/PushLimitDownScanEdgesAppendVerticesRule.cpp
)
nebula_add_subdirectory(test)
diff --git a/src/graph/optimizer/rule/GetEdgesTransformRule.cpp b/src/graph/optimizer/rule/GetEdgesTransformRule.cpp
new file mode 100644
index 00000000000..bfa8456c57a
--- /dev/null
+++ b/src/graph/optimizer/rule/GetEdgesTransformRule.cpp
@@ -0,0 +1,129 @@
+/* Copyright (c) 2021 vesoft inc. All rights reserved.
+ *
+ * This source code is licensed under Apache 2.0 License.
+ */
+
+#include "graph/optimizer/rule/GetEdgesTransformRule.h"
+
+#include "common/expression/Expression.h"
+#include "graph/optimizer/OptContext.h"
+#include "graph/optimizer/OptGroup.h"
+#include "graph/planner/plan/PlanNode.h"
+#include "graph/planner/plan/Query.h"
+#include "graph/visitor/ExtractFilterExprVisitor.h"
+
+using nebula::Expression;
+using nebula::graph::AppendVertices;
+using nebula::graph::PlanNode;
+using nebula::graph::Project;
+using nebula::graph::QueryContext;
+using nebula::graph::ScanEdges;
+using nebula::graph::ScanVertices;
+using nebula::graph::Traverse;
+
+namespace nebula {
+namespace opt {
+
+std::unique_ptr GetEdgesTransformRule::kInstance =
+ std::unique_ptr(new GetEdgesTransformRule());
+
+GetEdgesTransformRule::GetEdgesTransformRule() { RuleSet::QueryRules().addRule(this); }
+
+const Pattern &GetEdgesTransformRule::pattern() const {
+ static Pattern pattern =
+ Pattern::create(PlanNode::Kind::kAppendVertices,
+ {Pattern::create(PlanNode::Kind::kTraverse,
+ {Pattern::create(PlanNode::Kind::kScanVertices)})});
+ return pattern;
+}
+
+bool GetEdgesTransformRule::match(OptContext *ctx, const MatchedResult &matched) const {
+ if (!OptRule::match(ctx, matched)) {
+ return false;
+ }
+ auto traverse = static_cast(matched.planNode({0, 0}));
+ const auto &colNames = traverse->colNames();
+ auto colSize = colNames.size();
+ DCHECK_GE(colSize, 2);
+ if (colNames[colSize - 2][0] != '_') { // src
+ return false;
+ }
+ if (traverse->stepRange() != nullptr) {
+ return false;
+ }
+ return true;
+}
+
+StatusOr GetEdgesTransformRule::transform(
+ OptContext *ctx, const MatchedResult &matched) const {
+ auto appendVerticesGroupNode = matched.node;
+ auto appendVertices = static_cast(appendVerticesGroupNode->node());
+ auto traverseGroupNode = matched.dependencies.front().node;
+ auto traverse = static_cast(traverseGroupNode->node());
+ auto scanVerticesGroupNode = matched.dependencies.front().dependencies.front().node;
+ auto qctx = ctx->qctx();
+
+ auto newAppendVertices = appendVertices->clone();
+ auto colSize = appendVertices->colNames().size();
+ newAppendVertices->setColNames(
+ {appendVertices->colNames()[colSize - 2], appendVertices->colNames()[colSize - 1]});
+ auto newAppendVerticesGroupNode =
+ OptGroupNode::create(ctx, newAppendVertices, appendVerticesGroupNode->group());
+
+ auto *newScanEdges = traverseToScanEdges(traverse);
+ auto newScanEdgesGroup = OptGroup::create(ctx);
+ auto newScanEdgesGroupNode = newScanEdgesGroup->makeGroupNode(newScanEdges);
+
+ auto *newProj = projectEdges(qctx, newScanEdges, traverse->colNames().back());
+ newProj->setInputVar(newScanEdges->outputVar());
+ newProj->setOutputVar(traverse->outputVar());
+ newProj->setColNames({traverse->colNames().back()});
+ auto newProjGroup = OptGroup::create(ctx);
+ auto newProjGroupNode = newProjGroup->makeGroupNode(newProj);
+
+ newAppendVerticesGroupNode->dependsOn(newProjGroup);
+ newProjGroupNode->dependsOn(newScanEdgesGroup);
+ for (auto dep : scanVerticesGroupNode->dependencies()) {
+ newScanEdgesGroupNode->dependsOn(dep);
+ }
+
+ TransformResult result;
+ result.eraseCurr = true;
+ result.newGroupNodes.emplace_back(newAppendVerticesGroupNode);
+ return result;
+}
+
+std::string GetEdgesTransformRule::toString() const { return "GetEdgesTransformRule"; }
+
+/*static*/ graph::ScanEdges *GetEdgesTransformRule::traverseToScanEdges(
+ const graph::Traverse *traverse) {
+ const auto *edgeProps = traverse->edgeProps();
+ auto scanEdges = ScanEdges::make(
+ traverse->qctx(),
+ nullptr,
+ traverse->space(),
+ edgeProps == nullptr ? nullptr
+ : std::make_unique>(*edgeProps),
+ nullptr,
+ traverse->dedup(),
+ traverse->limit(),
+ {},
+ traverse->filter() == nullptr ? nullptr : traverse->filter()->clone());
+ return scanEdges;
+}
+
+/*static*/ graph::Project *GetEdgesTransformRule::projectEdges(graph::QueryContext *qctx,
+ PlanNode *input,
+ const std::string &colName) {
+ auto *yieldColumns = qctx->objPool()->makeAndAdd();
+ auto *edgeExpr = EdgeExpression::make(qctx->objPool());
+ auto *listEdgeExpr = ListExpression::make(qctx->objPool());
+ listEdgeExpr->setItems({edgeExpr});
+ yieldColumns->addColumn(new YieldColumn(listEdgeExpr, colName));
+ auto project = Project::make(qctx, input, yieldColumns);
+ project->setColNames({colName});
+ return project;
+}
+
+} // namespace opt
+} // namespace nebula
diff --git a/src/graph/optimizer/rule/GetEdgesTransformRule.h b/src/graph/optimizer/rule/GetEdgesTransformRule.h
new file mode 100644
index 00000000000..86492a44fa7
--- /dev/null
+++ b/src/graph/optimizer/rule/GetEdgesTransformRule.h
@@ -0,0 +1,45 @@
+/* Copyright (c) 2021 vesoft inc. All rights reserved.
+ *
+ * This source code is licensed under Apache 2.0 License.
+ */
+
+#pragma once
+
+#include "graph/optimizer/OptRule.h"
+
+namespace nebula {
+
+namespace graph {
+class ScanEdges;
+class Project;
+class Traverse;
+class PlanNode;
+} // namespace graph
+
+namespace opt {
+
+// e.g. match ()-[e]->(?) return e
+// Optimize to get edges directly
+class GetEdgesTransformRule final : public OptRule {
+ public:
+ const Pattern &pattern() const override;
+
+ bool match(OptContext *ctx, const MatchedResult &matched) const override;
+ StatusOr transform(OptContext *ctx, const MatchedResult &matched) const override;
+
+ std::string toString() const override;
+
+ private:
+ GetEdgesTransformRule();
+
+ static graph::ScanEdges *traverseToScanEdges(const graph::Traverse *traverse);
+
+ static graph::Project *projectEdges(graph::QueryContext *qctx,
+ graph::PlanNode *input,
+ const std::string &colName);
+
+ static std::unique_ptr kInstance;
+};
+
+} // namespace opt
+} // namespace nebula
diff --git a/src/graph/optimizer/rule/PushFilterDownScanVerticesRule.cpp b/src/graph/optimizer/rule/PushFilterDownScanVerticesRule.cpp
new file mode 100644
index 00000000000..00ed8a00711
--- /dev/null
+++ b/src/graph/optimizer/rule/PushFilterDownScanVerticesRule.cpp
@@ -0,0 +1,99 @@
+/* Copyright (c) 2021 vesoft inc. All rights reserved.
+ *
+ * This source code is licensed under Apache 2.0 License.
+ */
+
+#include "graph/optimizer/rule/PushFilterDownScanVerticesRule.h"
+
+#include "common/expression/Expression.h"
+#include "common/expression/LogicalExpression.h"
+#include "graph/optimizer/OptContext.h"
+#include "graph/optimizer/OptGroup.h"
+#include "graph/planner/plan/PlanNode.h"
+#include "graph/planner/plan/Query.h"
+#include "graph/visitor/ExtractFilterExprVisitor.h"
+
+using nebula::Expression;
+using nebula::graph::Filter;
+using nebula::graph::PlanNode;
+using nebula::graph::QueryContext;
+using nebula::graph::ScanVertices;
+
+namespace nebula {
+namespace opt {
+
+std::unique_ptr PushFilterDownScanVerticesRule::kInstance =
+ std::unique_ptr(new PushFilterDownScanVerticesRule());
+
+PushFilterDownScanVerticesRule::PushFilterDownScanVerticesRule() {
+ RuleSet::QueryRules().addRule(this);
+}
+
+const Pattern &PushFilterDownScanVerticesRule::pattern() const {
+ static Pattern pattern =
+ Pattern::create(PlanNode::Kind::kFilter, {Pattern::create(PlanNode::Kind::kScanVertices)});
+ return pattern;
+}
+
+StatusOr PushFilterDownScanVerticesRule::transform(
+ OptContext *ctx, const MatchedResult &matched) const {
+ auto filterGroupNode = matched.node;
+ auto svGroupNode = matched.dependencies.front().node;
+ auto filter = static_cast(filterGroupNode->node());
+ auto sv = static_cast(svGroupNode->node());
+ auto qctx = ctx->qctx();
+ auto pool = qctx->objPool();
+ auto condition = filter->condition()->clone();
+
+ auto visitor = graph::ExtractFilterExprVisitor::makePushGetVertices(pool);
+ condition->accept(&visitor);
+ if (!visitor.ok()) {
+ return TransformResult::noTransform();
+ }
+
+ auto remainedExpr = std::move(visitor).remainedExpr();
+ OptGroupNode *newFilterGroupNode = nullptr;
+ if (remainedExpr != nullptr) {
+ auto newFilter = Filter::make(qctx, nullptr, remainedExpr);
+ newFilter->setOutputVar(filter->outputVar());
+ newFilter->setInputVar(filter->inputVar());
+ newFilterGroupNode = OptGroupNode::create(ctx, newFilter, filterGroupNode->group());
+ }
+
+ auto newSVFilter = condition;
+ if (sv->filter() != nullptr) {
+ auto logicExpr = LogicalExpression::makeAnd(pool, condition, sv->filter()->clone());
+ newSVFilter = logicExpr;
+ }
+
+ auto newSV = static_cast(sv->clone());
+ newSV->setFilter(newSVFilter);
+
+ OptGroupNode *newSvGroupNode = nullptr;
+ if (newFilterGroupNode != nullptr) {
+ // Filter(A&&B)<-ScanVertices(C) => Filter(A)<-ScanVertices(B&&C)
+ auto newGroup = OptGroup::create(ctx);
+ newSvGroupNode = newGroup->makeGroupNode(newSV);
+ newFilterGroupNode->dependsOn(newGroup);
+ } else {
+ // Filter(A)<-ScanVertices(C) => ScanVertices(A&&C)
+ newSvGroupNode = OptGroupNode::create(ctx, newSV, filterGroupNode->group());
+ newSV->setOutputVar(filter->outputVar());
+ }
+
+ for (auto dep : svGroupNode->dependencies()) {
+ newSvGroupNode->dependsOn(dep);
+ }
+
+ TransformResult result;
+ result.eraseCurr = true;
+ result.newGroupNodes.emplace_back(newFilterGroupNode ? newFilterGroupNode : newSvGroupNode);
+ return result;
+}
+
+std::string PushFilterDownScanVerticesRule::toString() const {
+ return "PushFilterDownScanVerticesRule";
+}
+
+} // namespace opt
+} // namespace nebula
diff --git a/src/graph/optimizer/rule/PushFilterDownScanVerticesRule.h b/src/graph/optimizer/rule/PushFilterDownScanVerticesRule.h
new file mode 100644
index 00000000000..1f5cf14fc61
--- /dev/null
+++ b/src/graph/optimizer/rule/PushFilterDownScanVerticesRule.h
@@ -0,0 +1,30 @@
+/* Copyright (c) 2021 vesoft inc. All rights reserved.
+ *
+ * This source code is licensed under Apache 2.0 License.
+ */
+
+#pragma once
+
+#include
+
+#include "graph/optimizer/OptRule.h"
+
+namespace nebula {
+namespace opt {
+
+class PushFilterDownScanVerticesRule final : public OptRule {
+ public:
+ const Pattern &pattern() const override;
+
+ StatusOr transform(OptContext *ctx, const MatchedResult &matched) const override;
+
+ std::string toString() const override;
+
+ private:
+ PushFilterDownScanVerticesRule();
+
+ static std::unique_ptr kInstance;
+};
+
+} // namespace opt
+} // namespace nebula
diff --git a/src/graph/optimizer/rule/PushLimitDownScanAppendVerticesRule.cpp b/src/graph/optimizer/rule/PushLimitDownScanAppendVerticesRule.cpp
new file mode 100644
index 00000000000..26c4d0d1440
--- /dev/null
+++ b/src/graph/optimizer/rule/PushLimitDownScanAppendVerticesRule.cpp
@@ -0,0 +1,104 @@
+/* Copyright (c) 2021 vesoft inc. All rights reserved.
+ *
+ * This source code is licensed under Apache 2.0 License.
+ */
+
+#include "graph/optimizer/rule/PushLimitDownScanAppendVerticesRule.h"
+
+#include "graph/optimizer/OptContext.h"
+#include "graph/optimizer/OptGroup.h"
+#include "graph/planner/plan/PlanNode.h"
+#include "graph/planner/plan/Query.h"
+
+using nebula::graph::AppendVertices;
+using nebula::graph::Limit;
+using nebula::graph::PlanNode;
+using nebula::graph::QueryContext;
+using nebula::graph::ScanVertices;
+
+namespace nebula {
+namespace opt {
+
+// Limit->AppendVertices->ScanVertices ==> Limit->AppendVertices->ScanVertices(Limit)
+
+std::unique_ptr PushLimitDownScanAppendVerticesRule::kInstance =
+ std::unique_ptr(new PushLimitDownScanAppendVerticesRule());
+
+PushLimitDownScanAppendVerticesRule::PushLimitDownScanAppendVerticesRule() {
+ RuleSet::QueryRules().addRule(this);
+}
+
+const Pattern &PushLimitDownScanAppendVerticesRule::pattern() const {
+ static Pattern pattern =
+ Pattern::create(graph::PlanNode::Kind::kLimit,
+ {Pattern::create(graph::PlanNode::Kind::kAppendVertices,
+ {Pattern::create(graph::PlanNode::Kind::kScanVertices)})});
+ return pattern;
+}
+
+bool PushLimitDownScanAppendVerticesRule::match(OptContext *ctx,
+ const MatchedResult &matched) const {
+ if (!OptRule::match(ctx, matched)) {
+ return false;
+ }
+ auto av = static_cast(matched.planNode({0, 0}));
+ auto *src = av->src();
+ if (src->kind() != Expression::Kind::kInputProperty &&
+ src->kind() != Expression::Kind::kVarProperty) {
+ return false;
+ }
+ auto *propExpr = static_cast(src);
+ if (propExpr->prop() != kVid) {
+ return false;
+ }
+ auto *filter = av->filter();
+ auto *vFilter = av->vFilter();
+ // Limit can't push over filter operation
+ return filter == nullptr && vFilter == nullptr;
+}
+
+StatusOr PushLimitDownScanAppendVerticesRule::transform(
+ OptContext *octx, const MatchedResult &matched) const {
+ auto limitGroupNode = matched.node;
+ auto appendVerticesGroupNode = matched.dependencies.front().node;
+ auto scanVerticesGroupNode = matched.dependencies.front().dependencies.front().node;
+
+ const auto limit = static_cast(limitGroupNode->node());
+ const auto appendVertices = static_cast(appendVerticesGroupNode->node());
+ const auto scanVertices = static_cast(scanVerticesGroupNode->node());
+
+ int64_t limitRows = limit->offset() + limit->count();
+ if (scanVertices->limit() >= 0 && limitRows >= scanVertices->limit()) {
+ return TransformResult::noTransform();
+ }
+
+ auto newLimit = static_cast(limit->clone());
+ auto newLimitGroupNode = OptGroupNode::create(octx, newLimit, limitGroupNode->group());
+
+ auto newAppendVertices = static_cast(appendVertices->clone());
+ auto newAppendVerticesGroup = OptGroup::create(octx);
+ auto newAppendVerticesGroupNode = newAppendVerticesGroup->makeGroupNode(newAppendVertices);
+
+ auto newScanVertices = static_cast(scanVertices->clone());
+ newScanVertices->setLimit(limitRows);
+ auto newScanVerticesGroup = OptGroup::create(octx);
+ auto newScanVerticesGroupNode = newScanVerticesGroup->makeGroupNode(newScanVertices);
+
+ newLimitGroupNode->dependsOn(newAppendVerticesGroup);
+ newAppendVerticesGroupNode->dependsOn(newScanVerticesGroup);
+ for (auto dep : scanVerticesGroupNode->dependencies()) {
+ newScanVerticesGroupNode->dependsOn(dep);
+ }
+
+ TransformResult result;
+ result.eraseAll = true;
+ result.newGroupNodes.emplace_back(newLimitGroupNode);
+ return result;
+}
+
+std::string PushLimitDownScanAppendVerticesRule::toString() const {
+ return "PushLimitDownScanAppendVerticesRule";
+}
+
+} // namespace opt
+} // namespace nebula
diff --git a/src/graph/optimizer/rule/PushLimitDownScanAppendVerticesRule.h b/src/graph/optimizer/rule/PushLimitDownScanAppendVerticesRule.h
new file mode 100644
index 00000000000..6652da49d5e
--- /dev/null
+++ b/src/graph/optimizer/rule/PushLimitDownScanAppendVerticesRule.h
@@ -0,0 +1,30 @@
+/* Copyright (c) 2021 vesoft inc. All rights reserved.
+ *
+ * This source code is licensed under Apache 2.0 License.
+ */
+
+#pragma once
+
+#include "graph/optimizer/OptRule.h"
+
+namespace nebula {
+namespace opt {
+
+class PushLimitDownScanAppendVerticesRule final : public OptRule {
+ public:
+ const Pattern &pattern() const override;
+
+ bool match(OptContext *ctx, const MatchedResult &matched) const override;
+ StatusOr transform(OptContext *ctx,
+ const MatchedResult &matched) const override;
+
+ std::string toString() const override;
+
+ private:
+ PushLimitDownScanAppendVerticesRule();
+
+ static std::unique_ptr kInstance;
+};
+
+} // namespace opt
+} // namespace nebula
diff --git a/src/graph/optimizer/rule/PushLimitDownScanEdgesAppendVerticesRule.cpp b/src/graph/optimizer/rule/PushLimitDownScanEdgesAppendVerticesRule.cpp
new file mode 100644
index 00000000000..4f249ab001d
--- /dev/null
+++ b/src/graph/optimizer/rule/PushLimitDownScanEdgesAppendVerticesRule.cpp
@@ -0,0 +1,113 @@
+/* Copyright (c) 2021 vesoft inc. All rights reserved.
+ *
+ * This source code is licensed under Apache 2.0 License.
+ */
+
+#include "graph/optimizer/rule/PushLimitDownScanEdgesAppendVerticesRule.h"
+
+#include "graph/optimizer/OptContext.h"
+#include "graph/optimizer/OptGroup.h"
+#include "graph/planner/plan/PlanNode.h"
+#include "graph/planner/plan/Query.h"
+#include "graph/util/ExpressionUtils.h"
+
+using nebula::graph::AppendVertices;
+using nebula::graph::Limit;
+using nebula::graph::PlanNode;
+using nebula::graph::Project;
+using nebula::graph::QueryContext;
+using nebula::graph::ScanEdges;
+
+namespace nebula {
+namespace opt {
+
+// Limit->AppendVertices->Project->ScanEdges ==> Limit->AppendVertices->Project->ScanEdges(Limit)
+
+std::unique_ptr PushLimitDownScanEdgesAppendVerticesRule::kInstance =
+ std::unique_ptr(
+ new PushLimitDownScanEdgesAppendVerticesRule());
+
+PushLimitDownScanEdgesAppendVerticesRule::PushLimitDownScanEdgesAppendVerticesRule() {
+ RuleSet::QueryRules().addRule(this);
+}
+
+const Pattern &PushLimitDownScanEdgesAppendVerticesRule::pattern() const {
+ static Pattern pattern = Pattern::create(
+ graph::PlanNode::Kind::kLimit,
+ {Pattern::create(graph::PlanNode::Kind::kAppendVertices,
+ {Pattern::create(graph::PlanNode::Kind::kProject,
+ {Pattern::create(graph::PlanNode::Kind::kScanEdges)})})});
+ return pattern;
+}
+
+bool PushLimitDownScanEdgesAppendVerticesRule::match(OptContext *ctx,
+ const MatchedResult &matched) const {
+ if (!OptRule::match(ctx, matched)) {
+ return false;
+ }
+ auto av = static_cast(matched.planNode({0, 0}));
+ auto *src = av->src();
+ auto *inputExpr = graph::ExpressionUtils::findAny(
+ src, {Expression::Kind::kInputProperty, Expression::Kind::kVarProperty});
+ if (inputExpr == nullptr) {
+ return false;
+ }
+ auto *filter = av->filter();
+ auto *vFilter = av->vFilter();
+ // Limit can't push over filter operation
+ return filter == nullptr && vFilter == nullptr;
+}
+
+StatusOr PushLimitDownScanEdgesAppendVerticesRule::transform(
+ OptContext *octx, const MatchedResult &matched) const {
+ auto limitGroupNode = matched.node;
+ auto appendVerticesGroupNode = matched.dependencies.front().node;
+ auto projGroupNode = matched.dependencies.front().dependencies.front().node;
+ auto scanEdgesGroupNode =
+ matched.dependencies.front().dependencies.front().dependencies.front().node;
+
+ const auto limit = static_cast(limitGroupNode->node());
+ const auto appendVertices = static_cast(appendVerticesGroupNode->node());
+ const auto proj = static_cast(projGroupNode->node());
+ const auto scanEdges = static_cast(scanEdgesGroupNode->node());
+
+ int64_t limitRows = limit->offset() + limit->count();
+ if (scanEdges->limit() >= 0 && limitRows >= scanEdges->limit()) {
+ return TransformResult::noTransform();
+ }
+
+ auto newLimit = static_cast(limit->clone());
+ auto newLimitGroupNode = OptGroupNode::create(octx, newLimit, limitGroupNode->group());
+
+ auto newAppendVertices = static_cast(appendVertices->clone());
+ auto newAppendVerticesGroup = OptGroup::create(octx);
+ auto newAppendVerticesGroupNode = newAppendVerticesGroup->makeGroupNode(newAppendVertices);
+
+ auto newProj = static_cast(proj->clone());
+ auto newProjGroup = OptGroup::create(octx);
+ auto newProjGroupNode = newProjGroup->makeGroupNode(newProj);
+
+ auto newScanEdges = static_cast(scanEdges->clone());
+ newScanEdges->setLimit(limitRows);
+ auto newScanEdgesGroup = OptGroup::create(octx);
+ auto newScanEdgesGroupNode = newScanEdgesGroup->makeGroupNode(newScanEdges);
+
+ newLimitGroupNode->dependsOn(newAppendVerticesGroup);
+ newAppendVerticesGroupNode->dependsOn(newProjGroup);
+ newProjGroupNode->dependsOn(newScanEdgesGroup);
+ for (auto dep : scanEdgesGroupNode->dependencies()) {
+ newScanEdgesGroupNode->dependsOn(dep);
+ }
+
+ TransformResult result;
+ result.eraseAll = true;
+ result.newGroupNodes.emplace_back(newLimitGroupNode);
+ return result;
+}
+
+std::string PushLimitDownScanEdgesAppendVerticesRule::toString() const {
+ return "PushLimitDownScanEdgesAppendVerticesRule";
+}
+
+} // namespace opt
+} // namespace nebula
diff --git a/src/graph/optimizer/rule/PushLimitDownScanEdgesAppendVerticesRule.h b/src/graph/optimizer/rule/PushLimitDownScanEdgesAppendVerticesRule.h
new file mode 100644
index 00000000000..38c079bc660
--- /dev/null
+++ b/src/graph/optimizer/rule/PushLimitDownScanEdgesAppendVerticesRule.h
@@ -0,0 +1,30 @@
+/* Copyright (c) 2021 vesoft inc. All rights reserved.
+ *
+ * This source code is licensed under Apache 2.0 License.
+ */
+
+#pragma once
+
+#include "graph/optimizer/OptRule.h"
+
+namespace nebula {
+namespace opt {
+
+class PushLimitDownScanEdgesAppendVerticesRule final : public OptRule {
+ public:
+ const Pattern &pattern() const override;
+
+ bool match(OptContext *ctx, const MatchedResult &matched) const override;
+ StatusOr transform(OptContext *ctx,
+ const MatchedResult &matched) const override;
+
+ std::string toString() const override;
+
+ private:
+ PushLimitDownScanEdgesAppendVerticesRule();
+
+ static std::unique_ptr kInstance;
+};
+
+} // namespace opt
+} // namespace nebula
diff --git a/src/graph/optimizer/rule/PushVFilterDownScanVerticesRule.cpp b/src/graph/optimizer/rule/PushVFilterDownScanVerticesRule.cpp
new file mode 100644
index 00000000000..c335bc38238
--- /dev/null
+++ b/src/graph/optimizer/rule/PushVFilterDownScanVerticesRule.cpp
@@ -0,0 +1,126 @@
+/* Copyright (c) 2021 vesoft inc. All rights reserved.
+ *
+ * This source code is licensed under Apache 2.0 License.
+ */
+
+#include "graph/optimizer/rule/PushVFilterDownScanVerticesRule.h"
+
+#include "common/expression/Expression.h"
+#include "common/expression/LogicalExpression.h"
+#include "common/expression/PropertyExpression.h"
+#include "common/expression/UnaryExpression.h"
+#include "graph/optimizer/OptContext.h"
+#include "graph/optimizer/OptGroup.h"
+#include "graph/planner/plan/PlanNode.h"
+#include "graph/planner/plan/Query.h"
+#include "graph/util/ExpressionUtils.h"
+#include "graph/visitor/ExtractFilterExprVisitor.h"
+
+using nebula::Expression;
+using nebula::graph::AppendVertices;
+using nebula::graph::PlanNode;
+using nebula::graph::QueryContext;
+using nebula::graph::ScanVertices;
+
+namespace nebula {
+namespace opt {
+
+std::unique_ptr PushVFilterDownScanVerticesRule::kInstance =
+ std::unique_ptr(new PushVFilterDownScanVerticesRule());
+
+PushVFilterDownScanVerticesRule::PushVFilterDownScanVerticesRule() {
+ RuleSet::QueryRules().addRule(this);
+}
+
+const Pattern &PushVFilterDownScanVerticesRule::pattern() const {
+ static Pattern pattern = Pattern::create(PlanNode::Kind::kAppendVertices,
+ {Pattern::create(PlanNode::Kind::kScanVertices)});
+ return pattern;
+}
+
+// AppendVertices is the leaf node to fetch data from storage, so Filter can't push over it
+// normally.
+// But in this case, if AppendVertices get vId from ScanVertices, it can be pushed down.
+bool PushVFilterDownScanVerticesRule::match(OptContext *ctx, const MatchedResult &matched) const {
+ if (!OptRule::match(ctx, matched)) {
+ return false;
+ }
+ auto appendVerticesGroupNode = matched.node;
+ auto appendVertices = static_cast(appendVerticesGroupNode->node());
+ auto *src = appendVertices->src();
+ if (src->kind() != Expression::Kind::kInputProperty &&
+ src->kind() != Expression::Kind::kVarProperty) {
+ return false;
+ }
+ auto *propExpr = static_cast(src);
+ if (propExpr->prop() != kVid) {
+ return false;
+ }
+ if (appendVertices->vFilter() == nullptr) {
+ return false;
+ }
+ auto tagPropExprs = graph::ExpressionUtils::collectAll(appendVertices->vFilter(),
+ {Expression::Kind::kTagProperty});
+ for (const auto &tagPropExpr : tagPropExprs) {
+ auto tagProp = static_cast(tagPropExpr);
+ if (tagProp->sym() == "*") {
+ return false;
+ }
+ }
+ return true;
+}
+
+StatusOr PushVFilterDownScanVerticesRule::transform(
+ OptContext *ctx, const MatchedResult &matched) const {
+ auto appendVerticesGroupNode = matched.node;
+ auto svGroupNode = matched.dependencies.front().node;
+ auto appendVertices = static_cast(appendVerticesGroupNode->node());
+ auto sv = static_cast(svGroupNode->node());
+ auto qctx = ctx->qctx();
+ auto pool = qctx->objPool();
+ auto condition = appendVertices->vFilter()->clone();
+
+ auto visitor = graph::ExtractFilterExprVisitor::makePushGetVertices(pool);
+ condition->accept(&visitor);
+ if (!visitor.ok()) {
+ return TransformResult::noTransform();
+ }
+
+ auto remainedExpr = std::move(visitor).remainedExpr();
+ OptGroupNode *newAppendVerticesGroupNode = nullptr;
+ auto newAppendVertices = appendVertices->clone();
+ newAppendVertices->setVertexFilter(remainedExpr);
+ newAppendVertices->setOutputVar(appendVertices->outputVar());
+ newAppendVertices->setInputVar(appendVertices->inputVar());
+ newAppendVerticesGroupNode =
+ OptGroupNode::create(ctx, newAppendVertices, appendVerticesGroupNode->group());
+
+ auto newSVFilter = condition;
+ if (sv->filter() != nullptr) {
+ auto logicExpr = LogicalExpression::makeAnd(pool, condition, sv->filter()->clone());
+ newSVFilter = logicExpr;
+ }
+
+ auto newSV = static_cast(sv->clone());
+ newSV->setFilter(newSVFilter);
+
+ auto newGroup = OptGroup::create(ctx);
+ auto newSvGroupNode = newGroup->makeGroupNode(newSV);
+ newAppendVerticesGroupNode->dependsOn(newGroup);
+
+ for (auto dep : svGroupNode->dependencies()) {
+ newSvGroupNode->dependsOn(dep);
+ }
+
+ TransformResult result;
+ result.eraseCurr = true;
+ result.newGroupNodes.emplace_back(newAppendVerticesGroupNode);
+ return result;
+}
+
+std::string PushVFilterDownScanVerticesRule::toString() const {
+ return "PushVFilterDownScanVerticesRule";
+}
+
+} // namespace opt
+} // namespace nebula
diff --git a/src/graph/optimizer/rule/PushVFilterDownScanVerticesRule.h b/src/graph/optimizer/rule/PushVFilterDownScanVerticesRule.h
new file mode 100644
index 00000000000..098bf752636
--- /dev/null
+++ b/src/graph/optimizer/rule/PushVFilterDownScanVerticesRule.h
@@ -0,0 +1,31 @@
+/* Copyright (c) 2021 vesoft inc. All rights reserved.
+ *
+ * This source code is licensed under Apache 2.0 License.
+ */
+
+#pragma once
+
+#include
+
+#include "graph/optimizer/OptRule.h"
+
+namespace nebula {
+namespace opt {
+
+class PushVFilterDownScanVerticesRule final : public OptRule {
+ public:
+ const Pattern &pattern() const override;
+
+ bool match(OptContext *ctx, const MatchedResult &matched) const override;
+ StatusOr transform(OptContext *ctx, const MatchedResult &matched) const override;
+
+ std::string toString() const override;
+
+ private:
+ PushVFilterDownScanVerticesRule();
+
+ static std::unique_ptr kInstance;
+};
+
+} // namespace opt
+} // namespace nebula
diff --git a/src/graph/planner/CMakeLists.txt b/src/graph/planner/CMakeLists.txt
index c3d487105ed..bbd2aeda4e0 100644
--- a/src/graph/planner/CMakeLists.txt
+++ b/src/graph/planner/CMakeLists.txt
@@ -26,6 +26,7 @@ nebula_add_library(
match/PropIndexSeek.cpp
match/VertexIdSeek.cpp
match/LabelIndexSeek.cpp
+ match/ScanSeek.cpp
ngql/PathPlanner.cpp
ngql/GoPlanner.cpp
ngql/SubgraphPlanner.cpp
diff --git a/src/graph/planner/PlannersRegister.cpp b/src/graph/planner/PlannersRegister.cpp
index e682ad243b8..f7067b5c7ac 100644
--- a/src/graph/planner/PlannersRegister.cpp
+++ b/src/graph/planner/PlannersRegister.cpp
@@ -10,6 +10,7 @@
#include "graph/planner/match/LabelIndexSeek.h"
#include "graph/planner/match/MatchPlanner.h"
#include "graph/planner/match/PropIndexSeek.h"
+#include "graph/planner/match/ScanSeek.h"
#include "graph/planner/match/StartVidFinder.h"
#include "graph/planner/match/VertexIdSeek.h"
#include "graph/planner/ngql/FetchEdgesPlanner.h"
@@ -97,6 +98,11 @@ void PlannersRegister::registerMatch() {
// MATCH(n: tag) RETURN n
// MATCH(s)-[:edge]->(e) RETURN e
startVidFinders.emplace_back(&LabelIndexSeek::make);
+
+ // Scan the start vertex directly
+ // Now we hard code the order of match rules before CBO,
+ // put scan rule at the last for we assume it's most inefficient
+ startVidFinders.emplace_back(&ScanSeek::make);
}
} // namespace graph
diff --git a/src/graph/planner/match/ScanSeek.cpp b/src/graph/planner/match/ScanSeek.cpp
new file mode 100644
index 00000000000..b4874039c27
--- /dev/null
+++ b/src/graph/planner/match/ScanSeek.cpp
@@ -0,0 +1,103 @@
+/* Copyright (c) 2021 vesoft inc. All rights reserved.
+ *
+ * This source code is licensed under Apache 2.0 License.
+ */
+
+#include "graph/planner/match/ScanSeek.h"
+
+#include
+
+#include "graph/planner/match/MatchSolver.h"
+#include "graph/planner/plan/Query.h"
+#include "graph/util/ExpressionUtils.h"
+#include "graph/util/SchemaUtil.h"
+
+namespace nebula {
+namespace graph {
+
+bool ScanSeek::matchEdge(EdgeContext *edgeCtx) {
+ UNUSED(edgeCtx);
+ return false;
+}
+
+StatusOr ScanSeek::transformEdge(EdgeContext *edgeCtx) {
+ UNUSED(edgeCtx);
+ return Status::Error("Unimplemented for edge pattern.");
+}
+
+bool ScanSeek::matchNode(NodeContext *nodeCtx) {
+ auto &node = *nodeCtx->info;
+ // only require the tag
+ if (node.tids.size() == 0) {
+ // empty labels means all labels
+ const auto *qctx = nodeCtx->matchClauseCtx->qctx;
+ auto allLabels = qctx->schemaMng()->getAllTags(nodeCtx->matchClauseCtx->space.id);
+ if (!allLabels.ok()) {
+ return false;
+ }
+ for (const auto &label : allLabels.value()) {
+ nodeCtx->scanInfo.schemaIds.emplace_back(label.first);
+ nodeCtx->scanInfo.schemaNames.emplace_back(label.second);
+ }
+ nodeCtx->scanInfo.anyLabel = true;
+ } else {
+ for (std::size_t i = 0; i < node.tids.size(); i++) {
+ auto tagId = node.tids[i];
+ auto tagName = node.labels[i];
+ nodeCtx->scanInfo.schemaIds.emplace_back(tagId);
+ nodeCtx->scanInfo.schemaNames.emplace_back(tagName);
+ }
+ }
+ return true;
+}
+
+StatusOr ScanSeek::transformNode(NodeContext *nodeCtx) {
+ SubPlan plan;
+ auto *matchClauseCtx = nodeCtx->matchClauseCtx;
+ auto *qctx = matchClauseCtx->qctx;
+ auto *pool = qctx->objPool();
+ auto anyLabel = nodeCtx->scanInfo.anyLabel;
+
+ auto vProps = std::make_unique>();
+ std::vector colNames{kVid};
+ for (std::size_t i = 0; i < nodeCtx->scanInfo.schemaIds.size(); ++i) {
+ storage::cpp2::VertexProp vProp;
+ std::vector props{kTag};
+ vProp.set_tag(nodeCtx->scanInfo.schemaIds[i]);
+ vProp.set_props(std::move(props));
+ vProps->emplace_back(std::move(vProp));
+ colNames.emplace_back(nodeCtx->scanInfo.schemaNames[i] + "." + kTag);
+ }
+
+ auto *scanVertices =
+ ScanVertices::make(qctx, nullptr, matchClauseCtx->space.id, std::move(vProps));
+ scanVertices->setColNames(std::move(colNames));
+ plan.root = scanVertices;
+ plan.tail = scanVertices;
+
+ // Filter vertices lack labels
+ Expression *prev = nullptr;
+ for (const auto &tag : nodeCtx->scanInfo.schemaNames) {
+ auto *tagPropExpr = TagPropertyExpression::make(pool, tag, kTag);
+ auto *notEmpty = UnaryExpression::makeIsNotEmpty(pool, tagPropExpr);
+ if (prev != nullptr) {
+ if (anyLabel) {
+ auto *orExpr = LogicalExpression::makeOr(pool, prev, notEmpty);
+ prev = orExpr;
+ } else {
+ auto *andExpr = LogicalExpression::makeAnd(pool, prev, notEmpty);
+ prev = andExpr;
+ }
+ } else {
+ prev = notEmpty;
+ }
+ }
+ auto *filter = Filter::make(qctx, scanVertices, prev);
+ plan.root = filter;
+
+ nodeCtx->initialExpr = InputPropertyExpression::make(pool, kVid);
+ return plan;
+}
+
+} // namespace graph
+} // namespace nebula
diff --git a/src/graph/planner/match/ScanSeek.h b/src/graph/planner/match/ScanSeek.h
new file mode 100644
index 00000000000..a323723e751
--- /dev/null
+++ b/src/graph/planner/match/ScanSeek.h
@@ -0,0 +1,33 @@
+/* Copyright (c) 2021 vesoft inc. All rights reserved.
+ *
+ * This source code is licensed under Apache 2.0 License.
+ */
+
+#pragma once
+
+#include "graph/context/ast/CypherAstContext.h"
+#include "graph/planner/match/StartVidFinder.h"
+
+namespace nebula {
+namespace graph {
+/*
+ * The ScanSeek was designed to find if could get the starting vids in
+ * filter.
+ */
+class ScanSeek final : public StartVidFinder {
+ public:
+ static std::unique_ptr make() { return std::unique_ptr(new ScanSeek()); }
+
+ bool matchNode(NodeContext* nodeCtx) override;
+
+ bool matchEdge(EdgeContext* edgeCtx) override;
+
+ StatusOr transformNode(NodeContext* nodeCtx) override;
+
+ StatusOr transformEdge(EdgeContext* edgeCtx) override;
+
+ private:
+ ScanSeek() = default;
+};
+} // namespace graph
+} // namespace nebula
diff --git a/src/graph/planner/plan/PlanNode.cpp b/src/graph/planner/plan/PlanNode.cpp
index b1a43a2836c..a1629320279 100644
--- a/src/graph/planner/plan/PlanNode.cpp
+++ b/src/graph/planner/plan/PlanNode.cpp
@@ -55,6 +55,10 @@ const char* PlanNode::toString(PlanNode::Kind kind) {
return "EdgeIndexRangeScan";
case Kind::kEdgeIndexPrefixScan:
return "EdgeIndexPrefixScan";
+ case Kind::kScanVertices:
+ return "ScanVertices";
+ case Kind::kScanEdges:
+ return "ScanEdges";
case Kind::kFilter:
return "Filter";
case Kind::kUnion:
diff --git a/src/graph/planner/plan/PlanNode.h b/src/graph/planner/plan/PlanNode.h
index 053272ce01b..bac87810a32 100644
--- a/src/graph/planner/plan/PlanNode.h
+++ b/src/graph/planner/plan/PlanNode.h
@@ -39,6 +39,8 @@ class PlanNode {
kEdgeIndexFullScan,
kEdgeIndexPrefixScan,
kEdgeIndexRangeScan,
+ kScanVertices,
+ kScanEdges,
// ------------------
kFilter,
kUnion,
diff --git a/src/graph/planner/plan/Query.cpp b/src/graph/planner/plan/Query.cpp
index b45c4bb88a6..48b5c05a356 100644
--- a/src/graph/planner/plan/Query.cpp
+++ b/src/graph/planner/plan/Query.cpp
@@ -194,6 +194,64 @@ void IndexScan::cloneMembers(const IndexScan& g) {
isEmptyResultSet_ = g.isEmptyResultSet();
}
+std::unique_ptr ScanVertices::explain() const {
+ auto desc = Explore::explain();
+ addDescription("props", props_ ? folly::toJson(util::toJson(*props_)) : "", desc.get());
+ addDescription("exprs", exprs_ ? folly::toJson(util::toJson(*exprs_)) : "", desc.get());
+ return desc;
+}
+
+PlanNode* ScanVertices::clone() const {
+ auto* newGV = ScanVertices::make(qctx_, nullptr, space_);
+ newGV->cloneMembers(*this);
+ return newGV;
+}
+
+void ScanVertices::cloneMembers(const ScanVertices& gv) {
+ Explore::cloneMembers(gv);
+
+ if (gv.props_) {
+ auto vertexProps = *gv.props_;
+ auto vertexPropsPtr = std::make_unique(std::move(vertexProps));
+ setVertexProps(std::move(vertexPropsPtr));
+ }
+
+ if (gv.exprs_) {
+ auto exprs = *gv.exprs_;
+ auto exprsPtr = std::make_unique(std::move(exprs));
+ setExprs(std::move(exprsPtr));
+ }
+}
+
+std::unique_ptr ScanEdges::explain() const {
+ auto desc = Explore::explain();
+ addDescription("props", props_ ? folly::toJson(util::toJson(*props_)) : "", desc.get());
+ addDescription("exprs", exprs_ ? folly::toJson(util::toJson(*exprs_)) : "", desc.get());
+ return desc;
+}
+
+PlanNode* ScanEdges::clone() const {
+ auto* newGE = ScanEdges::make(qctx_, nullptr, space_);
+ newGE->cloneMembers(*this);
+ return newGE;
+}
+
+void ScanEdges::cloneMembers(const ScanEdges& ge) {
+ Explore::cloneMembers(ge);
+
+ if (ge.props_) {
+ auto edgeProps = *ge.props_;
+ auto edgePropsPtr = std::make_unique(std::move(edgeProps));
+ setEdgeProps(std::move(edgePropsPtr));
+ }
+
+ if (ge.exprs_) {
+ auto exprs = *ge.exprs_;
+ auto exprsPtr = std::make_unique(std::move(exprs));
+ setExprs(std::move(exprsPtr));
+ }
+}
+
Filter::Filter(QueryContext* qctx, PlanNode* input, Expression* condition, bool needStableFilter)
: SingleInputNode(qctx, Kind::kFilter, input) {
condition_ = condition;
@@ -664,7 +722,11 @@ AppendVertices* AppendVertices::clone() const {
void AppendVertices::cloneMembers(const AppendVertices& a) {
GetVertices::cloneMembers(a);
- setVertexFilter(a.vFilter_->clone());
+ if (a.vFilter_ != nullptr) {
+ setVertexFilter(a.vFilter_->clone());
+ } else {
+ setVertexFilter(nullptr);
+ }
}
std::unique_ptr AppendVertices::explain() const {
diff --git a/src/graph/planner/plan/Query.h b/src/graph/planner/plan/Query.h
index 076b7471b53..d4e20562216 100644
--- a/src/graph/planner/plan/Query.h
+++ b/src/graph/planner/plan/Query.h
@@ -464,6 +464,124 @@ class IndexScan : public Explore {
bool isEmptyResultSet_{false};
};
+/**
+ * Scan vertices
+ */
+class ScanVertices final : public Explore {
+ public:
+ static ScanVertices* make(QueryContext* qctx,
+ PlanNode* input,
+ GraphSpaceID space,
+ std::unique_ptr>&& props = nullptr,
+ std::unique_ptr>&& exprs = nullptr,
+ bool dedup = false,
+ std::vector orderBy = {},
+ int64_t limit = -1,
+ Expression* filter = nullptr) {
+ return qctx->objPool()->add(new ScanVertices(qctx,
+ input,
+ space,
+ std::move(props),
+ std::move(exprs),
+ dedup,
+ std::move(orderBy),
+ limit,
+ filter));
+ }
+
+ const std::vector* props() const { return props_.get(); }
+
+ const std::vector* exprs() const { return exprs_.get(); }
+
+ void setVertexProps(std::unique_ptr> props) { props_ = std::move(props); }
+
+ void setExprs(std::unique_ptr> exprs) { exprs_ = std::move(exprs); }
+
+ PlanNode* clone() const override;
+ std::unique_ptr explain() const override;
+
+ private:
+ ScanVertices(QueryContext* qctx,
+ PlanNode* input,
+ GraphSpaceID space,
+ std::unique_ptr>&& props,
+ std::unique_ptr>&& exprs,
+ bool dedup,
+ std::vector orderBy,
+ int64_t limit,
+ Expression* filter)
+ : Explore(qctx, Kind::kScanVertices, input, space, dedup, limit, filter, std::move(orderBy)),
+ props_(std::move(props)),
+ exprs_(std::move(exprs)) {}
+
+ void cloneMembers(const ScanVertices&);
+
+ private:
+ // props of the vertex
+ std::unique_ptr> props_;
+ // expression to get
+ std::unique_ptr> exprs_;
+};
+
+/**
+ * Scan edges
+ */
+class ScanEdges final : public Explore {
+ public:
+ static ScanEdges* make(QueryContext* qctx,
+ PlanNode* input,
+ GraphSpaceID space,
+ std::unique_ptr>&& props = nullptr,
+ std::unique_ptr>&& exprs = nullptr,
+ bool dedup = false,
+ int64_t limit = -1,
+ std::vector orderBy = {},
+ Expression* filter = nullptr) {
+ return qctx->objPool()->add(new ScanEdges(qctx,
+ input,
+ space,
+ std::move(props),
+ std::move(exprs),
+ dedup,
+ limit,
+ std::move(orderBy),
+ filter));
+ }
+
+ const std::vector* props() const { return props_.get(); }
+
+ const std::vector* exprs() const { return exprs_.get(); }
+
+ void setEdgeProps(std::unique_ptr> props) { props_ = std::move(props); }
+
+ void setExprs(std::unique_ptr> exprs) { exprs_ = std::move(exprs); }
+
+ PlanNode* clone() const override;
+ std::unique_ptr explain() const override;
+
+ private:
+ ScanEdges(QueryContext* qctx,
+ PlanNode* input,
+ GraphSpaceID space,
+ std::unique_ptr>&& props,
+ std::unique_ptr>&& exprs,
+ bool dedup,
+ int64_t limit,
+ std::vector orderBy,
+ Expression* filter)
+ : Explore(qctx, Kind::kScanEdges, input, space, dedup, limit, filter, std::move(orderBy)),
+ props_(std::move(props)),
+ exprs_(std::move(exprs)) {}
+
+ void cloneMembers(const ScanEdges&);
+
+ private:
+ // props of edge to get
+ std::unique_ptr> props_;
+ // expression to show
+ std::unique_ptr> exprs_;
+};
+
/**
* A Filter node helps filt some records with condition.
*/
diff --git a/src/graph/validator/GoValidator.cpp b/src/graph/validator/GoValidator.cpp
index 1866f66e9a8..03da2cede0d 100644
--- a/src/graph/validator/GoValidator.cpp
+++ b/src/graph/validator/GoValidator.cpp
@@ -36,9 +36,23 @@ Status GoValidator::validateImpl() {
return Status::SemanticError("$- must be referred in FROM before used in WHERE or YIELD");
}
- if (!exprProps.varProps().empty() && goCtx_->from.fromType != kVariable) {
- return Status::SemanticError(
- "A variable must be referred in FROM before used in WHERE or YIELD");
+ if (!exprProps.varProps().empty()) {
+ if (goCtx_->from.fromType != kVariable) {
+ return Status::SemanticError(
+ "A variable must be referred in FROM before used in WHERE or YIELD");
+ }
+ auto varPropsMap = exprProps.varProps();
+ std::vector keys;
+ for (const auto& [k, v] : varPropsMap) {
+ keys.emplace_back(k);
+ }
+ if (keys.size() > 1) {
+ return Status::SemanticError("Multiple variable property is not supported in WHERE or YIELD");
+ }
+ if (keys.front() != goCtx_->from.userDefinedVarName) {
+ return Status::SemanticError(
+ "A variable must be referred in FROM before used in WHERE or YIELD");
+ }
}
if ((!exprProps.inputProps().empty() && !exprProps.varProps().empty()) ||
diff --git a/src/graph/validator/MatchValidator.cpp b/src/graph/validator/MatchValidator.cpp
index 0327cd3411e..05995643d0e 100644
--- a/src/graph/validator/MatchValidator.cpp
+++ b/src/graph/validator/MatchValidator.cpp
@@ -503,6 +503,10 @@ Status MatchValidator::validateUnwind(const UnwindClause *unwindClause,
}
unwindCtx.alias = unwindClause->alias();
unwindCtx.unwindExpr = unwindClause->expr()->clone();
+ if (ExpressionUtils::hasAny(unwindCtx.unwindExpr, {Expression::Kind::kAggregate})) {
+ return Status::SemanticError("Can't use aggregating expressions in unwind clause, `%s'",
+ unwindCtx.unwindExpr->toString().c_str());
+ }
auto labelExprs = ExpressionUtils::collectAll(unwindCtx.unwindExpr, {Expression::Kind::kLabel});
for (auto *labelExpr : labelExprs) {
diff --git a/src/graph/validator/test/MatchValidatorTest.cpp b/src/graph/validator/test/MatchValidatorTest.cpp
index 481b09fc7fc..715f206f6d6 100644
--- a/src/graph/validator/test/MatchValidatorTest.cpp
+++ b/src/graph/validator/test/MatchValidatorTest.cpp
@@ -46,7 +46,13 @@ TEST_F(MatchValidatorTest, SeekByTagIndex) {
// non index
{
std::string query = "MATCH (v:room) RETURN id(v) AS id;";
- EXPECT_FALSE(validate(query));
+ std::vector expected = {PlanNode::Kind::kProject,
+ PlanNode::Kind::kProject,
+ PlanNode::Kind::kAppendVertices,
+ PlanNode::Kind::kFilter,
+ PlanNode::Kind::kScanVertices,
+ PlanNode::Kind::kStart};
+ EXPECT_TRUE(checkResult(query, expected));
}
}
diff --git a/src/graph/visitor/ExtractFilterExprVisitor.cpp b/src/graph/visitor/ExtractFilterExprVisitor.cpp
index f412b1a50bb..ce97ddd8cba 100644
--- a/src/graph/visitor/ExtractFilterExprVisitor.cpp
+++ b/src/graph/visitor/ExtractFilterExprVisitor.cpp
@@ -20,9 +20,31 @@ void ExtractFilterExprVisitor::visit(VariableExpression *expr) {
void ExtractFilterExprVisitor::visit(VersionedVariableExpression *) { canBePushed_ = false; }
-void ExtractFilterExprVisitor::visit(TagPropertyExpression *) { canBePushed_ = false; }
+void ExtractFilterExprVisitor::visit(TagPropertyExpression *) {
+ switch (pushType_) {
+ case PushType::kGetNeighbors:
+ canBePushed_ = false;
+ break;
+ case PushType::kGetVertices:
+ canBePushed_ = true;
+ break;
+ case PushType::kGetEdges:
+ canBePushed_ = false;
+ break;
+ }
+}
-void ExtractFilterExprVisitor::visit(EdgePropertyExpression *) { canBePushed_ = true; }
+void ExtractFilterExprVisitor::visit(EdgePropertyExpression *) {
+ switch (pushType_) {
+ case PushType::kGetNeighbors:
+ case PushType::kGetEdges:
+ canBePushed_ = true;
+ break;
+ case PushType::kGetVertices:
+ canBePushed_ = false;
+ break;
+ }
+}
void ExtractFilterExprVisitor::visit(InputPropertyExpression *) { canBePushed_ = false; }
@@ -30,15 +52,65 @@ void ExtractFilterExprVisitor::visit(VariablePropertyExpression *) { canBePushed
void ExtractFilterExprVisitor::visit(DestPropertyExpression *) { canBePushed_ = false; }
-void ExtractFilterExprVisitor::visit(SourcePropertyExpression *) { canBePushed_ = true; }
+void ExtractFilterExprVisitor::visit(SourcePropertyExpression *) {
+ switch (pushType_) {
+ case PushType::kGetNeighbors:
+ canBePushed_ = true;
+ break;
+ case PushType::kGetVertices:
+ case PushType::kGetEdges:
+ canBePushed_ = false;
+ break;
+ }
+}
-void ExtractFilterExprVisitor::visit(EdgeSrcIdExpression *) { canBePushed_ = true; }
+void ExtractFilterExprVisitor::visit(EdgeSrcIdExpression *) {
+ switch (pushType_) {
+ case PushType::kGetNeighbors:
+ case PushType::kGetEdges:
+ canBePushed_ = true;
+ break;
+ case PushType::kGetVertices:
+ canBePushed_ = false;
+ break;
+ }
+}
-void ExtractFilterExprVisitor::visit(EdgeTypeExpression *) { canBePushed_ = true; }
+void ExtractFilterExprVisitor::visit(EdgeTypeExpression *) {
+ switch (pushType_) {
+ case PushType::kGetNeighbors:
+ case PushType::kGetEdges:
+ canBePushed_ = true;
+ break;
+ case PushType::kGetVertices:
+ canBePushed_ = false;
+ break;
+ }
+}
-void ExtractFilterExprVisitor::visit(EdgeRankExpression *) { canBePushed_ = true; }
+void ExtractFilterExprVisitor::visit(EdgeRankExpression *) {
+ switch (pushType_) {
+ case PushType::kGetNeighbors:
+ case PushType::kGetEdges:
+ canBePushed_ = true;
+ break;
+ case PushType::kGetVertices:
+ canBePushed_ = false;
+ break;
+ }
+}
-void ExtractFilterExprVisitor::visit(EdgeDstIdExpression *) { canBePushed_ = true; }
+void ExtractFilterExprVisitor::visit(EdgeDstIdExpression *) {
+ switch (pushType_) {
+ case PushType::kGetNeighbors:
+ case PushType::kGetEdges:
+ canBePushed_ = true;
+ break;
+ case PushType::kGetVertices:
+ canBePushed_ = false;
+ break;
+ }
+}
void ExtractFilterExprVisitor::visit(VertexExpression *) { canBePushed_ = false; }
diff --git a/src/graph/visitor/ExtractFilterExprVisitor.h b/src/graph/visitor/ExtractFilterExprVisitor.h
index 33dfe8a7221..33db050234b 100644
--- a/src/graph/visitor/ExtractFilterExprVisitor.h
+++ b/src/graph/visitor/ExtractFilterExprVisitor.h
@@ -20,6 +20,24 @@ class ExtractFilterExprVisitor final : public ExprVisitorImpl {
Expression *remainedExpr() { return remainedExpr_; }
+ static ExtractFilterExprVisitor makePushGetNeighbors(ObjectPool *pool) {
+ ExtractFilterExprVisitor visitor(pool);
+ visitor.pushType_ = PushType::kGetNeighbors;
+ return visitor;
+ }
+
+ static ExtractFilterExprVisitor makePushGetVertices(ObjectPool *pool) {
+ ExtractFilterExprVisitor visitor(pool);
+ visitor.pushType_ = PushType::kGetVertices;
+ return visitor;
+ }
+
+ static ExtractFilterExprVisitor makePushGetEdges(ObjectPool *pool) {
+ ExtractFilterExprVisitor visitor(pool);
+ visitor.pushType_ = PushType::kGetEdges;
+ return visitor;
+ }
+
private:
using ExprVisitorImpl::visit;
@@ -45,9 +63,16 @@ class ExtractFilterExprVisitor final : public ExprVisitorImpl {
void visit(SubscriptRangeExpression *) override;
private:
+ enum class PushType {
+ kGetNeighbors,
+ kGetVertices, // Get/Append/Scan Vertices
+ kGetEdges, // Get/Append/Scan Edges
+ };
+
ObjectPool *pool_;
bool canBePushed_{true};
Expression *remainedExpr_{nullptr};
+ PushType pushType_{PushType::kGetNeighbors};
};
} // namespace graph
diff --git a/src/interface/storage.thrift b/src/interface/storage.thrift
index 98c884b5485..07c56e2b2fa 100644
--- a/src/interface/storage.thrift
+++ b/src/interface/storage.thrift
@@ -39,7 +39,7 @@ struct ResponseCommon {
// Only contains the partition that returns error
1: required list failed_parts,
// Query latency from storage service
- 2: required i32 latency_in_us,
+ 2: required i64 latency_in_us,
3: optional map latency_detail_us,
}
@@ -585,21 +585,11 @@ struct ScanVertexRequest {
10: optional RequestCommon common,
}
-struct ScanVertexResponse {
- 1: required ResponseCommon result,
- // The data will return as a dataset. The format is as follows:
- // Each column represents one property. the column name is in the form of "tag_name.prop_alias"
- // in the same order which specified in VertexProp in request.
- 2: common.DataSet vertex_data,
- 3: map (cpp.template = "std::unordered_map")
- cursors;
-}
-
struct ScanEdgeRequest {
1: common.GraphSpaceID space_id,
2: map (cpp.template = "std::unordered_map")
parts,
- 3: EdgeProp return_columns,
+ 3: list return_columns,
// max row count of edge in this response
4: i64 limit,
// only return data in time range [start_time, end_time)
@@ -614,12 +604,13 @@ struct ScanEdgeRequest {
10: optional RequestCommon common,
}
-struct ScanEdgeResponse {
+struct ScanResponse {
1: required ResponseCommon result,
// The data will return as a dataset. The format is as follows:
- // Each column represents one property. the column name is in the form of "edge_name.prop_alias"
- // in the same order which specified in EdgeProp in requests.
- 2: common.DataSet edge_data,
+ // Each column represents one property. the column name is in the form of "edge/tag_name.prop_alias"
+ // in the same order which specified in VertexProp/EdgeProp in request
+ // Should keep same with result of GetProps
+ 2: optional common.DataSet props,
3: map (cpp.template = "std::unordered_map")
cursors;
}
@@ -679,8 +670,8 @@ service GraphStorageService {
UpdateResponse updateVertex(1: UpdateVertexRequest req);
UpdateResponse updateEdge(1: UpdateEdgeRequest req);
- ScanVertexResponse scanVertex(1: ScanVertexRequest req)
- ScanEdgeResponse scanEdge(1: ScanEdgeRequest req)
+ ScanResponse scanVertex(1: ScanVertexRequest req)
+ ScanResponse scanEdge(1: ScanEdgeRequest req)
GetUUIDResp getUUID(1: GetUUIDReq req);
diff --git a/src/kvstore/raftex/RaftPart.cpp b/src/kvstore/raftex/RaftPart.cpp
index 8dd3aaad96a..bfa99392102 100644
--- a/src/kvstore/raftex/RaftPart.cpp
+++ b/src/kvstore/raftex/RaftPart.cpp
@@ -273,7 +273,7 @@ void RaftPart::start(std::vector&& peers, bool asLearner) {
LOG(INFO) << idStr_ << "Reset lastLogId " << lastLogId_ << " to be the committedLogId "
<< committedLogId_;
lastLogId_ = committedLogId_;
- lastLogTerm_ = term_;
+ lastLogTerm_ = logIdAndTerm.second;
wal_->reset();
}
LOG(INFO) << idStr_ << "There are " << peers.size() << " peer hosts, and total "
@@ -293,6 +293,7 @@ void RaftPart::start(std::vector&& peers, bool asLearner) {
if (asLearner) {
role_ = Role::LEARNER;
}
+ leader_ = HostAddr("", 0);
startTimeMs_ = time::WallClock::fastNowInMilliSec();
// Set up a leader election task
size_t delayMS = 100 + folly::Random::rand32(900);
@@ -852,6 +853,7 @@ void RaftPart::processAppendLogResponses(const AppendLogResponses& resps,
std::lock_guard g(raftLock_);
res = canAppendLogs(currTerm);
if (res != AppendLogResult::SUCCEEDED) {
+ wal_->rollbackToLog(lastLogId_);
break;
}
lastLogId_ = lastLogId;
@@ -996,6 +998,9 @@ bool RaftPart::prepareElectionRequest(cpp2::AskForVoteRequest& req,
req.set_term(term_ + 1);
} else {
req.set_term(++term_);
+ // vote for myself
+ votedAddr_ = addr_;
+ votedTerm_ = term_;
}
req.set_last_log_id(lastLogId_);
req.set_last_log_term(lastLogTerm_);
@@ -1073,6 +1078,7 @@ bool RaftPart::processElectionResponses(const RaftPart::ElectionResponses& resul
LOG(INFO) << idStr_ << "Partition is elected as the new leader for term " << proposedTerm;
term_ = proposedTerm;
role_ = Role::LEADER;
+ leader_ = addr_;
isBlindFollower_ = false;
}
return true;
@@ -1106,6 +1112,7 @@ folly::Future RaftPart::leaderElection(bool isPreVote) {
// So we need to go back to the follower state to avoid the case.
std::lock_guard g(raftLock_);
role_ = Role::FOLLOWER;
+ leader_ = HostAddr("", 0);
inElection_ = false;
return false;
}
@@ -1303,10 +1310,13 @@ void RaftPart::processAskForVoteRequest(const cpp2::AskForVoteRequest& req,
return;
}
+ auto oldRole = role_;
auto oldTerm = term_;
- // req.get_term() >= term_, we won't update term in prevote
- if (!req.get_is_pre_vote()) {
+ if (!req.get_is_pre_vote() && req.get_term() > term_) {
+ // req.get_term() > term_, we won't update term in prevote
term_ = req.get_term();
+ role_ = Role::FOLLOWER;
+ leader_ = HostAddr("", 0);
}
// Check the last term to receive a log
@@ -1345,29 +1355,34 @@ void RaftPart::processAskForVoteRequest(const cpp2::AskForVoteRequest& req,
resp.set_error_code(cpp2::ErrorCode::E_TERM_OUT_OF_DATE);
return;
}
+
+ // Ok, no reason to refuse, we will vote for the candidate
+ LOG(INFO) << idStr_ << "The partition will vote for the candidate " << candidate
+ << ", isPreVote = " << req.get_is_pre_vote();
+
if (req.get_is_pre_vote()) {
// return succeed if it is prevote, do not change any state
resp.set_error_code(cpp2::ErrorCode::SUCCEEDED);
return;
}
- // Ok, no reason to refuse, we will vote for the candidate
- LOG(INFO) << idStr_ << "The partition will vote for the candidate " << candidate
- << ", isPreVote = " << req.get_is_pre_vote();
-
- // Before change role from leader to follower, check the logs locally.
- if (role_ == Role::LEADER && wal_->lastLogId() > lastLogId_) {
+ // not a pre-vote, need to rollback wal if necessary
+ // role_ and term_ has been set above
+ if (oldRole == Role::LEADER && wal_->lastLogId() > lastLogId_) {
LOG(INFO) << idStr_ << "There are some logs up to " << wal_->lastLogId()
<< " i did not commit when i was leader, rollback to " << lastLogId_;
wal_->rollbackToLog(lastLogId_);
}
- if (role_ == Role::LEADER) {
+ if (oldRole == Role::LEADER) {
bgWorkers_->addTask([self = shared_from_this(), oldTerm] { self->onLostLeadership(oldTerm); });
}
+ // not a pre-vote, req.term() >= term_, all check passed, convert to follower
+ term_ = req.get_term();
role_ = Role::FOLLOWER;
+ leader_ = HostAddr("", 0);
votedAddr_ = candidate;
votedTerm_ = req.get_term();
- leader_ = HostAddr("", 0);
+ resp.set_error_code(cpp2::ErrorCode::SUCCEEDED);
// Reset the last message time
lastMsgRecvDur_.reset();
diff --git a/src/kvstore/raftex/test/RaftCase.cpp b/src/kvstore/raftex/test/RaftCase.cpp
index 42855cf0d81..f5f7f6b1eaf 100644
--- a/src/kvstore/raftex/test/RaftCase.cpp
+++ b/src/kvstore/raftex/test/RaftCase.cpp
@@ -62,7 +62,7 @@ TEST_F(ThreeRaftTest, LeaderCrashReboot) {
waitUntilLeaderElected(copies_, leader_);
LOG(INFO) << "=====> Now all copy rejoin, should not disrupt leader";
- rebootOneCopy(services_, copies_, allHosts_, idx);
+ rebootOneCopy(services_, copies_, allHosts_, (idx + 1) % copies_.size());
sleep(FLAGS_raft_heartbeat_interval_secs);
waitUntilAllHasLeader(copies_);
checkLeadership(copies_, leader_);
diff --git a/src/kvstore/test/DiskManagerTest.cpp b/src/kvstore/test/DiskManagerTest.cpp
index df95bb0aa0b..93435537b20 100644
--- a/src/kvstore/test/DiskManagerTest.cpp
+++ b/src/kvstore/test/DiskManagerTest.cpp
@@ -145,6 +145,39 @@ TEST(DiskManagerTest, WalNoSpaceTest) {
}
}
+TEST(DiskManagerTest, GetDiskPartsTest) {
+ GraphSpaceID spaceId = 1;
+ fs::TempDir disk1("/tmp/get_disk_part_test.XXXXXX");
+ auto path1 = folly::stringPrintf("%s/nebula/%d", disk1.path(), spaceId);
+ boost::filesystem::create_directories(path1);
+ fs::TempDir disk2("/tmp/get_disk_part_test.XXXXXX");
+ auto path2 = folly::stringPrintf("%s/nebula/%d", disk2.path(), spaceId);
+ boost::filesystem::create_directories(path2);
+ GraphSpaceID spaceId2 = 2;
+ fs::TempDir disk3("/tmp/get_disk_part_test.XXXXXX");
+ auto path3 = folly::stringPrintf("%s/nebula/%d", disk3.path(), spaceId2);
+ boost::filesystem::create_directories(path3);
+
+ std::vector dataPaths = {disk1.path(), disk2.path(), disk3.path()};
+ DiskManager diskMan(dataPaths);
+ for (PartitionID partId = 1; partId <= 10; partId++) {
+ diskMan.addPartToPath(spaceId, partId, path1);
+ }
+ for (PartitionID partId = 11; partId <= 20; partId++) {
+ diskMan.addPartToPath(spaceId, partId, path2);
+ }
+ for (PartitionID partId = 1; partId <= 10; partId++) {
+ diskMan.addPartToPath(spaceId2, partId, path3);
+ }
+
+ SpaceDiskPartsMap diskParts;
+ diskMan.getDiskParts(diskParts);
+ ASSERT_EQ(2, diskParts.size());
+ ASSERT_EQ(2, diskParts[spaceId].size());
+ ASSERT_EQ(1, diskParts[spaceId2].size());
+ ASSERT_EQ(10, diskParts[spaceId2][path3].get_part_list().size());
+}
+
} // namespace kvstore
} // namespace nebula
diff --git a/src/storage/CompactionFilter.h b/src/storage/CompactionFilter.h
index 1c17b1d3ff4..9041ab47b9d 100644
--- a/src/storage/CompactionFilter.h
+++ b/src/storage/CompactionFilter.h
@@ -31,7 +31,7 @@ class StorageCompactionFilter final : public kvstore::KVFilter {
const folly::StringPiece& key,
const folly::StringPiece& val) const override {
if (NebulaKeyUtils::isTag(vIdLen_, key)) {
- return !vertexValid(spaceId, key, val);
+ return !tagValid(spaceId, key, val);
} else if (NebulaKeyUtils::isEdge(vIdLen_, key)) {
return !edgeValid(spaceId, key, val);
} else if (IndexKeyUtils::isIndexKey(key)) {
@@ -46,9 +46,9 @@ class StorageCompactionFilter final : public kvstore::KVFilter {
}
private:
- bool vertexValid(GraphSpaceID spaceId,
- const folly::StringPiece& key,
- const folly::StringPiece& val) const {
+ bool tagValid(GraphSpaceID spaceId,
+ const folly::StringPiece& key,
+ const folly::StringPiece& val) const {
auto tagId = NebulaKeyUtils::getTagId(vIdLen_, key);
auto schema = schemaMan_->getTagSchema(spaceId, tagId);
if (!schema) {
diff --git a/src/storage/GraphStorageServiceHandler.cpp b/src/storage/GraphStorageServiceHandler.cpp
index 23f9abe18ec..7f7f027af5e 100644
--- a/src/storage/GraphStorageServiceHandler.cpp
+++ b/src/storage/GraphStorageServiceHandler.cpp
@@ -135,13 +135,13 @@ folly::Future GraphStorageServiceHandler::future_lookupIn
RETURN_FUTURE(processor);
}
-folly::Future GraphStorageServiceHandler::future_scanVertex(
+folly::Future GraphStorageServiceHandler::future_scanVertex(
const cpp2::ScanVertexRequest& req) {
auto* processor = ScanVertexProcessor::instance(env_, &kScanVertexCounters, readerPool_.get());
RETURN_FUTURE(processor);
}
-folly::Future GraphStorageServiceHandler::future_scanEdge(
+folly::Future GraphStorageServiceHandler::future_scanEdge(
const cpp2::ScanEdgeRequest& req) {
auto* processor = ScanEdgeProcessor::instance(env_, &kScanEdgeCounters, readerPool_.get());
RETURN_FUTURE(processor);
diff --git a/src/storage/GraphStorageServiceHandler.h b/src/storage/GraphStorageServiceHandler.h
index a28b1509cb1..d4e806d9bc5 100644
--- a/src/storage/GraphStorageServiceHandler.h
+++ b/src/storage/GraphStorageServiceHandler.h
@@ -55,10 +55,9 @@ class GraphStorageServiceHandler final : public cpp2::GraphStorageServiceSvIf {
folly::Future future_chainAddEdges(const cpp2::AddEdgesRequest& req) override;
- folly::Future future_scanVertex(
- const cpp2::ScanVertexRequest& req) override;
+ folly::Future future_scanVertex(const cpp2::ScanVertexRequest& req) override;
- folly::Future future_scanEdge(const cpp2::ScanEdgeRequest& req) override;
+ folly::Future future_scanEdge(const cpp2::ScanEdgeRequest& req) override;
folly::Future future_getUUID(const cpp2::GetUUIDReq& req) override;
diff --git a/src/storage/exec/GetPropNode.h b/src/storage/exec/GetPropNode.h
index bacef34baa5..87b35c7e9b9 100644
--- a/src/storage/exec/GetPropNode.h
+++ b/src/storage/exec/GetPropNode.h
@@ -30,11 +30,19 @@ class GetTagPropNode : public QueryNode {
return ret;
}
- // if none of the tag node valid, do not emplace the row
+ // if none of the tag node and vertex valid, do not emplace the row
if (!std::any_of(tagNodes_.begin(), tagNodes_.end(), [](const auto& tagNode) {
return tagNode->valid();
})) {
- return nebula::cpp2::ErrorCode::SUCCEEDED;
+ auto kvstore = context_->env()->kvstore_;
+ auto vertexKey = NebulaKeyUtils::vertexKey(context_->vIdLen(), partId, vId);
+ std::string value;
+ ret = kvstore->get(context_->spaceId(), partId, vertexKey, &value);
+ if (ret == nebula::cpp2::ErrorCode::E_KEY_NOT_FOUND) {
+ return nebula::cpp2::ErrorCode::SUCCEEDED;
+ } else if (ret != nebula::cpp2::ErrorCode::SUCCEEDED) {
+ return ret;
+ }
}
List row;
diff --git a/src/storage/exec/IndexScanNode.h b/src/storage/exec/IndexScanNode.h
index ac600d641fb..61e8eb7d4b6 100644
--- a/src/storage/exec/IndexScanNode.h
+++ b/src/storage/exec/IndexScanNode.h
@@ -258,7 +258,7 @@ class QualifiedStrategy {
q.func_ = [suffixSet = Set(),
suffixLength = dedupSuffixLength](const folly::StringPiece& key) mutable -> Result {
std::string suffix = key.subpiece(key.size() - suffixLength, suffixLength).toString();
- auto [iter, result] = suffixSet.insert(std::move(suffix));
+ auto result = suffixSet.insert(std::move(suffix)).second;
return result ? Result::COMPATIBLE : Result::INCOMPATIBLE;
};
return q;
diff --git a/src/storage/exec/UpdateNode.h b/src/storage/exec/UpdateNode.h
index 0b6e3abe36d..261cce227a0 100644
--- a/src/storage/exec/UpdateNode.h
+++ b/src/storage/exec/UpdateNode.h
@@ -412,6 +412,7 @@ class UpdateTagNode : public UpdateNode {
}
}
// step 3, insert new vertex data
+ batchHolder->put(NebulaKeyUtils::vertexKey(context_->vIdLen(), partId, vId), "");
batchHolder->put(std::move(key_), std::move(nVal));
return encodeBatchValue(batchHolder->getBatch());
}
diff --git a/src/storage/mutate/AddVerticesProcessor.cpp b/src/storage/mutate/AddVerticesProcessor.cpp
index 2396014d841..055c4309538 100644
--- a/src/storage/mutate/AddVerticesProcessor.cpp
+++ b/src/storage/mutate/AddVerticesProcessor.cpp
@@ -78,7 +78,7 @@ void AddVerticesProcessor::doProcess(const cpp2::AddVerticesRequest& req) {
code = nebula::cpp2::ErrorCode::E_INVALID_VID;
break;
}
-
+ data.emplace_back(NebulaKeyUtils::vertexKey(spaceVidLen_, partId, vid), "");
for (auto& newTag : newTags) {
auto tagId = newTag.get_tag_id();
VLOG(3) << "PartitionID: " << partId << ", VertexID: " << vid << ", TagID: " << tagId;
@@ -155,7 +155,7 @@ void AddVerticesProcessor::doProcessWithIndex(const cpp2::AddVerticesRequest& re
code = nebula::cpp2::ErrorCode::E_INVALID_VID;
break;
}
-
+ batchHolder->put(NebulaKeyUtils::vertexKey(spaceVidLen_, partId, vid), "");
for (auto& newTag : newTags) {
auto tagId = newTag.get_tag_id();
auto l = std::make_tuple(spaceId_, partId, tagId, vid);
diff --git a/src/storage/mutate/DeleteVerticesProcessor.cpp b/src/storage/mutate/DeleteVerticesProcessor.cpp
index e9a6bae1840..570cbb0672a 100644
--- a/src/storage/mutate/DeleteVerticesProcessor.cpp
+++ b/src/storage/mutate/DeleteVerticesProcessor.cpp
@@ -61,7 +61,7 @@ void DeleteVerticesProcessor::process(const cpp2::DeleteVerticesRequest& req) {
code = nebula::cpp2::ErrorCode::E_INVALID_VID;
break;
}
-
+ keys.emplace_back(NebulaKeyUtils::vertexKey(spaceVidLen_, partId, vid.getStr()));
auto prefix = NebulaKeyUtils::tagPrefix(spaceVidLen_, partId, vid.getStr());
std::unique_ptr iter;
code = env_->kvstore_->prefix(spaceId_, partId, prefix, &iter);
@@ -112,6 +112,7 @@ ErrorOr DeleteVerticesProcessor::deleteVer
target.reserve(vertices.size());
std::unique_ptr batchHolder = std::make_unique();
for (auto& vertex : vertices) {
+ batchHolder->remove(NebulaKeyUtils::vertexKey(spaceVidLen_, partId, vertex.getStr()));
auto prefix = NebulaKeyUtils::tagPrefix(spaceVidLen_, partId, vertex.getStr());
std::unique_ptr iter;
auto ret = env_->kvstore_->prefix(spaceId_, partId, prefix, &iter);
diff --git a/src/storage/query/ScanEdgeProcessor.cpp b/src/storage/query/ScanEdgeProcessor.cpp
index 94a6a03a1b2..29b9d3cce33 100644
--- a/src/storage/query/ScanEdgeProcessor.cpp
+++ b/src/storage/query/ScanEdgeProcessor.cpp
@@ -25,7 +25,8 @@ void ScanEdgeProcessor::process(const cpp2::ScanEdgeRequest& req) {
void ScanEdgeProcessor::doProcess(const cpp2::ScanEdgeRequest& req) {
spaceId_ = req.get_space_id();
enableReadFollower_ = req.get_enable_read_from_follower();
- limit_ = req.get_limit();
+ // Negative means no limit
+ limit_ = req.get_limit() < 0 ? std::numeric_limits::max() : req.get_limit();
auto retCode = getSpaceVidLen(spaceId_);
if (retCode != nebula::cpp2::ErrorCode::SUCCEEDED) {
@@ -61,7 +62,7 @@ nebula::cpp2::ErrorCode ScanEdgeProcessor::checkAndBuildContexts(const cpp2::Sca
return ret;
}
- std::vector returnProps = {*req.return_columns_ref()};
+ std::vector returnProps = *req.return_columns_ref();
ret = handleEdgeProps(returnProps);
buildEdgeColName(returnProps);
ret = buildFilter(req, [](const cpp2::ScanEdgeRequest& r) -> const std::string* {
@@ -85,7 +86,7 @@ void ScanEdgeProcessor::buildEdgeColName(const std::vector& edge
}
void ScanEdgeProcessor::onProcessFinished() {
- resp_.set_edge_data(std::move(resultDataSet_));
+ resp_.set_props(std::move(resultDataSet_));
resp_.set_cursors(std::move(cursors_));
}
diff --git a/src/storage/query/ScanEdgeProcessor.h b/src/storage/query/ScanEdgeProcessor.h
index 40c5186975d..f3798cabc83 100644
--- a/src/storage/query/ScanEdgeProcessor.h
+++ b/src/storage/query/ScanEdgeProcessor.h
@@ -16,7 +16,7 @@ namespace storage {
extern ProcessorCounters kScanEdgeCounters;
-class ScanEdgeProcessor : public QueryBaseProcessor {
+class ScanEdgeProcessor : public QueryBaseProcessor {
public:
static ScanEdgeProcessor* instance(StorageEnv* env,
const ProcessorCounters* counters = &kScanEdgeCounters,
@@ -30,8 +30,7 @@ class ScanEdgeProcessor : public QueryBaseProcessor(env, counters, executor) {
- }
+ : QueryBaseProcessor(env, counters, executor) {}
nebula::cpp2::ErrorCode checkAndBuildContexts(const cpp2::ScanEdgeRequest& req) override;
diff --git a/src/storage/query/ScanVertexProcessor.cpp b/src/storage/query/ScanVertexProcessor.cpp
index bb9b3a705ad..c8624a0d490 100644
--- a/src/storage/query/ScanVertexProcessor.cpp
+++ b/src/storage/query/ScanVertexProcessor.cpp
@@ -5,6 +5,8 @@
#include "storage/query/ScanVertexProcessor.h"
+#include
+
#include "common/utils/NebulaKeyUtils.h"
#include "storage/StorageFlags.h"
#include "storage/exec/QueryUtils.h"
@@ -24,7 +26,8 @@ void ScanVertexProcessor::process(const cpp2::ScanVertexRequest& req) {
void ScanVertexProcessor::doProcess(const cpp2::ScanVertexRequest& req) {
spaceId_ = req.get_space_id();
- limit_ = req.get_limit();
+ // negative limit number means no limit
+ limit_ = req.get_limit() < 0 ? std::numeric_limits::max() : req.get_limit();
enableReadFollower_ = req.get_enable_read_from_follower();
auto retCode = getSpaceVidLen(spaceId_);
@@ -87,7 +90,7 @@ void ScanVertexProcessor::buildTagColName(const std::vector& t
}
void ScanVertexProcessor::onProcessFinished() {
- resp_.set_vertex_data(std::move(resultDataSet_));
+ resp_.set_props(std::move(resultDataSet_));
resp_.set_cursors(std::move(cursors_));
}
diff --git a/src/storage/query/ScanVertexProcessor.h b/src/storage/query/ScanVertexProcessor.h
index 39b34aedae9..6512c1788a6 100644
--- a/src/storage/query/ScanVertexProcessor.h
+++ b/src/storage/query/ScanVertexProcessor.h
@@ -16,8 +16,7 @@ namespace storage {
extern ProcessorCounters kScanVertexCounters;
-class ScanVertexProcessor
- : public QueryBaseProcessor {
+class ScanVertexProcessor : public QueryBaseProcessor {
public:
static ScanVertexProcessor* instance(StorageEnv* env,
const ProcessorCounters* counters = &kScanVertexCounters,
@@ -31,8 +30,7 @@ class ScanVertexProcessor
private:
ScanVertexProcessor(StorageEnv* env, const ProcessorCounters* counters, folly::Executor* executor)
- : QueryBaseProcessor(
- env, counters, executor) {}
+ : QueryBaseProcessor(env, counters, executor) {}
nebula::cpp2::ErrorCode checkAndBuildContexts(const cpp2::ScanVertexRequest& req) override;
diff --git a/src/storage/test/ScanEdgeTest.cpp b/src/storage/test/ScanEdgeTest.cpp
index 381b0df6c33..320361726e9 100644
--- a/src/storage/test/ScanEdgeTest.cpp
+++ b/src/storage/test/ScanEdgeTest.cpp
@@ -14,13 +14,14 @@
namespace nebula {
namespace storage {
-cpp2::ScanEdgeRequest buildRequest(std::vector partIds,
- std::vector cursors,
- const std::pair>& edge,
- int64_t rowLimit = 100,
- int64_t startTime = 0,
- int64_t endTime = std::numeric_limits::max(),
- bool onlyLatestVer = false) {
+cpp2::ScanEdgeRequest buildRequest(
+ std::vector partIds,
+ std::vector cursors,
+ const std::vector>>& edges,
+ int64_t rowLimit = 100,
+ int64_t startTime = 0,
+ int64_t endTime = std::numeric_limits::max(),
+ bool onlyLatestVer = false) {
cpp2::ScanEdgeRequest req;
req.set_space_id(1);
cpp2::ScanCursor c;
@@ -32,13 +33,17 @@ cpp2::ScanEdgeRequest buildRequest(std::vector partIds,
parts.emplace(partIds[i], c);
}
req.set_parts(std::move(parts));
- EdgeType edgeType = edge.first;
- cpp2::EdgeProp edgeProp;
- edgeProp.set_type(edgeType);
- for (const auto& prop : edge.second) {
- (*edgeProp.props_ref()).emplace_back(std::move(prop));
+ std::vector edgeProps;
+ for (const auto& edge : edges) {
+ EdgeType edgeType = edge.first;
+ cpp2::EdgeProp edgeProp;
+ edgeProp.set_type(edgeType);
+ for (const auto& prop : edge.second) {
+ (*edgeProp.props_ref()).emplace_back(std::move(prop));
+ }
+ edgeProps.emplace_back(std::move(edgeProp));
}
- req.set_return_columns(std::move(edgeProp));
+ req.set_return_columns(std::move(edgeProps));
req.set_limit(rowLimit);
req.set_start_time(startTime);
req.set_end_time(endTime);
@@ -104,14 +109,14 @@ TEST(ScanEdgeTest, PropertyTest) {
serve,
std::vector{kSrc, kType, kRank, kDst, "teamName", "startYear", "endYear"});
for (PartitionID partId = 1; partId <= totalParts; partId++) {
- auto req = buildRequest({partId}, {""}, edge);
+ auto req = buildRequest({partId}, {""}, {edge});
auto* processor = ScanEdgeProcessor::instance(env, nullptr);
auto f = processor->getFuture();
processor->process(req);
auto resp = std::move(f).get();
ASSERT_EQ(0, resp.result.failed_parts.size());
- checkResponse(*resp.edge_data_ref(), edge, edge.second.size(), totalRowCount);
+ checkResponse(*resp.props_ref(), edge, edge.second.size(), totalRowCount);
}
CHECK_EQ(mock::MockData::serves_.size(), totalRowCount);
}
@@ -120,7 +125,7 @@ TEST(ScanEdgeTest, PropertyTest) {
size_t totalRowCount = 0;
auto edge = std::make_pair(serve, std::vector{});
for (PartitionID partId = 1; partId <= totalParts; partId++) {
- auto req = buildRequest({partId}, {""}, edge);
+ auto req = buildRequest({partId}, {""}, {edge});
auto* processor = ScanEdgeProcessor::instance(env, nullptr);
auto f = processor->getFuture();
processor->process(req);
@@ -128,7 +133,7 @@ TEST(ScanEdgeTest, PropertyTest) {
ASSERT_EQ(0, resp.result.failed_parts.size());
// all 9 columns in value
- checkResponse(*resp.edge_data_ref(), edge, 9, totalRowCount);
+ checkResponse(*resp.props_ref(), edge, 9, totalRowCount);
}
CHECK_EQ(mock::MockData::serves_.size(), totalRowCount);
}
@@ -155,14 +160,14 @@ TEST(ScanEdgeTest, CursorTest) {
bool hasNext = true;
std::string cursor = "";
while (hasNext) {
- auto req = buildRequest({partId}, {cursor}, edge, 5);
+ auto req = buildRequest({partId}, {cursor}, {edge}, 5);
auto* processor = ScanEdgeProcessor::instance(env, nullptr);
auto f = processor->getFuture();
processor->process(req);
auto resp = std::move(f).get();
ASSERT_EQ(0, resp.result.failed_parts.size());
- checkResponse(*resp.edge_data_ref(), edge, edge.second.size(), totalRowCount);
+ checkResponse(*resp.props_ref(), edge, edge.second.size(), totalRowCount);
hasNext = resp.get_cursors().at(partId).get_has_next();
if (hasNext) {
CHECK(resp.get_cursors().at(partId).next_cursor_ref().has_value());
@@ -182,14 +187,14 @@ TEST(ScanEdgeTest, CursorTest) {
bool hasNext = true;
std::string cursor = "";
while (hasNext) {
- auto req = buildRequest({partId}, {cursor}, edge, 1);
+ auto req = buildRequest({partId}, {cursor}, {edge}, 1);
auto* processor = ScanEdgeProcessor::instance(env, nullptr);
auto f = processor->getFuture();
processor->process(req);
auto resp = std::move(f).get();
ASSERT_EQ(0, resp.result.failed_parts.size());
- checkResponse(*resp.edge_data_ref(), edge, edge.second.size(), totalRowCount);
+ checkResponse(*resp.props_ref(), edge, edge.second.size(), totalRowCount);
hasNext = resp.get_cursors().at(partId).get_has_next();
if (hasNext) {
CHECK(resp.get_cursors().at(partId).next_cursor_ref().has_value());
@@ -218,20 +223,20 @@ TEST(ScanEdgeTest, MultiplePartsTest) {
auto edge = std::make_pair(
serve,
std::vector{kSrc, kType, kRank, kDst, "teamName", "startYear", "endYear"});
- auto req = buildRequest({1, 3}, {"", ""}, edge);
+ auto req = buildRequest({1, 3}, {"", ""}, {edge});
auto* processor = ScanEdgeProcessor::instance(env, nullptr);
auto f = processor->getFuture();
processor->process(req);
auto resp = std::move(f).get();
ASSERT_EQ(0, resp.result.failed_parts.size());
- checkResponse(*resp.edge_data_ref(), edge, edge.second.size(), totalRowCount);
+ checkResponse(*resp.props_ref(), edge, edge.second.size(), totalRowCount);
}
{
LOG(INFO) << "Scan one edge with all properties in one batch";
size_t totalRowCount = 0;
auto edge = std::make_pair(serve, std::vector{});
- auto req = buildRequest({1, 3}, {"", ""}, edge);
+ auto req = buildRequest({1, 3}, {"", ""}, {edge});
auto* processor = ScanEdgeProcessor::instance(env, nullptr);
auto f = processor->getFuture();
processor->process(req);
@@ -239,7 +244,7 @@ TEST(ScanEdgeTest, MultiplePartsTest) {
ASSERT_EQ(0, resp.result.failed_parts.size());
// all 9 columns in value
- checkResponse(*resp.edge_data_ref(), edge, 9, totalRowCount);
+ checkResponse(*resp.props_ref(), edge, 9, totalRowCount);
}
}
@@ -261,14 +266,14 @@ TEST(ScanEdgeTest, LimitTest) {
auto edge = std::make_pair(
serve,
std::vector{kSrc, kType, kRank, kDst, "teamName", "startYear", "endYear"});
- auto req = buildRequest({1}, {""}, edge, limit);
+ auto req = buildRequest({1}, {""}, {edge}, limit);
auto* processor = ScanEdgeProcessor::instance(env, nullptr);
auto f = processor->getFuture();
processor->process(req);
auto resp = std::move(f).get();
ASSERT_EQ(0, resp.result.failed_parts.size());
- checkResponse(*resp.edge_data_ref(), edge, edge.second.size(), totalRowCount);
+ checkResponse(*resp.props_ref(), edge, edge.second.size(), totalRowCount);
EXPECT_EQ(totalRowCount, limit);
}
{
@@ -276,7 +281,7 @@ TEST(ScanEdgeTest, LimitTest) {
constexpr std::size_t limit = 3;
size_t totalRowCount = 0;
auto edge = std::make_pair(serve, std::vector{});
- auto req = buildRequest({1}, {""}, edge, limit);
+ auto req = buildRequest({1}, {""}, {edge}, limit);
auto* processor = ScanEdgeProcessor::instance(env, nullptr);
auto f = processor->getFuture();
processor->process(req);
@@ -284,7 +289,7 @@ TEST(ScanEdgeTest, LimitTest) {
ASSERT_EQ(0, resp.result.failed_parts.size());
// all 9 columns in value
- checkResponse(*resp.edge_data_ref(), edge, 9, totalRowCount);
+ checkResponse(*resp.props_ref(), edge, 9, totalRowCount);
EXPECT_EQ(totalRowCount, limit);
}
}
@@ -307,7 +312,7 @@ TEST(ScanEdgeTest, FilterTest) {
auto edge = std::make_pair(
serve,
std::vector{kSrc, kType, kRank, kDst, "teamName", "startYear", "endYear"});
- auto req = buildRequest({1}, {""}, edge, limit);
+ auto req = buildRequest({1}, {""}, {edge}, limit);
Expression* filter = EdgePropertyExpression::make(&pool, "101", kSrc);
filter = RelationalExpression::makeEQ(
&pool, filter, ConstantExpression::make(&pool, "Damian Lillard"));
@@ -328,7 +333,7 @@ TEST(ScanEdgeTest, FilterTest) {
"101.endYear"});
expected.emplace_back(
List({"Damian Lillard", 101, 2012, "Trail Blazers", "Trail Blazers", 2012, 2020}));
- EXPECT_EQ(*resp.edge_data_ref(), expected);
+ EXPECT_EQ(*resp.props_ref(), expected);
}
}
diff --git a/src/storage/test/ScanVertexTest.cpp b/src/storage/test/ScanVertexTest.cpp
index ea972dd39e6..09653b7d1bf 100644
--- a/src/storage/test/ScanVertexTest.cpp
+++ b/src/storage/test/ScanVertexTest.cpp
@@ -120,7 +120,7 @@ TEST(ScanVertexTest, PropertyTest) {
auto resp = std::move(f).get();
ASSERT_EQ(0, resp.result.failed_parts.size());
- checkResponse(*resp.vertex_data_ref(), tag, tag.second.size() + 1 /* kVid */, totalRowCount);
+ checkResponse(*resp.props_ref(), tag, tag.second.size() + 1 /* kVid */, totalRowCount);
}
CHECK_EQ(mock::MockData::players_.size(), totalRowCount);
}
@@ -149,7 +149,7 @@ TEST(ScanVertexTest, PropertyTest) {
ASSERT_EQ(0, resp.result.failed_parts.size());
// all 11 columns in value
- checkResponse(*resp.vertex_data_ref(), respTag, 11 + 1 /* kVid */, totalRowCount);
+ checkResponse(*resp.props_ref(), respTag, 11 + 1 /* kVid */, totalRowCount);
}
CHECK_EQ(mock::MockData::players_.size(), totalRowCount);
}
@@ -182,8 +182,7 @@ TEST(ScanVertexTest, CursorTest) {
auto resp = std::move(f).get();
ASSERT_EQ(0, resp.result.failed_parts.size());
- checkResponse(
- *resp.vertex_data_ref(), tag, tag.second.size() + 1 /* kVid */, totalRowCount);
+ checkResponse(*resp.props_ref(), tag, tag.second.size() + 1 /* kVid */, totalRowCount);
hasNext = resp.get_cursors().at(partId).get_has_next();
if (hasNext) {
CHECK(resp.get_cursors().at(partId).next_cursor_ref());
@@ -209,8 +208,7 @@ TEST(ScanVertexTest, CursorTest) {
auto resp = std::move(f).get();
ASSERT_EQ(0, resp.result.failed_parts.size());
- checkResponse(
- *resp.vertex_data_ref(), tag, tag.second.size() + 1 /* kVid */, totalRowCount);
+ checkResponse(*resp.props_ref(), tag, tag.second.size() + 1 /* kVid */, totalRowCount);
hasNext = resp.get_cursors().at(partId).get_has_next();
if (hasNext) {
CHECK(resp.get_cursors().at(partId).next_cursor_ref());
@@ -245,7 +243,7 @@ TEST(ScanVertexTest, MultiplePartsTest) {
auto resp = std::move(f).get();
ASSERT_EQ(0, resp.result.failed_parts.size());
- checkResponse(*resp.vertex_data_ref(), tag, tag.second.size() + 1 /* kVid */, totalRowCount);
+ checkResponse(*resp.props_ref(), tag, tag.second.size() + 1 /* kVid */, totalRowCount);
}
{
LOG(INFO) << "Scan one tag with all properties in one batch";
@@ -271,7 +269,7 @@ TEST(ScanVertexTest, MultiplePartsTest) {
ASSERT_EQ(0, resp.result.failed_parts.size());
// all 11 columns in value
- checkResponse(*resp.vertex_data_ref(), respTag, 11 + 1 /* kVid */, totalRowCount);
+ checkResponse(*resp.props_ref(), respTag, 11 + 1 /* kVid */, totalRowCount);
}
}
@@ -299,7 +297,7 @@ TEST(ScanVertexTest, LimitTest) {
auto resp = std::move(f).get();
ASSERT_EQ(0, resp.result.failed_parts.size());
- checkResponse(*resp.vertex_data_ref(), tag, tag.second.size() + 1 /* kVid */, totalRowCount);
+ checkResponse(*resp.props_ref(), tag, tag.second.size() + 1 /* kVid */, totalRowCount);
EXPECT_EQ(totalRowCount, limit);
}
{
@@ -327,7 +325,7 @@ TEST(ScanVertexTest, LimitTest) {
ASSERT_EQ(0, resp.result.failed_parts.size());
// all 11 columns in value
- checkResponse(*resp.vertex_data_ref(), respTag, 11 + 1 /* kVid */, totalRowCount);
+ checkResponse(*resp.props_ref(), respTag, 11 + 1 /* kVid */, totalRowCount);
EXPECT_EQ(totalRowCount, limit);
}
}
@@ -433,7 +431,7 @@ TEST(ScanVertexTest, MultipleTagsTest) {
16.7,
Value::kEmpty,
Value::kEmpty}));
- EXPECT_EQ(expect, *resp.vertex_data_ref());
+ EXPECT_EQ(expect, *resp.props_ref());
}
}
@@ -471,7 +469,7 @@ TEST(ScanVertexTest, FilterTest) {
{"_vid", "1._vid", "1._tag", "1.name", "1.age", "1.avgScore", "2._tag", "2.name"});
expect.emplace_back(List(
{"Kobe Bryant", "Kobe Bryant", 1, "Kobe Bryant", 41, 25, Value::kEmpty, Value::kEmpty}));
- EXPECT_EQ(expect, *resp.vertex_data_ref());
+ EXPECT_EQ(expect, *resp.props_ref());
}
{
LOG(INFO) << "Scan one tag with some properties in one batch";
@@ -498,7 +496,7 @@ TEST(ScanVertexTest, FilterTest) {
{"_vid", "1._vid", "1._tag", "1.name", "1.age", "1.avgScore", "2._tag", "2.name"});
expect.emplace_back(List(
{"Kobe Bryant", "Kobe Bryant", 1, "Kobe Bryant", 41, 25, Value::kEmpty, Value::kEmpty}));
- EXPECT_EQ(expect, *resp.vertex_data_ref());
+ EXPECT_EQ(expect, *resp.props_ref());
}
}
diff --git a/tests/tck/features/delete/DeleteTag.IntVid.feature b/tests/tck/features/delete/DeleteTag.IntVid.feature
index af279dd4701..aaffae4d825 100644
--- a/tests/tck/features/delete/DeleteTag.IntVid.feature
+++ b/tests/tck/features/delete/DeleteTag.IntVid.feature
@@ -41,6 +41,7 @@ Feature: Delete int vid of tag
"""
Then the result should be, in any order:
| player.name | player.age |
+ | EMPTY | EMPTY |
When executing query:
"""
FETCH PROP ON bachelor hash("Tim Duncan") YIELD bachelor.name, bachelor.speciality
@@ -94,12 +95,14 @@ Feature: Delete int vid of tag
"""
Then the result should be, in any order:
| player.name | player.age |
+ | EMPTY | EMPTY |
When executing query:
"""
FETCH PROP ON bachelor hash("Tim Duncan") YIELD bachelor.name, bachelor.speciality
"""
Then the result should be, in any order:
| bachelor.name | bachelor.speciality |
+ | EMPTY | EMPTY |
When executing query:
"""
LOOKUP ON player WHERE player.name == "Tim Duncan" YIELD id(vertex) as id
@@ -146,12 +149,14 @@ Feature: Delete int vid of tag
"""
Then the result should be, in any order:
| player.name | player.age |
+ | EMPTY | EMPTY |
When executing query:
"""
FETCH PROP ON bachelor hash("Tim Duncan") YIELD bachelor.name, bachelor.speciality
"""
Then the result should be, in any order:
| bachelor.name | bachelor.speciality |
+ | EMPTY | EMPTY |
When executing query:
"""
LOOKUP ON player WHERE player.name == "Tim Duncan" YIELD id(vertex) as id
@@ -205,12 +210,14 @@ Feature: Delete int vid of tag
"""
Then the result should be, in any order:
| player.name | player.age |
+ | EMPTY | EMPTY |
When executing query:
"""
FETCH PROP ON player hash("Tony Parker") YIELD player.name, player.age
"""
Then the result should be, in any order:
| player.name | player.age |
+ | EMPTY | EMPTY |
When executing query:
"""
LOOKUP ON player WHERE player.name == "Tim Duncan" YIELD id(vertex) as id
@@ -256,6 +263,7 @@ Feature: Delete int vid of tag
"""
Then the result should be, in any order:
| team.name |
+ | EMPTY |
# delete tag from pipe and normal
When executing query:
"""
@@ -295,6 +303,7 @@ Feature: Delete int vid of tag
"""
Then the result should be, in any order:
| team.name |
+ | EMPTY |
# delete one tag from var and normal
When executing query:
"""
diff --git a/tests/tck/features/delete/DeleteTag.feature b/tests/tck/features/delete/DeleteTag.feature
index 4e01b0cdeff..5770bdcd133 100644
--- a/tests/tck/features/delete/DeleteTag.feature
+++ b/tests/tck/features/delete/DeleteTag.feature
@@ -41,6 +41,7 @@ Feature: Delete string vid of tag
"""
Then the result should be, in any order:
| player.name | player.age |
+ | EMPTY | EMPTY |
When executing query:
"""
FETCH PROP ON bachelor "Tim Duncan" YIELD bachelor.name, bachelor.speciality
@@ -94,12 +95,14 @@ Feature: Delete string vid of tag
"""
Then the result should be, in any order:
| player.name | player.age |
+ | EMPTY | EMPTY |
When executing query:
"""
FETCH PROP ON bachelor "Tim Duncan" YIELD bachelor.name, bachelor.speciality
"""
Then the result should be, in any order:
| bachelor.name | bachelor.speciality |
+ | EMPTY | EMPTY |
When executing query:
"""
LOOKUP ON player WHERE player.name == "Tim Duncan" YIELD id(vertex) as id
@@ -146,12 +149,14 @@ Feature: Delete string vid of tag
"""
Then the result should be, in any order:
| player.name | player.age |
+ | EMPTY | EMPTY |
When executing query:
"""
FETCH PROP ON bachelor "Tim Duncan" YIELD bachelor.name, bachelor.speciality
"""
Then the result should be, in any order:
| bachelor.name | bachelor.speciality |
+ | EMPTY | EMPTY |
When executing query:
"""
LOOKUP ON player WHERE player.name == "Tim Duncan" YIELD id(vertex) as id
@@ -205,12 +210,14 @@ Feature: Delete string vid of tag
"""
Then the result should be, in any order:
| player.name | player.age |
+ | EMPTY | EMPTY |
When executing query:
"""
FETCH PROP ON player "Tony Parker" YIELD player.name, player.age
"""
Then the result should be, in any order:
| player.name | player.age |
+ | EMPTY | EMPTY |
When executing query:
"""
LOOKUP ON player WHERE player.name == "Tim Duncan" YIELD id(vertex) as id
@@ -256,6 +263,7 @@ Feature: Delete string vid of tag
"""
Then the result should be, in any order:
| team.name |
+ | EMPTY |
# delete tag from pipe and normal
When executing query:
"""
@@ -295,6 +303,7 @@ Feature: Delete string vid of tag
"""
Then the result should be, in any order:
| team.name |
+ | EMPTY |
# delete one tag from var and normal
When executing query:
"""
diff --git a/tests/tck/features/go/GO.feature b/tests/tck/features/go/GO.feature
index 3ed44e7a4a2..9ee57c3eb26 100644
--- a/tests/tck/features/go/GO.feature
+++ b/tests/tck/features/go/GO.feature
@@ -753,6 +753,34 @@ Feature: Go Sentence
RETURN $B IF $A IS NOT NULL
"""
Then a SemanticError should be raised at runtime: `$a.id', not exist variable `a'
+ When executing query:
+ """
+ $A = GO FROM 'Tim Duncan' OVER like YIELD like._dst AS dst;
+ $B = GO FROM $A.dst OVER like YIELD like._dst AS dst;
+ GO FROM $A.dst over like YIELD like._dst AS dst, $B.dst
+ """
+ Then a SemanticError should be raised at runtime: A variable must be referred in FROM before used in WHERE or YIELD
+ When executing query:
+ """
+ $A = GO FROM 'Tim Duncan' OVER like YIELD like._dst AS dst;
+ $B = GO FROM $A.dst OVER like YIELD like._dst AS dst;
+ GO FROM $A.dst over like WHERE $B.dst > "A" YIELD like._dst AS dst
+ """
+ Then a SemanticError should be raised at runtime: A variable must be referred in FROM before used in WHERE or YIELD
+ When executing query:
+ """
+ $A = GO FROM 'Tim Duncan' OVER like YIELD like._dst AS dst;
+ $B = GO FROM $A.dst OVER like YIELD like._dst AS dst;
+ GO FROM $A.dst over like YIELD like._dst AS dst, $B.dst, $A.dst
+ """
+ Then a SemanticError should be raised at runtime: Multiple variable property is not supported in WHERE or YIELD
+ When executing query:
+ """
+ $A = GO FROM 'Tim Duncan' OVER like YIELD like._dst AS dst;
+ $B = GO FROM $A.dst OVER like YIELD like._dst AS dst;
+ GO FROM $A.dst over like WHERE $A.dst > "A" and $B.dst > "B" YIELD like._dst
+ """
+ Then a SemanticError should be raised at runtime: Multiple variable property is not supported in WHERE or YIELD
When executing query:
"""
RETURN $rA IF $rA IS NOT NULL;
diff --git a/tests/tck/features/match/Base.IntVid.feature b/tests/tck/features/match/Base.IntVid.feature
index d06379f3775..36d9c69d83e 100644
--- a/tests/tck/features/match/Base.IntVid.feature
+++ b/tests/tck/features/match/Base.IntVid.feature
@@ -487,36 +487,36 @@ Feature: Basic match
"""
MATCH (v) return v
"""
- Then a SemanticError should be raised at runtime: Can't solve the start vids from the sentence: MATCH (v) RETURN v
+ Then a ExecutionError should be raised at runtime: Scan vertices must specify limit number.
When executing query:
"""
MATCH (v{name: "Tim Duncan"}) return v
"""
- Then a SemanticError should be raised at runtime: Can't solve the start vids from the sentence: MATCH (v{name:"Tim Duncan"}) RETURN v
+ Then a ExecutionError should be raised at runtime: Scan vertices must specify limit number.
When executing query:
"""
MATCH (v:player:bachelor) RETURN v
"""
- Then a SemanticError should be raised at runtime: Can't solve the start vids from the sentence: MATCH (v:player:bachelor) RETURN v
+ Then a ExecutionError should be raised at runtime: Scan vertices must specify limit number.
When executing query:
"""
MATCH (v:player{age:23}:bachelor) RETURN v
"""
- Then a SemanticError should be raised at runtime: Can't solve the start vids from the sentence: MATCH (v:player{age:23}:bachelor) RETURN v
+ Then a ExecutionError should be raised at runtime: Scan vertices must specify limit number.
When executing query:
"""
MATCH () -[]-> (v) return *
"""
- Then a SemanticError should be raised at runtime: Can't solve the start vids from the sentence: MATCH ()-->(v) RETURN *
+ Then a ExecutionError should be raised at runtime: Scan edges must specify limit number.
When executing query:
"""
MATCH () --> (v) --> () return *
"""
- Then a SemanticError should be raised at runtime: Can't solve the start vids from the sentence: MATCH ()-->(v)-->() RETURN *
+ Then a ExecutionError should be raised at runtime: Scan vertices must specify limit number.
# The 0 step means node scan in fact, but p and t has no label or properties for index seek
# So it's not workable now
When executing query:
"""
MATCH (p)-[:serve*0..3]->(t) RETURN p
"""
- Then a SemanticError should be raised at runtime: Can't solve the start vids from the sentence: MATCH (p)-[:serve*0..3]->(t) RETURN p
+ Then a ExecutionError should be raised at runtime: Scan vertices must specify limit number.
diff --git a/tests/tck/features/match/Base.feature b/tests/tck/features/match/Base.feature
index ddd9f76d970..2177de5273e 100644
--- a/tests/tck/features/match/Base.feature
+++ b/tests/tck/features/match/Base.feature
@@ -596,36 +596,36 @@ Feature: Basic match
"""
MATCH (v) return v
"""
- Then a SemanticError should be raised at runtime: Can't solve the start vids from the sentence: MATCH (v) RETURN v
+ Then a ExecutionError should be raised at runtime: Scan vertices must specify limit number.
When executing query:
"""
MATCH (v{name: "Tim Duncan"}) return v
"""
- Then a SemanticError should be raised at runtime: Can't solve the start vids from the sentence: MATCH (v{name:"Tim Duncan"}) RETURN v
+ Then a ExecutionError should be raised at runtime: Scan vertices must specify limit number.
When executing query:
"""
MATCH (v:player:bachelor) RETURN v
"""
- Then a SemanticError should be raised at runtime: Can't solve the start vids from the sentence: MATCH (v:player:bachelor) RETURN v
+ Then a ExecutionError should be raised at runtime: Scan vertices must specify limit number.
When executing query:
"""
MATCH (v:player{age:23}:bachelor) RETURN v
"""
- Then a SemanticError should be raised at runtime: Can't solve the start vids from the sentence: MATCH (v:player{age:23}:bachelor) RETURN v
+ Then a ExecutionError should be raised at runtime: Scan vertices must specify limit number.
When executing query:
"""
MATCH () -[]-> (v) return *
"""
- Then a SemanticError should be raised at runtime: Can't solve the start vids from the sentence: MATCH ()-->(v) RETURN *
+ Then a ExecutionError should be raised at runtime: Scan edges must specify limit number.
When executing query:
"""
MATCH () --> (v) --> () return *
"""
- Then a SemanticError should be raised at runtime: Can't solve the start vids from the sentence: MATCH ()-->(v)-->() RETURN *
+ Then a ExecutionError should be raised at runtime: Scan vertices must specify limit number.
# The 0 step means node scan in fact, but p and t has no label or properties for index seek
# So it's not workable now
When executing query:
"""
MATCH (p)-[:serve*0..3]->(t) RETURN p
"""
- Then a SemanticError should be raised at runtime: Can't solve the start vids from the sentence: MATCH (p)-[:serve*0..3]->(t) RETURN p
+ Then a ExecutionError should be raised at runtime: Scan vertices must specify limit number.
diff --git a/tests/tck/features/match/Scan.feature b/tests/tck/features/match/Scan.feature
new file mode 100644
index 00000000000..faf8e5cded3
--- /dev/null
+++ b/tests/tck/features/match/Scan.feature
@@ -0,0 +1,157 @@
+# Copyright (c) 2021 vesoft inc. All rights reserved.
+#
+# This source code is licensed under Apache 2.0 License.
+Feature: Match seek by scan
+
+ Background: Prepare space
+ Given a graph with space named "student"
+
+ Scenario: query vertices by scan
+ When executing query:
+ """
+ MATCH (v)
+ RETURN v.name AS Name
+ LIMIT 3
+ """
+ Then the result should be, in any order:
+ | Name |
+ | /\w+/ |
+ | /\w+/ |
+ | /\w+/ |
+ When executing query:
+ """
+ MATCH (v:teacher)
+ RETURN v.name AS Name
+ LIMIT 3
+ """
+ Then the result should be, in any order:
+ | Name |
+ | /\w+/ |
+ | /\w+/ |
+ | /\w+/ |
+ # TODO check index validation in match planner entry
+ # When executing query:
+ # """
+ # MATCH (v:teacher)
+ # WHERE v.name = "Mary"
+ # RETURN v.name AS Name
+ # LIMIT 3
+ # """
+ # Then the result should be, in any order:
+ # | Name |
+ # | "Mary" |
+ # When executing query:
+ # """
+ # MATCH (v:teacher {name: "Mary"})
+ # RETURN v.name AS Name
+ # LIMIT 3
+ # """
+ # Then the result should be, in any order:
+ # | Name |
+ # | "Mary" |
+ When executing query:
+ """
+ MATCH (v:teacher:student)
+ RETURN v.name AS Name
+ LIMIT 3
+ """
+ Then the result should be, in any order:
+ | Name |
+ When executing query:
+ """
+ MATCH (v:person:teacher)
+ RETURN v.name AS Name
+ LIMIT 3
+ """
+ Then the result should be, in any order:
+ | Name |
+ | /\w+/ |
+ | /\w+/ |
+ | /\w+/ |
+ When executing query:
+ """
+ MATCH (v:person{name: "Mary"}:teacher)
+ RETURN v.name AS Name
+ LIMIT 3
+ """
+ Then the result should be, in any order:
+ | Name |
+ | "Mary" |
+
+ Scenario: query vertices by scan failed
+ When executing query:
+ """
+ MATCH (v)
+ RETURN v.name AS Name
+ """
+ Then a ExecutionError should be raised at runtime: Scan vertices must specify limit number.
+ When executing query:
+ """
+ MATCH (v{name: "Mary"})
+ RETURN v.name AS Name
+ LIMIT 3
+ """
+ Then a ExecutionError should be raised at runtime: Scan vertices must specify limit number.
+
+ Scenario: query edge by scan
+ When executing query:
+ """
+ MATCH ()-[e]->()
+ RETURN type(e) AS Type
+ LIMIT 3
+ """
+ Then the result should be, in any order:
+ | Type |
+ | /[\w_]+/ |
+ | /[\w_]+/ |
+ | /[\w_]+/ |
+ When executing query:
+ """
+ MATCH ()-[e:is_teacher]->()
+ RETURN type(e) AS Type, e.start_year AS StartYear, e.end_year AS EndYear
+ LIMIT 3
+ """
+ Then the result should be, in any order:
+ | Type | StartYear | EndYear |
+ | /[\w_]+/ | /\d{4}/ | /\d{4}/ |
+ | /[\w_]+/ | /\d{4}/ | /\d{4}/ |
+ | /[\w_]+/ | /\d{4}/ | /\d{4}/ |
+
+ # TODO check index validation in match planner entry
+ # When executing query:
+ # """
+ # MATCH ()-[e:is_teacher]->()
+ # WHERE e.start_year == 2018
+ # RETURN type(e) AS Type, e.start_year AS StartYear, e.end_year AS EndYear
+ # LIMIT 3
+ # """
+ # Then the result should be, in any order:
+ # | Type | StartYear | EndYear |
+ # | /[\w_]+/ | /\d{4}/ | /\d{4}/ |
+ # | /[\w_]+/ | /\d{4}/ | /\d{4}/ |
+ # | /[\w_]+/ | /\d{4}/ | /\d{4}/ |
+ # When executing query:
+ # """
+ # MATCH ()-[e:is_teacher {start_year: 2018}]->()
+ # RETURN type(e) AS Type, e.start_year AS StartYear, e.end_year AS EndYear
+ # LIMIT 3
+ # """
+ # Then the result should be, in any order:
+ # | Type | StartYear | EndYear |
+ # | /[\w_]+/ | /\d{4}/ | /\d{4}/ |
+ # | /[\w_]+/ | /\d{4}/ | /\d{4}/ |
+ # | /[\w_]+/ | /\d{4}/ | /\d{4}/ |
+ Scenario: query edge by scan failed
+ When executing query:
+ """
+ MATCH ()-[e]->()
+ RETURN type(e) AS Type
+ """
+ Then a ExecutionError should be raised at runtime: Scan edges must specify limit number.
+ When executing query:
+ """
+ MATCH (v)-[e]->()
+ RETURN v.name, type(e) AS Type
+ LIMIT 3
+ """
+ Then a ExecutionError should be raised at runtime: Scan vertices must specify limit number.
diff --git a/tests/tck/features/match/SeekByEdge.feature b/tests/tck/features/match/SeekByEdge.feature
index ef1629c7652..51c1983eb0b 100644
--- a/tests/tck/features/match/SeekByEdge.feature
+++ b/tests/tck/features/match/SeekByEdge.feature
@@ -1469,7 +1469,7 @@ Feature: Match seek by edge
MATCH (p1)-[:teammate]->(p2)
RETURN p1.name, id(p2)
"""
- Then a SemanticError should be raised at runtime: Can't solve the start vids from the sentence: MATCH (p1)-[:teammate]->(p2) RETURN p1.name,id(p2)
+ Then a ExecutionError should be raised at runtime: Scan vertices must specify limit number.
Scenario Outline: seek by edge in a single edge type space
Given an empty graph
@@ -1490,16 +1490,16 @@ Feature: Match seek by edge
MATCH (p1)-[]->(p2)
RETURN p1.name, id(p2)
"""
- Then a SemanticError should be raised at runtime: Can't solve the start vids from the sentence: MATCH (p1)-->(p2) RETURN p1.name,id(p2)
+ Then a ExecutionError should be raised at runtime: Scan vertices must specify limit number.
When executing query:
"""
MATCH (p1)-[b]->(p2)
RETURN p1.name, id(p2)
"""
- Then a SemanticError should be raised at runtime: Can't solve the start vids from the sentence: MATCH (p1)-[b]->(p2) RETURN p1.name,id(p2)
+ Then a ExecutionError should be raised at runtime: Scan vertices must specify limit number.
When executing query:
"""
MATCH (p1)-[:edge_1]->(p2)
RETURN p1.name, id(p2)
"""
- Then a SemanticError should be raised at runtime: Can't solve the start vids from the sentence: MATCH (p1)-[:edge_1]->(p2) RETURN p1.name,id(p2)
+ Then a ExecutionError should be raised at runtime: Scan vertices must specify limit number.
diff --git a/tests/tck/features/match/SeekById.feature b/tests/tck/features/match/SeekById.feature
index e4793ecea7b..c3dd7f5ba72 100644
--- a/tests/tck/features/match/SeekById.feature
+++ b/tests/tck/features/match/SeekById.feature
@@ -222,37 +222,37 @@ Feature: Match seek by id
WHERE NOT id(v) == 'Paul Gasol'
RETURN v.name AS Name, v.age AS Age
"""
- Then a SemanticError should be raised at runtime:
+ Then a ExecutionError should be raised at runtime: Scan vertices must specify limit number.
When executing query:
"""
MATCH (v)
WHERE NOT id(v) IN ['James Harden', 'Jonathon Simmons', 'Klay Thompson', 'Dejounte Murray']
RETURN v.name AS Name
"""
- Then a SemanticError should be raised at runtime:
+ Then a ExecutionError should be raised at runtime: Scan vertices must specify limit number.
When executing query:
"""
MATCH (v)
WHERE id(v) IN ['James Harden', 'Jonathon Simmons', 'Klay Thompson', 'Dejounte Murray']
- OR v.age == 23
+ OR v.age == 23
RETURN v.name AS Name
"""
- Then a SemanticError should be raised at runtime:
+ Then a ExecutionError should be raised at runtime: Scan vertices must specify limit number.
When executing query:
"""
MATCH (v)
WHERE id(v) == 'James Harden'
- OR v.age == 23
+ OR v.age == 23
RETURN v.name AS Name
"""
- Then a SemanticError should be raised at runtime:
+ Then a ExecutionError should be raised at runtime: Scan vertices must specify limit number.
When executing query:
"""
MATCH (v)
WHERE id(x) == 'James Harden'
RETURN v.name AS Name
"""
- Then a SemanticError should be raised at runtime:
+ Then a SemanticError should be raised at runtime: Alias used but not defined: `x'
When executing query:
"""
MATCH (v)
@@ -266,7 +266,7 @@ Feature: Match seek by id
WHERE id(v) IN ['James Harden', v.name]
RETURN v.name AS Name
"""
- Then a SemanticError should be raised at runtime:
+ Then a ExecutionError should be raised at runtime: Scan vertices must specify limit number.
Scenario: Start from end
When executing query:
diff --git a/tests/tck/features/match/SeekById.intVid.feature b/tests/tck/features/match/SeekById.intVid.feature
index 02a9f94c806..fb5fa4db1c2 100644
--- a/tests/tck/features/match/SeekById.intVid.feature
+++ b/tests/tck/features/match/SeekById.intVid.feature
@@ -222,44 +222,44 @@ Feature: Match seek by id
WHERE NOT id(v) == hash('Paul Gasol')
RETURN v.name AS Name, v.age AS Age
"""
- Then a SemanticError should be raised at runtime:
+ Then a ExecutionError should be raised at runtime: Scan vertices must specify limit number.
When executing query:
"""
MATCH (v)
WHERE NOT id(v) IN [hash('James Harden'), hash('Jonathon Simmons'), hash('Klay Thompson'), hash('Dejounte Murray')]
RETURN v.name AS Name
"""
- Then a SemanticError should be raised at runtime:
+ Then a ExecutionError should be raised at runtime: Scan vertices must specify limit number.
When executing query:
"""
MATCH (v)
WHERE id(v) IN [hash('James Harden'), hash('Jonathon Simmons'), hash('Klay Thompson'), hash('Dejounte Murray')]
- OR v.age == 23
+ OR v.age == 23
RETURN v.name AS Name
"""
- Then a SemanticError should be raised at runtime:
+ Then a ExecutionError should be raised at runtime: Scan vertices must specify limit number.
When executing query:
"""
MATCH (v)
WHERE id(v) == hash('James Harden')
- OR v.age == 23
+ OR v.age == 23
RETURN v.name AS Name
"""
- Then a SemanticError should be raised at runtime:
+ Then a ExecutionError should be raised at runtime: Scan vertices must specify limit number.
When executing query:
"""
MATCH (v)
WHERE id(x) == hash('James Harden')
RETURN v.name AS Name
"""
- Then a SemanticError should be raised at runtime:
+ Then a SemanticError should be raised at runtime: Alias used but not defined: `x'
When executing query:
"""
MATCH (v)
WHERE id(v) IN [hash('James Harden'), v.name]
RETURN v.name AS Name
"""
- Then a SemanticError should be raised at runtime:
+ Then a ExecutionError should be raised at runtime: Scan vertices must specify limit number.
Scenario: with arithmetic
When executing query:
diff --git a/tests/tck/features/match/Unwind.feature b/tests/tck/features/match/Unwind.feature
index 718a18199ac..44e1c30852b 100644
--- a/tests/tck/features/match/Unwind.feature
+++ b/tests/tck/features/match/Unwind.feature
@@ -131,3 +131,26 @@ Feature: Unwind clause
| <("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"})-[:like@0 {likeness: 95}]->("Manu Ginobili" :player{age: 41, name: "Manu Ginobili"})> |
| <("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"})-[:like@0 {likeness: 95}]->("Tony Parker" :player{age: 36, name: "Tony Parker"})-[:like@0 {likeness: 95}]->("Manu Ginobili" :player{age: 41, name: "Manu Ginobili"})> |
| <("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"})-[:like@0 {likeness: 95}]->("Tony Parker" :player{age: 36, name: "Tony Parker"})-[:like@0 {likeness: 90}]->("LaMarcus Aldridge" :player{age: 33, name: "LaMarcus Aldridge"})> |
+
+ Scenario: unwind invalid expression
+ When executing query:
+ """
+ UNWIND collect([1,2,3]) as n return n
+ """
+ Then a SemanticError should be raised at runtime: Can't use aggregating expressions in unwind clause, `collect([1,2,3])'
+ When executing query:
+ """
+ LOOKUP on player YIELD id(vertex) as id |
+ GO 1 TO 3 STEPS FROM $-.id OVER * BIDIRECT YIELD DISTINCT src(edge) as src_id, dst(edge) as dst_id |
+ UNWIND collect($-.src_id) + collect($-.dst_id) as vid
+ WITH DISTINCT vid
+ RETURN collect(vid) as vids
+ """
+ Then a SemanticError should be raised at runtime: Can't use aggregating expressions in unwind clause, `(collect($-.src_id)+collect($-.dst_id))'
+ When executing query:
+ """
+ MATCH (a:player {name:"Tim Duncan"}) - [e:like] -> (b)
+ UNWIND count(b) as num
+ RETURN num
+ """
+ Then a SemanticError should be raised at runtime: Can't use aggregating expressions in unwind clause, `count(b)'
diff --git a/tests/tck/features/match/With.feature b/tests/tck/features/match/With.feature
index 57a9de0c5b7..613c39c0496 100644
--- a/tests/tck/features/match/With.feature
+++ b/tests/tck/features/match/With.feature
@@ -158,6 +158,24 @@ Feature: With clause
RETURN *
"""
Then a SemanticError should be raised at runtime: RETURN * is not allowed when there are no variables in scope
+ When executing query:
+ """
+ MATCH (:player {name:"Chris Paul"})-[:serve]->(b)
+ WITH collect(b) as teams
+ RETURN teams
+ """
+ Then the result should be, in any order, with relax comparison:
+ | teams |
+ | [("Rockets" :team{name: "Rockets"}), ("Clippers" :team{name: "Clippers"}), ("Hornets" :team{name: "Hornets"})] |
+ When executing query:
+ """
+ MATCH (:player {name:"Chris Paul"})-[e:like]->(b)
+ WITH avg(e.likeness) as avg, max(e.likeness) as max
+ RETURN avg, max
+ """
+ Then the result should be, in any order, with relax comparison:
+ | avg | max |
+ | 90.0 | 90 |
@skip
Scenario: with match return
diff --git a/tests/tck/features/optimizer/PushLimitDownScanEdgesRule.feature b/tests/tck/features/optimizer/PushLimitDownScanEdgesRule.feature
new file mode 100644
index 00000000000..a0aabec877f
--- /dev/null
+++ b/tests/tck/features/optimizer/PushLimitDownScanEdgesRule.feature
@@ -0,0 +1,47 @@
+# Copyright (c) 2021 vesoft inc. All rights reserved.
+#
+# This source code is licensed under Apache 2.0 License.
+Feature: Push Limit down scan edges rule
+
+ Background:
+ Given a graph with space named "student"
+
+ Scenario: push limit down to ScanEdges
+ When profiling query:
+ """
+ MATCH ()-[e:is_teacher]->()
+ RETURN e.start_year LIMIT 3
+ """
+ Then the result should be, in any order:
+ | e.start_year |
+ | /\d+/ |
+ | /\d+/ |
+ | /\d+/ |
+ And the execution plan should be:
+ | id | name | dependencies | operator info |
+ | 9 | DataCollect | 19 | |
+ | 19 | Project | 16 | |
+ | 16 | Limit | 11 | |
+ | 11 | AppendVertices | 3 | |
+ | 3 | Project | 2 | |
+ | 2 | ScanEdges | 0 | {"limit": "3"} |
+ | 0 | Start | | |
+ When profiling query:
+ """
+ MATCH ()-[e]->()
+ RETURN type(e) LIMIT 3
+ """
+ Then the result should be, in any order:
+ | type(e) |
+ | /\w+/ |
+ | /\w+/ |
+ | /\w+/ |
+ And the execution plan should be:
+ | id | name | dependencies | operator info |
+ | 9 | DataCollect | 19 | |
+ | 19 | Project | 16 | |
+ | 16 | Limit | 11 | |
+ | 11 | AppendVertices | 3 | |
+ | 3 | Project | 2 | |
+ | 2 | ScanEdges | 0 | {"limit": "3"} |
+ | 0 | Start | | |
diff --git a/tests/tck/features/optimizer/PushLimitDownScanVerticesRule.feature b/tests/tck/features/optimizer/PushLimitDownScanVerticesRule.feature
new file mode 100644
index 00000000000..dd503af4909
--- /dev/null
+++ b/tests/tck/features/optimizer/PushLimitDownScanVerticesRule.feature
@@ -0,0 +1,45 @@
+# Copyright (c) 2021 vesoft inc. All rights reserved.
+#
+# This source code is licensed under Apache 2.0 License.
+Feature: Push Limit down scan vertices rule
+
+ Background:
+ Given a graph with space named "student"
+
+ Scenario: push limit down to ScanVertices
+ When profiling query:
+ """
+ MATCH (v)
+ RETURN v.name LIMIT 3
+ """
+ Then the result should be, in any order:
+ | v.name |
+ | /\w+/ |
+ | /\w+/ |
+ | /\w+/ |
+ And the execution plan should be:
+ | id | name | dependencies | operator info |
+ | 9 | DataCollect | 19 | |
+ | 19 | Project | 16 | |
+ | 16 | Limit | 11 | |
+ | 11 | AppendVertices | 2 | |
+ | 2 | ScanVertices | 0 | {"limit": "3"} |
+ | 0 | Start | | |
+ When profiling query:
+ """
+ MATCH (v:person)
+ RETURN v.name LIMIT 3
+ """
+ Then the result should be, in any order:
+ | v.name |
+ | /\w+/ |
+ | /\w+/ |
+ | /\w+/ |
+ And the execution plan should be:
+ | id | name | dependencies | operator info |
+ | 9 | DataCollect | 19 | |
+ | 19 | Project | 16 | |
+ | 16 | Limit | 11 | |
+ | 11 | AppendVertices | 2 | |
+ | 2 | ScanVertices | 0 | {"limit": "3"} |
+ | 0 | Start | | |
diff --git a/tests/tck/features/ttl/TTL.feature b/tests/tck/features/ttl/TTL.feature
index 1cb456d209d..f3f74c6c618 100644
--- a/tests/tck/features/ttl/TTL.feature
+++ b/tests/tck/features/ttl/TTL.feature
@@ -393,31 +393,36 @@ Feature: TTLTest
FETCH PROP ON person "1" YIELD vertex as node;
"""
Then the result should be, in any order, with relax comparison:
- | node |
+ | node |
+ | ("1") |
When executing query:
"""
FETCH PROP ON person "1" YIELD person.id as id
"""
Then the result should be, in any order:
- | id |
+ | id |
+ | EMPTY |
When executing query:
"""
FETCH PROP ON * "1" YIELD person.id, career.id
"""
Then the result should be, in any order:
| person.id | career.id |
+ | EMPTY | EMPTY |
When executing query:
"""
FETCH PROP ON person "2" YIELD person.id
"""
Then the result should be, in any order:
| person.id |
+ | EMPTY |
When executing query:
"""
FETCH PROP ON person "2" YIELD person.id as id
"""
Then the result should be, in any order:
- | id |
+ | id |
+ | EMPTY |
When executing query:
"""
FETCH PROP ON career "2" YIELD career.id;
@@ -486,5 +491,6 @@ Feature: TTLTest
FETCH PROP ON person "1" YIELD person.age as age;
"""
Then the result should be, in any order:
- | age |
+ | age |
+ | EMPTY |
And drop the used space