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