From c9f84b1d4428a48cc803e0bca077c11e23bfcf3e Mon Sep 17 00:00:00 2001 From: Sophie <84560950+Sophie-Xie@users.noreply.github.com> Date: Tue, 25 Oct 2022 17:35:07 +0800 Subject: [PATCH] Cherry pick 3.3 (1022-1025) (#4779) * Fix/find start error (#4771) * Fix find start error. * Fix test. Co-authored-by: Sophie <84560950+Sophie-Xie@users.noreply.github.com> * Introduce JSON_EXTRACT function (#4743) * Introduce JSON_EXTRACT function close: #3513 Note, we don't support the path argument in this phase * address jievince's review commit removed the unecessary interface of construct Map from Value * Type handling Only primitive types are supported * Support depth1 nested * lint: fmt * ut: ctest, fixed wrong expression of Map * fix ut errors * tck: case for json_extract added Co-authored-by: Sophie <84560950+Sophie-Xie@users.noreply.github.com> * Fix bug #1337 from ent (#4740) * Return an semantic error if no tag is found for a property while validating a match. * Return an semantic error if no tag is found for a property while validating a match. * Add a tck case for the fixed bug. * commented out unused codes. * add tag in tck cases * fixing tck * updated tck cases to add missing cases that are supposed to be there. * Return an semantic error if no tag is found for a property while validating a match. * Add a tck case for the fixed bug. * commented out unused codes. * add tag in tck cases * fixing tck * updated tck cases to add missing cases that are supposed to be there. * update * update tck case. Co-authored-by: Sophie <84560950+Sophie-Xie@users.noreply.github.com> * Fix RollUpApplyExecutor (#4778) Co-authored-by: Sophie <84560950+Sophie-Xie@users.noreply.github.com> * Fix mutil-match crash in optimization phase (#4780) fmt small fix small fix * fix subgraph step (#4776) * fix subgraph step * forbid function call in where clause * fix error Co-authored-by: shylock <33566796+Shylock-Hg@users.noreply.github.com> Co-authored-by: Wey Gu Co-authored-by: Cheng Xuntao <7731943+xtcyclist@users.noreply.github.com> Co-authored-by: Yichen Wang <18348405+Aiee@users.noreply.github.com> Co-authored-by: kyle.cao Co-authored-by: jimingquan --- src/common/datatypes/Map.cpp | 44 ++++++++++++++ src/common/datatypes/Map.h | 3 + src/common/function/FunctionManager.cpp | 33 +++++++++++ .../function/test/FunctionManagerTest.cpp | 21 ++++++- src/graph/executor/algo/SubgraphExecutor.cpp | 7 +-- .../executor/query/RollUpApplyExecutor.cpp | 8 ++- src/graph/planner/match/ArgumentFinder.h | 4 ++ src/graph/planner/match/LabelIndexSeek.cpp | 2 +- src/graph/planner/match/LabelIndexSeek.h | 4 ++ src/graph/planner/match/PropIndexSeek.h | 4 ++ src/graph/planner/match/ScanSeek.h | 4 ++ src/graph/planner/match/StartVidFinder.h | 2 + src/graph/planner/match/VertexIdSeek.h | 4 ++ src/graph/planner/ngql/SubgraphPlanner.cpp | 4 -- src/graph/planner/plan/PlanNode.cpp | 6 +- src/graph/planner/plan/Query.cpp | 12 +++- src/graph/validator/MatchValidator.cpp | 15 ++++- .../features/bugfix/FindStartError.feature | 16 +++++ tests/tck/features/function/coalesce.feature | 2 +- .../features/function/json_extract.feature | 59 +++++++++++++++++++ tests/tck/features/match/Base.IntVid.feature | 9 +-- tests/tck/features/match/Base.feature | 16 ++--- .../features/match/MatchById.IntVid.feature | 2 +- tests/tck/features/match/MatchById.feature | 17 +++++- tests/tck/features/match/PathExpr.feature | 27 +++++++++ tests/tck/features/match/Scan.feature | 3 +- .../match/VariableLengthPattern.feature | 2 +- .../VariableLengthPattern.intVid.feature | 2 +- .../features/subgraph/subgraph.IntVid.feature | 8 +++ tests/tck/features/subgraph/subgraph.feature | 8 +++ .../subgraph/subgraphWithFilter.feature | 8 +++ tests/tck/features/yield/parameter.feature | 2 +- 32 files changed, 317 insertions(+), 41 deletions(-) create mode 100644 tests/tck/features/bugfix/FindStartError.feature create mode 100644 tests/tck/features/function/json_extract.feature diff --git a/src/common/datatypes/Map.cpp b/src/common/datatypes/Map.cpp index 8d9248ae47f..095d001cc4a 100644 --- a/src/common/datatypes/Map.cpp +++ b/src/common/datatypes/Map.cpp @@ -6,6 +6,7 @@ #include "common/datatypes/Map.h" #include +#include #include @@ -44,6 +45,49 @@ folly::dynamic Map::getMetaData() const { return mapMetadataObj; } +// Map constructor to covert from folly::dynamic object +// Called by function: json_extract() + +// TODO(wey-gu) support Datetime, deeper nested Map/Datatypes +Map::Map(const folly::dynamic& obj) { + DCHECK(obj.isObject()); + for (auto& kv : obj.items()) { + if (kv.second.isString()) { + kvs.emplace(kv.first.asString(), Value(kv.second.asString())); + } else if (kv.second.isInt()) { + kvs.emplace(kv.first.asString(), Value(kv.second.asInt())); + } else if (kv.second.isDouble()) { + kvs.emplace(kv.first.asString(), Value(kv.second.asDouble())); + } else if (kv.second.isBool()) { + kvs.emplace(kv.first.asString(), Value(kv.second.asBool())); + } else if (kv.second.isNull()) { + kvs.emplace(kv.first.asString(), Value()); + } else if (kv.second.isObject()) { + std::unordered_map values; + for (auto& nkv : kv.second.items()) { + if (nkv.second.isString()) { + values.emplace(nkv.first.asString(), Value(nkv.second.asString())); + } else if (nkv.second.isInt()) { + values.emplace(nkv.first.asString(), Value(nkv.second.asInt())); + } else if (nkv.second.isDouble()) { + values.emplace(nkv.first.asString(), Value(nkv.second.asDouble())); + } else if (nkv.second.isBool()) { + values.emplace(nkv.first.asString(), Value(nkv.second.asBool())); + } else { + LOG(WARNING) << "JSON_EXTRACT nested layer 1: Map can be populated only by " + "Bool, Double, Int, String value and null, now trying to parse from: " + << nkv.second.typeName(); + } + } + kvs.emplace(kv.first.asString(), Value(Map(std::move(values)))); + } else { + LOG(WARNING) << "JSON_EXTRACT Only Bool, Double, Int, String value, null and Map(depth==1) " + "are supported, now trying to parse from: " + << kv.second.typeName(); + } + } +} + } // namespace nebula namespace std { diff --git a/src/common/datatypes/Map.h b/src/common/datatypes/Map.h index 26c370baa45..91134a48387 100644 --- a/src/common/datatypes/Map.h +++ b/src/common/datatypes/Map.h @@ -8,6 +8,7 @@ #include +#include "common/base/Logging.h" #include "common/datatypes/Value.h" namespace nebula { @@ -22,6 +23,8 @@ struct Map { kvs = std::move(values); } + explicit Map(const folly::dynamic& obj); + Map& operator=(const Map& rhs) { if (this == &rhs) { return *this; diff --git a/src/common/function/FunctionManager.cpp b/src/common/function/FunctionManager.cpp index c07e2e7d5e6..425a87410eb 100644 --- a/src/common/function/FunctionManager.cpp +++ b/src/common/function/FunctionManager.cpp @@ -5,6 +5,8 @@ #include "FunctionManager.h" +#include + #include #include "common/base/Base.h" @@ -421,6 +423,9 @@ std::unordered_map> FunctionManager::typ TypeSignature({Value::Type::MAP}, Value::Type::DURATION)}}, {"extract", {TypeSignature({Value::Type::STRING, Value::Type::STRING}, Value::Type::LIST)}}, {"_nodeid", {TypeSignature({Value::Type::PATH, Value::Type::INT}, Value::Type::INT)}}, + {"json_extract", + {TypeSignature({Value::Type::STRING}, Value::Type::MAP), + TypeSignature({Value::Type::STRING}, Value::Type::NULLVALUE)}}, }; // static @@ -2766,6 +2771,34 @@ FunctionManager::FunctionManager() { } }; } + { + auto &attr = functions_["json_extract"]; + // note, we don't support second argument(path) like MySQL JSON_EXTRACT for now + attr.minArity_ = 1; + attr.maxArity_ = 1; + attr.isAlwaysPure_ = true; + attr.body_ = [](const auto &args) -> Value { + if (!args[0].get().isStr()) { + return Value::kNullBadType; + } + auto json = args[0].get().getStr(); + + // invalid string to json will be caught and returned as null + try { + auto obj = folly::parseJson(json); + if (!obj.isObject()) { + return Value::kNullBadData; + } + // if obj is empty, i.e. "{}", return empty map + if (obj.empty()) { + return Map(); + } + return Map(obj); + } catch (const std::exception &e) { + return Value::kNullBadData; + } + }; + } } // NOLINT // static diff --git a/src/common/function/test/FunctionManagerTest.cpp b/src/common/function/test/FunctionManagerTest.cpp index 20e460c4661..05e36f119f9 100644 --- a/src/common/function/test/FunctionManagerTest.cpp +++ b/src/common/function/test/FunctionManagerTest.cpp @@ -132,7 +132,11 @@ std::unordered_map> FunctionManagerTest::args_ = {"date", {Date(1984, 10, 11)}}, {"datetime", {DateTime(1984, 10, 11, 12, 31, 14, 341)}}, {"edge", {Edge("1", "2", -1, "e1", 0, {{"e1", 1}, {"e2", 2}})}}, -}; + {"json_extract0", {"{\"a\": 1, \"b\": 0.2}"}}, + {"json_extract1", {"{\"a\": 1, \"b\": 0.2, \"c\": {\"d\": true}}"}}, + {"json_extract2", {"_"}}, + {"json_extract3", {"{a: 1, \"b\": 0.2}"}}, + {"json_extract4", {"{\"a\": \"foo\", \"b\": 0.2, \"c\": {\"d\": {\"e\": 0.1}}}"}}}; #define TEST_FUNCTION(expr, ...) \ do { \ @@ -385,6 +389,21 @@ TEST_F(FunctionManagerTest, functionCall) { { TEST_FUNCTION(rand32, args_["empty"]); } { TEST_FUNCTION(now, args_["empty"]); } { TEST_FUNCTION(hash, args_["string"]); } + { + TEST_FUNCTION( + json_extract, args_["json_extract0"], Value(Map({{"a", Value(1)}, {"b", Value(0.2)}}))); + TEST_FUNCTION( + json_extract, + args_["json_extract1"], + Value(Map({{"a", Value(1)}, {"b", Value(0.2)}, {"c", Value(Map({{"d", Value(true)}}))}}))); + // invalid json string + TEST_FUNCTION(json_extract, args_["json_extract2"], Value::kNullBadData); + TEST_FUNCTION(json_extract, args_["json_extract3"], Value::kNullBadData); + // when there is nested Map in depth >= 2, the value will be dropped as empty Map() + TEST_FUNCTION(json_extract, + args_["json_extract4"], + Value(Map({{"a", Value("foo")}, {"b", Value(0.2)}, {"c", Value(Map())}}))); + } { auto result = FunctionManager::get("hash", 1); ASSERT_TRUE(result.ok()); diff --git a/src/graph/executor/algo/SubgraphExecutor.cpp b/src/graph/executor/algo/SubgraphExecutor.cpp index 826b5c1d86c..4a05a4a6b75 100644 --- a/src/graph/executor/algo/SubgraphExecutor.cpp +++ b/src/graph/executor/algo/SubgraphExecutor.cpp @@ -92,12 +92,7 @@ folly::Future SubgraphExecutor::handleResponse(RpcResponse&& resps) { auto listVal = std::make_shared(std::move(list)); auto iter = std::make_unique(listVal); - auto steps = totalSteps_; - if (!subgraph_->oneMoreStep()) { - --steps; - } - - if (!process(std::move(iter)) || ++currentStep_ > steps) { + if (!process(std::move(iter)) || ++currentStep_ > totalSteps_) { filterEdges(0); return folly::makeFuture(Status::OK()); } else { diff --git a/src/graph/executor/query/RollUpApplyExecutor.cpp b/src/graph/executor/query/RollUpApplyExecutor.cpp index 657df7ca5df..87c96c8e10d 100644 --- a/src/graph/executor/query/RollUpApplyExecutor.cpp +++ b/src/graph/executor/query/RollUpApplyExecutor.cpp @@ -142,17 +142,21 @@ folly::Future RollUpApplyExecutor::rollUpApply() { NG_RETURN_IF_ERROR(checkBiInputDataSets()); DataSet result; mv_ = movable(node()->inputVars()[0]); + if (rollUpApplyNode->compareCols().size() == 0) { List hashTable; buildZeroKeyHashTable(rollUpApplyNode->collectCol(), rhsIter_.get(), hashTable); result = probeZeroKey(lhsIter_.get(), hashTable); } else if (rollUpApplyNode->compareCols().size() == 1) { std::unordered_map hashTable; - buildSingleKeyHashTable(rollUpApplyNode->compareCols()[0], + // Clone the expression so when evaluating the InputPropertyExpression, the propIndex_ will not + // be buffered. + buildSingleKeyHashTable(rollUpApplyNode->compareCols()[0]->clone(), rollUpApplyNode->collectCol(), rhsIter_.get(), hashTable); - result = probeSingleKey(rollUpApplyNode->compareCols()[0], lhsIter_.get(), hashTable); + + result = probeSingleKey(rollUpApplyNode->compareCols()[0]->clone(), lhsIter_.get(), hashTable); } else { std::unordered_map hashTable; buildHashTable( diff --git a/src/graph/planner/match/ArgumentFinder.h b/src/graph/planner/match/ArgumentFinder.h index 1c7a31d3ba5..e344a705ba6 100644 --- a/src/graph/planner/match/ArgumentFinder.h +++ b/src/graph/planner/match/ArgumentFinder.h @@ -26,6 +26,10 @@ class ArgumentFinder final : public StartVidFinder { StatusOr transformEdge(EdgeContext* edgeCtx) override; + const char* name() const override { + return "ArgumentFinder"; + } + private: ArgumentFinder() = default; }; diff --git a/src/graph/planner/match/LabelIndexSeek.cpp b/src/graph/planner/match/LabelIndexSeek.cpp index 54b07f43628..9c95008c97d 100644 --- a/src/graph/planner/match/LabelIndexSeek.cpp +++ b/src/graph/planner/match/LabelIndexSeek.cpp @@ -91,7 +91,7 @@ StatusOr LabelIndexSeek::transformNode(NodeContext* nodeCtx) { if (filter->kind() == Expression::Kind::kLogicalOr) { auto exprs = ExpressionUtils::collectAll(filter, {Expression::Kind::kLabelTagProperty}); - bool matched = true; + bool matched = exprs.empty() ? false : true; for (auto* expr : exprs) { auto tagPropExpr = static_cast(expr); if (static_cast(tagPropExpr->label())->prop() != nodeAlias || diff --git a/src/graph/planner/match/LabelIndexSeek.h b/src/graph/planner/match/LabelIndexSeek.h index 24f6b550d23..45c797136f5 100644 --- a/src/graph/planner/match/LabelIndexSeek.h +++ b/src/graph/planner/match/LabelIndexSeek.h @@ -18,6 +18,10 @@ class LabelIndexSeek final : public StartVidFinder { return std::unique_ptr(new LabelIndexSeek()); } + const char* name() const override { + return "LabelIndexSeekFinder"; + } + private: LabelIndexSeek() = default; diff --git a/src/graph/planner/match/PropIndexSeek.h b/src/graph/planner/match/PropIndexSeek.h index 821178e1615..9a9b52c70c9 100644 --- a/src/graph/planner/match/PropIndexSeek.h +++ b/src/graph/planner/match/PropIndexSeek.h @@ -26,6 +26,10 @@ class PropIndexSeek final : public StartVidFinder { StatusOr transformEdge(EdgeContext* edgeCtx) override; + const char* name() const override { + return "PropIndexSeekFinder"; + } + private: PropIndexSeek() = default; }; diff --git a/src/graph/planner/match/ScanSeek.h b/src/graph/planner/match/ScanSeek.h index c07243d8a7f..03e522c4fa8 100644 --- a/src/graph/planner/match/ScanSeek.h +++ b/src/graph/planner/match/ScanSeek.h @@ -26,6 +26,10 @@ class ScanSeek final : public StartVidFinder { StatusOr transformEdge(EdgeContext* edgeCtx) override; + const char* name() const override { + return "ScanSeekFinder"; + } + private: ScanSeek() = default; }; diff --git a/src/graph/planner/match/StartVidFinder.h b/src/graph/planner/match/StartVidFinder.h index bc841274e71..7726ef0b646 100644 --- a/src/graph/planner/match/StartVidFinder.h +++ b/src/graph/planner/match/StartVidFinder.h @@ -61,6 +61,8 @@ class StartVidFinder { virtual StatusOr transformEdge(EdgeContext* edgeCtx) = 0; + virtual const char* name() const = 0; + protected: StartVidFinder() = default; }; diff --git a/src/graph/planner/match/VertexIdSeek.h b/src/graph/planner/match/VertexIdSeek.h index b3ea83d5f60..5b5746209c8 100644 --- a/src/graph/planner/match/VertexIdSeek.h +++ b/src/graph/planner/match/VertexIdSeek.h @@ -30,6 +30,10 @@ class VertexIdSeek final : public StartVidFinder { std::string listToAnnoVarVid(QueryContext* qctx, const List& list); + const char* name() const override { + return "VertexIdSeekFinder"; + } + private: VertexIdSeek() = default; }; diff --git a/src/graph/planner/ngql/SubgraphPlanner.cpp b/src/graph/planner/ngql/SubgraphPlanner.cpp index c7b480f7136..f79b08393fc 100644 --- a/src/graph/planner/ngql/SubgraphPlanner.cpp +++ b/src/graph/planner/ngql/SubgraphPlanner.cpp @@ -69,7 +69,6 @@ StatusOr>> SubgraphPlanner::buildEdgeProps StatusOr SubgraphPlanner::nSteps(SubPlan& startVidPlan, const std::string& input) { auto* qctx = subgraphCtx_->qctx; const auto& space = subgraphCtx_->space; - const auto& dstTagProps = subgraphCtx_->exprProps.dstTagProps(); const auto& steps = subgraphCtx_->steps; auto vertexProps = buildVertexProps(); @@ -89,9 +88,6 @@ StatusOr SubgraphPlanner::nSteps(SubPlan& startVidPlan, const std::stri subgraph->setEdgeProps(std::move(edgeProps).value()); subgraph->setInputVar(input); subgraph->setBiDirectEdgeTypes(subgraphCtx_->biDirectEdgeTypes); - if (subgraphCtx_->getEdgeProp || subgraphCtx_->withProp || !dstTagProps.empty()) { - subgraph->setOneMoreStep(); - } auto* dc = DataCollect::make(qctx, DataCollect::DCKind::kSubgraph); dc->addDep(subgraph); diff --git a/src/graph/planner/plan/PlanNode.cpp b/src/graph/planner/plan/PlanNode.cpp index 9e8dc0b0228..fb1b8023aa6 100644 --- a/src/graph/planner/plan/PlanNode.cpp +++ b/src/graph/planner/plan/PlanNode.cpp @@ -423,7 +423,11 @@ BinaryInputNode::BinaryInputNode(QueryContext* qctx, } addDep(right); - readVariable(right->outputVarPtr()); + if (right != nullptr) { + readVariable(right->outputVarPtr()); + } else { + inputVars_.emplace_back(nullptr); + } } // It's used for clone diff --git a/src/graph/planner/plan/Query.cpp b/src/graph/planner/plan/Query.cpp index 127e2ce4dea..5c0f57b602e 100644 --- a/src/graph/planner/plan/Query.cpp +++ b/src/graph/planner/plan/Query.cpp @@ -870,7 +870,9 @@ std::unique_ptr BiLeftJoin::explain() const { } PlanNode* BiLeftJoin::clone() const { - auto* newLeftJoin = BiLeftJoin::make(qctx_, nullptr, nullptr); + auto* lnode = left() ? left()->clone() : nullptr; + auto* rnode = right() ? right()->clone() : nullptr; + auto* newLeftJoin = BiLeftJoin::make(qctx_, lnode, rnode); newLeftJoin->cloneMembers(*this); return newLeftJoin; } @@ -886,7 +888,9 @@ std::unique_ptr BiInnerJoin::explain() const { } PlanNode* BiInnerJoin::clone() const { - auto* newInnerJoin = BiInnerJoin::make(qctx_, nullptr, nullptr); + auto* lnode = left() ? left()->clone() : nullptr; + auto* rnode = right() ? right()->clone() : nullptr; + auto* newInnerJoin = BiInnerJoin::make(qctx_, lnode, rnode); newInnerJoin->cloneMembers(*this); return newInnerJoin; } @@ -925,7 +929,9 @@ void RollUpApply::cloneMembers(const RollUpApply& r) { } PlanNode* RollUpApply::clone() const { - auto* newRollUpApply = RollUpApply::make(qctx_, nullptr, nullptr, {}, nullptr); + auto* lnode = left() ? left()->clone() : nullptr; + auto* rnode = right() ? right()->clone() : nullptr; + auto* newRollUpApply = RollUpApply::make(qctx_, lnode, rnode, {}, nullptr); newRollUpApply->cloneMembers(*this); return newRollUpApply; } diff --git a/src/graph/validator/MatchValidator.cpp b/src/graph/validator/MatchValidator.cpp index 0b156720212..ca93ab9fb0d 100644 --- a/src/graph/validator/MatchValidator.cpp +++ b/src/graph/validator/MatchValidator.cpp @@ -192,6 +192,12 @@ Status MatchValidator::buildNodeInfo(const MatchPath *path, auto alias = node->alias(); auto *props = node->props(); auto anonymous = false; + // if there exists some property with no tag, return a semantic error + if (props != nullptr) { + return Status::SemanticError("`%s:%s': No tag found for property.", + props->items()[0].first.c_str(), + props->items()[0].second->toString().c_str()); + } if (node->labels() != nullptr) { auto &labels = node->labels()->labels(); for (const auto &label : labels) { @@ -213,11 +219,18 @@ Status MatchValidator::buildNodeInfo(const MatchPath *path, nodeAliases.emplace(alias, AliasType::kNode); } Expression *filter = nullptr; + /* Note(Xuntao): Commented out the following part. With the current parser, + if no tag is given in match clauses, node->props() is not nullptr but + node-labels() is. This is not supposed to be valid. + */ + /* if (props != nullptr) { auto result = makeNodeSubFilter(const_cast(props), "*"); NG_RETURN_IF_ERROR(result); filter = result.value(); - } else if (node->labels() != nullptr && !node->labels()->labels().empty()) { + } else + */ + if (node->labels() != nullptr && !node->labels()->labels().empty()) { const auto &labels = node->labels()->labels(); for (const auto &label : labels) { auto result = makeNodeSubFilter(label->props(), *label->label()); diff --git a/tests/tck/features/bugfix/FindStartError.feature b/tests/tck/features/bugfix/FindStartError.feature new file mode 100644 index 00000000000..f4ad71868e1 --- /dev/null +++ b/tests/tck/features/bugfix/FindStartError.feature @@ -0,0 +1,16 @@ +# Copyright (c) 2022 vesoft inc. All rights reserved. +# +# This source code is licensed under Apache 2.0 License. +Feature: Test find start error for property index seek + + Background: + Given a graph with space named "nba" + + # #4763 + Scenario: Find start of match pattern + When executing query: + """ + WITH 1 as a MATCH (v:player) WHERE a == 3 OR (a + 1) == 4 RETURN v.player.name; + """ + Then the result should be, in any order: + | v.player.name | diff --git a/tests/tck/features/function/coalesce.feature b/tests/tck/features/function/coalesce.feature index c06ae41ec50..0391c892f49 100644 --- a/tests/tck/features/function/coalesce.feature +++ b/tests/tck/features/function/coalesce.feature @@ -1,4 +1,4 @@ -Feature: Fetch Int Vid Edges +Feature: Coalesce Function Background: Test coalesce function diff --git a/tests/tck/features/function/json_extract.feature b/tests/tck/features/function/json_extract.feature new file mode 100644 index 00000000000..d2cb0ee2b79 --- /dev/null +++ b/tests/tck/features/function/json_extract.feature @@ -0,0 +1,59 @@ +Feature: json_extract Function + + Background: + Test json_extract function + + Scenario: Test Positive Cases + When executing query: + """ + YIELD JSON_EXTRACT('{"a": "foo", "b": 0.2, "c": true}') AS result; + """ + Then the result should be, in any order: + | result | + | {a: "foo", b: 0.2, c: true} | + When executing query: + """ + YIELD JSON_EXTRACT('{"a": 1, "b": {}, "c": {"d": true}}') AS result; + """ + Then the result should be, in any order: + | result | + | {a: 1, b: {}, c: {d: true}} | + When executing query: + """ + YIELD JSON_EXTRACT('{}') AS result; + """ + Then the result should be, in any order: + | result | + | {} | + + Scenario: Test Cases With Invalid JSON String + When executing query: + """ + YIELD JSON_EXTRACT('fuzz') AS result; + """ + Then the result should be, in any order: + | result | + | BAD_DATA | + When executing query: + """ + YIELD JSON_EXTRACT(3.1415926) AS result; + """ + Then a SemanticError should be raised at runtime: `JSON_EXTRACT(3.1415926)' is not a valid expression : Parameter's type error + + Scenario: Test Cases Hitting Limitations + # Here nested Map depth is 2, the nested item is omitted: + When executing query: + """ + YIELD JSON_EXTRACT('{"a": "foo", "b": false, "c": {"d": {"e": 0.1}}}') AS result; + """ + Then the result should be, in any order: + | result | + | {a: "foo", b: false, c: {}} | + # Here List is not yet supported, the encounted value is omitted: + When executing query: + """ + YIELD JSON_EXTRACT('{"a": "foo", "b": false, "c": [1, 2, 3]}') AS result; + """ + Then the result should be, in any order: + | result | + | {a: "foo", b: false} | diff --git a/tests/tck/features/match/Base.IntVid.feature b/tests/tck/features/match/Base.IntVid.feature index dc4bff00b7b..20a43766a8f 100644 --- a/tests/tck/features/match/Base.IntVid.feature +++ b/tests/tck/features/match/Base.IntVid.feature @@ -128,7 +128,7 @@ Feature: Basic match | "serve" | "Cavaliers" | When executing query: """ - MATCH (v1:player{name: "LeBron James"}) -[r:serve]-> (v2 {name: "Cavaliers"}) + MATCH (v1:player{name: "LeBron James"}) -[r:serve]-> (v2:team{name: "Cavaliers"}) RETURN type(r) AS Type, v2.team.name AS Name """ Then the result should be, in any order: @@ -137,7 +137,7 @@ Feature: Basic match | "serve" | "Cavaliers" | When executing query: """ - MATCH (v1:player{name: "LeBron James"}) -[r:serve]-> (v2 {name: "Cavaliers"}) + MATCH (v1:player{name: "LeBron James"}) -[r:serve]-> (v2:team{name: "Cavaliers"}) WHERE r.start_year <= 2005 AND r.end_year >= 2005 RETURN r.start_year AS Start_Year, r.end_year AS Start_Year """ @@ -514,11 +514,6 @@ Feature: Basic match MATCH (v) return v """ Then a ExecutionError should be raised at runtime: Scan vertices or edges need to specify a limit number, or limit number can not push down. - When executing query: - """ - MATCH (v{name: "Tim Duncan"}) return v - """ - Then a ExecutionError should be raised at runtime: Scan vertices or edges need to specify a limit number, or limit number can not push down. When executing query: """ MATCH (v:player:bachelor) RETURN v diff --git a/tests/tck/features/match/Base.feature b/tests/tck/features/match/Base.feature index 27348d21ef9..01876f32ae6 100644 --- a/tests/tck/features/match/Base.feature +++ b/tests/tck/features/match/Base.feature @@ -166,7 +166,7 @@ Feature: Basic match | "serve" | "Cavaliers" | When executing query: """ - MATCH (v1:player{name: "LeBron James"}) -[r:serve]-> (v2 {name: "Cavaliers"}) + MATCH (v1:player{name: "LeBron James"}) -[r:serve]-> (v2:team{name: "Cavaliers"}) RETURN type(r) AS Type, v2.team.name AS Name """ Then the result should be, in any order: @@ -175,7 +175,7 @@ Feature: Basic match | "serve" | "Cavaliers" | When executing query: """ - MATCH (v1:player{name: "LeBron James"}) -[r:serve]-> (v2 {name: "Cavaliers"}) + MATCH (v1:player{name: "LeBron James"}) -[r:serve]-> (v2:team{name: "Cavaliers"}) WHERE r.start_year <= 2005 AND r.end_year >= 2005 RETURN r.start_year AS Start_Year, r.end_year AS Start_Year """ @@ -624,11 +624,6 @@ Feature: Basic match MATCH (v) return v """ Then a ExecutionError should be raised at runtime: Scan vertices or edges need to specify a limit number, or limit number can not push down. - When executing query: - """ - MATCH (v{name: "Tim Duncan"}) return v - """ - Then a ExecutionError should be raised at runtime: Scan vertices or edges need to specify a limit number, or limit number can not push down. When executing query: """ MATCH (v:player:bachelor) RETURN v @@ -761,3 +756,10 @@ Feature: Basic match """ Then the result should be, in any order, with relax comparison: | id(v) | + + Scenario: match_with_wrong_syntax + When executing query: + """ + MATCH (v{name: "Tim Duncan"}) return v + """ + Then a SemanticError should be raised at runtime: `name:"Tim Duncan"': No tag found for property. diff --git a/tests/tck/features/match/MatchById.IntVid.feature b/tests/tck/features/match/MatchById.IntVid.feature index 1bf44590933..38ff151f642 100644 --- a/tests/tck/features/match/MatchById.IntVid.feature +++ b/tests/tck/features/match/MatchById.IntVid.feature @@ -133,7 +133,7 @@ Feature: Integer Vid Match By Id | 'serve' | 'Lakers' | When executing query: """ - MATCH (v1) -[r:serve]-> (v2 {name: "Cavaliers"}) + MATCH (v1) -[r:serve]-> (v2:team {name: "Cavaliers"}) WHERE id(v1) == hash("LeBron James") RETURN type(r) AS Type, v2.team.name AS Name """ diff --git a/tests/tck/features/match/MatchById.feature b/tests/tck/features/match/MatchById.feature index ba600a169bf..c679c7d6486 100644 --- a/tests/tck/features/match/MatchById.feature +++ b/tests/tck/features/match/MatchById.feature @@ -133,7 +133,7 @@ Feature: Match By Id | 'serve' | 'Lakers' | When executing query: """ - MATCH (v1) -[r:serve]-> (v2 {name: "Cavaliers"}) + MATCH (v1) -[r:serve]-> (v2:team{name: "Cavaliers"}) WHERE id(v1) == "LeBron James" RETURN type(r) AS Type, v2.team.name AS Name """ @@ -1017,6 +1017,21 @@ Feature: Match By Id RETURN id(a) as src, id(b) as dst """ Then a ExecutionError should be raised at runtime: Scan vertices or edges need to specify a limit number, or limit number can not push down. + When executing query: + """ + MATCH (n) MATCH (n) WHERE id(n) == 'James Harden' RETURN n + """ + Then a ExecutionError should be raised at runtime: Scan vertices or edges need to specify a limit number, or limit number can not push down. + When executing query: + """ + OPTIONAL MATCH (n) MATCH (n) WHERE id(n) == 'James Harden' RETURN n + """ + Then a ExecutionError should be raised at runtime: Scan vertices or edges need to specify a limit number, or limit number can not push down. + When executing query: + """ + OPTIONAL MATCH (n) OPTIONAL MATCH (n) WHERE id(n) == 'James Harden' RETURN n + """ + Then a ExecutionError should be raised at runtime: Scan vertices or edges need to specify a limit number, or limit number can not push down. When executing query: """ MATCH (v1)-[:like]->(v2:player)-[:serve]->(v3) diff --git a/tests/tck/features/match/PathExpr.feature b/tests/tck/features/match/PathExpr.feature index ffcd80d8d2f..0cb908646c5 100644 --- a/tests/tck/features/match/PathExpr.feature +++ b/tests/tck/features/match/PathExpr.feature @@ -6,6 +6,23 @@ Feature: Basic match Background: Given a graph with space named "nba" + Scenario: Tagless property + When executing query: + """ + match p = (v{name: "hello"})-->(v1{name: "hello"}) where id(v) == "kk" return p limit 1; + """ + Then a SemanticError should be raised at runtime: `name:"hello"': No tag found for property. + When executing query: + """ + match p = (v:player{name: "hello"})-->(v1{name: "world"}) where id(v) == "kk" return p limit 1; + """ + Then a SemanticError should be raised at runtime: `name:"world"': No tag found for property. + When executing query: + """ + match p = (v{name: "hello"})-->(v1:player{name: "world"}) where id(v) == "kk" return p limit 1; + """ + Then a SemanticError should be raised at runtime: `name:"hello"': No tag found for property. + Scenario: Undefined aliases When executing query: """ @@ -123,6 +140,16 @@ Feature: Basic match | name | | "Tim Duncan" | | "Tim Duncan" | + When executing query: + """ + MATCH (v:player{name:"Tim Duncan"})<-[:like]-(v2) WHERE NOT (v2)<-[:like]-() RETURN v2; + """ + Then the result should be, in any order: + | v2 | + | ("Dejounte Murray" :player{age: 29, name: "Dejounte Murray"}) | + | ("Aron Baynes" :player{age: 32, name: "Aron Baynes"}) | + | ("Tiago Splitter" :player{age: 34, name: "Tiago Splitter"}) | + | ("Boris Diaw" :player{age: 36, name: "Boris Diaw"}) | Scenario: In With When executing query: diff --git a/tests/tck/features/match/Scan.feature b/tests/tck/features/match/Scan.feature index 3937e5ea91c..fc84d2f70f5 100644 --- a/tests/tck/features/match/Scan.feature +++ b/tests/tck/features/match/Scan.feature @@ -87,9 +87,8 @@ Feature: Match seek by scan Then a ExecutionError should be raised at runtime: Scan vertices or edges need to specify a limit number, or limit number can not push down. When executing query: """ - MATCH (v{name: "Mary"}) + MATCH (v:person) RETURN v.student.name AS Name - LIMIT 3 """ Then a ExecutionError should be raised at runtime: Scan vertices or edges need to specify a limit number, or limit number can not push down. diff --git a/tests/tck/features/match/VariableLengthPattern.feature b/tests/tck/features/match/VariableLengthPattern.feature index 8d3b26edf05..c3c19133787 100644 --- a/tests/tck/features/match/VariableLengthPattern.feature +++ b/tests/tck/features/match/VariableLengthPattern.feature @@ -310,7 +310,7 @@ Feature: Variable length Pattern match (m to n) Scenario: multi-steps and filter by node properties When executing query: """ - MATCH (v:player{name: 'Tim Duncan'})-[e1:like*1..2]-(v2{name: 'Tony Parker'})-[e2:serve]-(v3{name: 'Spurs'}) + MATCH (v:player{name: 'Tim Duncan'})-[e1:like*1..2]-(v2:player{name: 'Tony Parker'})-[e2:serve]-(v3:team{name: 'Spurs'}) RETURN e1, e2 """ Then the result should be, in any order, with relax comparison: diff --git a/tests/tck/features/match/VariableLengthPattern.intVid.feature b/tests/tck/features/match/VariableLengthPattern.intVid.feature index 70721c32c77..238b5671671 100644 --- a/tests/tck/features/match/VariableLengthPattern.intVid.feature +++ b/tests/tck/features/match/VariableLengthPattern.intVid.feature @@ -310,7 +310,7 @@ Feature: Integer Vid Variable length Pattern match (m to n) Scenario: Integer Vid multi-steps and filter by node properties When executing query: """ - MATCH (v:player{name: 'Tim Duncan'})-[e1:like*1..2]-(v2{name: 'Tony Parker'})-[e2:serve]-(v3{name: 'Spurs'}) + MATCH (v:player{name: 'Tim Duncan'})-[e1:like*1..2]-(v2:player{name: 'Tony Parker'})-[e2:serve]-(v3:team{name: 'Spurs'}) RETURN e1, e2 """ Then the result should be, in any order, with relax comparison: diff --git a/tests/tck/features/subgraph/subgraph.IntVid.feature b/tests/tck/features/subgraph/subgraph.IntVid.feature index e70c30ae9f1..390aae3747e 100644 --- a/tests/tck/features/subgraph/subgraph.IntVid.feature +++ b/tests/tck/features/subgraph/subgraph.IntVid.feature @@ -145,6 +145,14 @@ Feature: Integer Vid subgraph | nodes | relationships | | [("Tim Duncan")] | <[edge1]> | | <[vertex2]> | <[edge2]> | + When executing query: + """ + GET SUBGRAPH FROM hash('Tim Duncan') OUT like YIELD vertices as v + """ + Then the result should be, in any order, with relax comparison: + | v | + | [("Tim Duncan")] | + | [("Manu Ginobili"), ("Tony Parker")] | Scenario: yield Integer Vid two steps When executing query: diff --git a/tests/tck/features/subgraph/subgraph.feature b/tests/tck/features/subgraph/subgraph.feature index e33ecd62c08..4951352eee1 100644 --- a/tests/tck/features/subgraph/subgraph.feature +++ b/tests/tck/features/subgraph/subgraph.feature @@ -145,6 +145,14 @@ Feature: subgraph | nodes | relationships | | [("Tim Duncan")] | <[edge1]> | | <[vertex2]> | <[edge2]> | + When executing query: + """ + GET SUBGRAPH FROM 'Tim Duncan' OUT like YIELD vertices as v + """ + Then the result should be, in any order, with relax comparison: + | v | + | [("Tim Duncan")] | + | [("Manu Ginobili"), ("Tony Parker")] | Scenario: two steps When executing query: diff --git a/tests/tck/features/subgraph/subgraphWithFilter.feature b/tests/tck/features/subgraph/subgraphWithFilter.feature index c5793a5ced5..97afffab724 100644 --- a/tests/tck/features/subgraph/subgraphWithFilter.feature +++ b/tests/tck/features/subgraph/subgraphWithFilter.feature @@ -7,6 +7,14 @@ Feature: subgraph with fitler Given a graph with space named "nba" Scenario: subgraph with edge filter + When executing query: + """ + GET SUBGRAPH FROM 'Tim Duncan' OUT like WHERE like.likeness > 90 YIELD vertices as v + """ + Then the result should be, in any order, with relax comparison: + | v | + | [("Tim Duncan")] | + | [("Manu Ginobili"), ("Tony Parker")] | When executing query: """ GET SUBGRAPH FROM 'Tim Duncan' OUT like WHERE like.likeness > 90 YIELD vertices as v, edges as e diff --git a/tests/tck/features/yield/parameter.feature b/tests/tck/features/yield/parameter.feature index 58bd96d71c9..f7688f2d2c5 100644 --- a/tests/tck/features/yield/parameter.feature +++ b/tests/tck/features/yield/parameter.feature @@ -61,7 +61,7 @@ Feature: Parameter | "Tony Parker" | When executing query: """ - MATCH (v:player)-[:like]->(n{name:$p7.a.b.c}) + MATCH (v:player)-[:like]->(n:player{name:$p7.a.b.c}) RETURN n.player.name AS dst LIMIT $p7.a.b.d[0] """ Then the result should be, in any order: