From 37fcf81c87f4f6fa19169e6314ce7c444c819030 Mon Sep 17 00:00:00 2001 From: "kyle.cao" Date: Wed, 17 Nov 2021 16:21:31 +0800 Subject: [PATCH] support cypher parameter(variable) --- src/graph/context/QueryContext.cpp | 6 + src/graph/context/QueryContext.h | 4 + src/graph/context/QueryExpressionContext.h | 2 +- .../executor/query/AppendVerticesExecutor.cpp | 2 +- src/graph/executor/query/GetEdgesExecutor.cpp | 2 +- .../executor/query/GetVerticesExecutor.cpp | 2 +- .../executor/query/IndexScanExecutor.cpp | 2 +- src/graph/executor/query/TraverseExecutor.cpp | 2 +- src/graph/optimizer/OptimizerUtils.cpp | 5 +- src/graph/optimizer/OptimizerUtils.h | 4 +- .../rule/GeoPredicateIndexScanBaseRule.cpp | 2 +- .../optimizer/rule/IndexFullScanBaseRule.cpp | 2 +- src/graph/optimizer/rule/IndexScanRule.cpp | 17 ++- .../OptimizeEdgeIndexScanByFilterRule.cpp | 2 +- .../rule/OptimizeTagIndexScanByFilterRule.cpp | 2 +- .../PushLimitDownEdgeIndexFullScanRule.cpp | 5 +- .../PushLimitDownEdgeIndexPrefixScanRule.cpp | 5 +- .../PushLimitDownEdgeIndexRangeScanRule.cpp | 5 +- .../rule/PushLimitDownGetNeighborsRule.cpp | 5 +- .../rule/PushLimitDownIndexScanRule.cpp | 5 +- .../PushLimitDownTagIndexFullScanRule.cpp | 5 +- .../PushLimitDownTagIndexPrefixScanRule.cpp | 5 +- .../PushLimitDownTagIndexRangeScanRule.cpp | 5 +- .../PushStepLimitDownGetNeighborsRule.cpp | 11 +- .../PushStepSampleDownGetNeighborsRule.cpp | 3 +- src/graph/optimizer/rule/TopNRule.cpp | 2 +- .../rule/UnionAllIndexScanBaseRule.cpp | 8 +- src/graph/planner/plan/Query.cpp | 16 +- src/graph/planner/plan/Query.h | 4 +- src/graph/service/GraphService.cpp | 52 ++++++- src/graph/service/GraphService.h | 10 ++ src/graph/service/RequestContext.h | 7 + src/graph/util/ExpressionUtils.cpp | 11 +- src/graph/util/ExpressionUtils.h | 4 +- src/graph/validator/FetchEdgesValidator.cpp | 4 +- src/graph/validator/GroupByValidator.cpp | 4 +- src/graph/validator/LookupValidator.cpp | 2 +- src/graph/validator/MatchValidator.cpp | 24 +-- src/graph/validator/MutateValidator.cpp | 10 +- src/graph/validator/Validator.cpp | 2 +- src/graph/visitor/CMakeLists.txt | 1 + src/graph/visitor/DeduceTypeVisitor.cpp | 4 +- src/graph/visitor/EvaluableExprVisitor.cpp | 67 +++++++++ src/graph/visitor/EvaluableExprVisitor.h | 49 +++--- src/interface/graph.thrift | 5 +- src/parser/parser.yy | 38 ++++- tests/Makefile | 4 +- tests/common/utils.py | 8 +- tests/tck/conftest.py | 48 +++++- tests/tck/features/go/GO.IntVid.feature | 2 +- tests/tck/features/go/GO.feature | 2 +- .../tck/features/go/GoYieldVertexEdge.feature | 2 +- tests/tck/features/yield/parameter.feature | 141 ++++++++++++++++++ 53 files changed, 515 insertions(+), 126 deletions(-) create mode 100644 src/graph/visitor/EvaluableExprVisitor.cpp create mode 100644 tests/tck/features/yield/parameter.feature diff --git a/src/graph/context/QueryContext.cpp b/src/graph/context/QueryContext.cpp index 4fc2c6f65be..d22004a97a9 100644 --- a/src/graph/context/QueryContext.cpp +++ b/src/graph/context/QueryContext.cpp @@ -29,6 +29,12 @@ void QueryContext::init() { objPool_ = std::make_unique(); ep_ = std::make_unique(); ectx_ = std::make_unique(); + // copy parameterMap into ExecutionContext + if (rctx_) { + for (auto item : rctx_->parameterMap()) { + ectx_->setValue(std::move(item.first), std::move(item.second)); + } + } idGen_ = std::make_unique(0); symTable_ = std::make_unique(objPool_.get()); vctx_ = std::make_unique(std::make_unique(symTable_.get())); diff --git a/src/graph/context/QueryContext.h b/src/graph/context/QueryContext.h index 00aa8b75336..c8213879689 100644 --- a/src/graph/context/QueryContext.h +++ b/src/graph/context/QueryContext.h @@ -96,6 +96,10 @@ class QueryContext { bool isKilled() const { return killed_.load(); } + bool existParameter(const std::string& param) const { + return ectx_->exist(param) && (ectx_->getValue(param).type() != Value::Type::DATASET); + } + private: void init(); diff --git a/src/graph/context/QueryExpressionContext.h b/src/graph/context/QueryExpressionContext.h index 004f954049e..b624fb1d22a 100644 --- a/src/graph/context/QueryExpressionContext.h +++ b/src/graph/context/QueryExpressionContext.h @@ -53,7 +53,7 @@ class QueryExpressionContext final : public ExpressionContext { void setVar(const std::string&, Value val) override; - QueryExpressionContext& operator()(Iterator* iter) { + QueryExpressionContext& operator()(Iterator* iter = nullptr) { iter_ = iter; return *this; } diff --git a/src/graph/executor/query/AppendVerticesExecutor.cpp b/src/graph/executor/query/AppendVerticesExecutor.cpp index aa6088f1db5..70e3228b0a4 100644 --- a/src/graph/executor/query/AppendVerticesExecutor.cpp +++ b/src/graph/executor/query/AppendVerticesExecutor.cpp @@ -45,7 +45,7 @@ folly::Future AppendVerticesExecutor::appendVertices() { av->exprs(), av->dedup(), av->orderBy(), - av->limit(), + av->limit(qctx()), av->filter()) .via(runner()) .ensure([this, getPropsTime]() { diff --git a/src/graph/executor/query/GetEdgesExecutor.cpp b/src/graph/executor/query/GetEdgesExecutor.cpp index 4ee5ddace34..675aaf42f40 100644 --- a/src/graph/executor/query/GetEdgesExecutor.cpp +++ b/src/graph/executor/query/GetEdgesExecutor.cpp @@ -77,7 +77,7 @@ folly::Future GetEdgesExecutor::getEdges() { ge->exprs(), ge->dedup(), ge->orderBy(), - ge->limit(), + ge->limit(qctx()), ge->filter()) .via(runner()) .ensure([this, getPropsTime]() { diff --git a/src/graph/executor/query/GetVerticesExecutor.cpp b/src/graph/executor/query/GetVerticesExecutor.cpp index f3a7bd2dd59..603b05033c6 100644 --- a/src/graph/executor/query/GetVerticesExecutor.cpp +++ b/src/graph/executor/query/GetVerticesExecutor.cpp @@ -44,7 +44,7 @@ folly::Future GetVerticesExecutor::getVertices() { gv->exprs(), gv->dedup(), gv->orderBy(), - gv->limit(), + gv->limit(qctx()), gv->filter()) .via(runner()) .ensure([this, getPropsTime]() { diff --git a/src/graph/executor/query/IndexScanExecutor.cpp b/src/graph/executor/query/IndexScanExecutor.cpp index 3f3b20ae37e..2221ac26ebd 100644 --- a/src/graph/executor/query/IndexScanExecutor.cpp +++ b/src/graph/executor/query/IndexScanExecutor.cpp @@ -45,7 +45,7 @@ folly::Future IndexScanExecutor::indexScan() { lookup->isEdge(), lookup->schemaId(), lookup->returnColumns(), - lookup->limit()) + lookup->limit(qctx_)) .via(runner()) .thenValue([this](StorageRpcResponse &&rpcResp) { addStats(rpcResp, otherStats_); diff --git a/src/graph/executor/query/TraverseExecutor.cpp b/src/graph/executor/query/TraverseExecutor.cpp index ee1e638da8f..9d5ee99a653 100644 --- a/src/graph/executor/query/TraverseExecutor.cpp +++ b/src/graph/executor/query/TraverseExecutor.cpp @@ -104,7 +104,7 @@ void TraverseExecutor::getNeighbors() { finalStep ? traverse_->dedup() : false, finalStep ? traverse_->random() : false, finalStep ? traverse_->orderBy() : std::vector(), - finalStep ? traverse_->limit() : -1, + finalStep ? traverse_->limit(qctx()) : -1, finalStep ? traverse_->filter() : nullptr) .via(runner()) .thenValue([this, getNbrTime](StorageRpcResponse&& resp) mutable { diff --git a/src/graph/optimizer/OptimizerUtils.cpp b/src/graph/optimizer/OptimizerUtils.cpp index 679453a61c8..527bde6f12e 100644 --- a/src/graph/optimizer/OptimizerUtils.cpp +++ b/src/graph/optimizer/OptimizerUtils.cpp @@ -416,7 +416,8 @@ bool OptimizerUtils::relExprHasIndex( } void OptimizerUtils::copyIndexScanData(const nebula::graph::IndexScan* from, - nebula::graph::IndexScan* to) { + nebula::graph::IndexScan* to, + QueryContext* qctx) { to->setEmptyResultSet(from->isEmptyResultSet()); to->setSpace(from->space()); to->setReturnCols(from->returnColumns()); @@ -424,7 +425,7 @@ void OptimizerUtils::copyIndexScanData(const nebula::graph::IndexScan* from, to->setSchemaId(from->schemaId()); to->setDedup(from->dedup()); to->setOrderBy(from->orderBy()); - to->setLimit(from->limit()); + to->setLimit(from->limit(qctx)); to->setFilter(from->filter() == nullptr ? nullptr : from->filter()->clone()); } diff --git a/src/graph/optimizer/OptimizerUtils.h b/src/graph/optimizer/OptimizerUtils.h index d628c83020f..47cef54fa20 100644 --- a/src/graph/optimizer/OptimizerUtils.h +++ b/src/graph/optimizer/OptimizerUtils.h @@ -73,7 +73,9 @@ class OptimizerUtils { const Expression* expr, const std::vector>& indexItems); - static void copyIndexScanData(const nebula::graph::IndexScan* from, nebula::graph::IndexScan* to); + static void copyIndexScanData(const nebula::graph::IndexScan* from, + nebula::graph::IndexScan* to, + QueryContext* qctx); }; } // namespace graph diff --git a/src/graph/optimizer/rule/GeoPredicateIndexScanBaseRule.cpp b/src/graph/optimizer/rule/GeoPredicateIndexScanBaseRule.cpp index 210c37539c9..3a0af33852e 100644 --- a/src/graph/optimizer/rule/GeoPredicateIndexScanBaseRule.cpp +++ b/src/graph/optimizer/rule/GeoPredicateIndexScanBaseRule.cpp @@ -117,7 +117,7 @@ StatusOr GeoPredicateIndexScanBaseRule::transform( } auto scanNode = IndexScan::make(ctx->qctx(), nullptr); - OptimizerUtils::copyIndexScanData(scan, scanNode); + OptimizerUtils::copyIndexScanData(scan, scanNode, ctx->qctx()); scanNode->setIndexQueryContext(std::move(idxCtxs)); // TODO(jie): geo predicate's calculation is a little heavy, // which is not suitable to push down to the storage diff --git a/src/graph/optimizer/rule/IndexFullScanBaseRule.cpp b/src/graph/optimizer/rule/IndexFullScanBaseRule.cpp index 08cabbe856f..459826c5f5c 100644 --- a/src/graph/optimizer/rule/IndexFullScanBaseRule.cpp +++ b/src/graph/optimizer/rule/IndexFullScanBaseRule.cpp @@ -66,7 +66,7 @@ StatusOr IndexFullScanBaseRule::transform(OptContext* ctx, idxCtxs.emplace_back(std::move(ictx)); auto scanNode = this->scan(ctx, scan); - OptimizerUtils::copyIndexScanData(scan, scanNode); + OptimizerUtils::copyIndexScanData(scan, scanNode, ctx->qctx()); scanNode->setOutputVar(scan->outputVar()); scanNode->setColNames(scan->colNames()); scanNode->setIndexQueryContext(std::move(idxCtxs)); diff --git a/src/graph/optimizer/rule/IndexScanRule.cpp b/src/graph/optimizer/rule/IndexScanRule.cpp index a19f0554e22..0c01a513da2 100644 --- a/src/graph/optimizer/rule/IndexScanRule.cpp +++ b/src/graph/optimizer/rule/IndexScanRule.cpp @@ -6,6 +6,7 @@ #include "graph/optimizer/rule/IndexScanRule.h" #include "common/expression/LabelAttributeExpression.h" +#include "common/expression/VariableExpression.h" #include "graph/optimizer/OptContext.h" #include "graph/optimizer/OptGroup.h" #include "graph/optimizer/OptRule.h" @@ -13,6 +14,7 @@ #include "graph/planner/plan/PlanNode.h" #include "graph/planner/plan/Query.h" #include "graph/util/IndexUtil.h" +#include "graph/visitor/RewriteVisitor.h" using nebula::graph::IndexScan; using nebula::graph::IndexUtil; @@ -63,7 +65,20 @@ StatusOr IndexScanRule::transform(OptContext* ctx, } else { FilterItems items; ScanKind kind; - NG_RETURN_IF_ERROR(analyzeExpression(filter, &items, &kind, isEdge(groupNode))); + // rewrite ParameterExpression to ConstantExpression + // TODO: refactor index selector logic to avoid this rewriting + auto matcher = [qctx](const Expression* e) -> bool { + return e->kind() == Expression::Kind::kVar && + qctx->existParameter(static_cast(e)->var()); + }; + auto rewriter = [qctx](const Expression* e) -> Expression* { + DCHECK_EQ(e->kind(), Expression::Kind::kVar); + auto& v = const_cast(e)->eval(graph::QueryExpressionContext(qctx->ectx())()); + return ConstantExpression::make(qctx->objPool(), v); + }; + auto* newFilter = graph::RewriteVisitor::transform(filter, matcher, rewriter); + + NG_RETURN_IF_ERROR(analyzeExpression(newFilter, &items, &kind, isEdge(groupNode))); auto status = createIndexQueryCtx(iqctx, kind, items, qctx, groupNode); if (!status.ok()) { NG_RETURN_IF_ERROR(createIndexQueryCtx(iqctx, qctx, groupNode)); diff --git a/src/graph/optimizer/rule/OptimizeEdgeIndexScanByFilterRule.cpp b/src/graph/optimizer/rule/OptimizeEdgeIndexScanByFilterRule.cpp index 3471d0947d1..f02f025d5e7 100644 --- a/src/graph/optimizer/rule/OptimizeEdgeIndexScanByFilterRule.cpp +++ b/src/graph/optimizer/rule/OptimizeEdgeIndexScanByFilterRule.cpp @@ -82,7 +82,7 @@ EdgeIndexScan* makeEdgeIndexScan(QueryContext* qctx, const EdgeIndexScan* scan, } else { scanNode = EdgeIndexRangeScan::make(qctx, nullptr, scan->edgeType()); } - OptimizerUtils::copyIndexScanData(scan, scanNode); + OptimizerUtils::copyIndexScanData(scan, scanNode, qctx); return scanNode; } diff --git a/src/graph/optimizer/rule/OptimizeTagIndexScanByFilterRule.cpp b/src/graph/optimizer/rule/OptimizeTagIndexScanByFilterRule.cpp index 60bf9ab1f80..79160207799 100644 --- a/src/graph/optimizer/rule/OptimizeTagIndexScanByFilterRule.cpp +++ b/src/graph/optimizer/rule/OptimizeTagIndexScanByFilterRule.cpp @@ -92,7 +92,7 @@ TagIndexScan* makeTagIndexScan(QueryContext* qctx, const TagIndexScan* scan, boo tagScan = TagIndexRangeScan::make(qctx, nullptr, scan->tagName()); } - OptimizerUtils::copyIndexScanData(scan, tagScan); + OptimizerUtils::copyIndexScanData(scan, tagScan, qctx); return tagScan; } diff --git a/src/graph/optimizer/rule/PushLimitDownEdgeIndexFullScanRule.cpp b/src/graph/optimizer/rule/PushLimitDownEdgeIndexFullScanRule.cpp index 81adac8747a..c6d488da165 100644 --- a/src/graph/optimizer/rule/PushLimitDownEdgeIndexFullScanRule.cpp +++ b/src/graph/optimizer/rule/PushLimitDownEdgeIndexFullScanRule.cpp @@ -34,14 +34,15 @@ const Pattern &PushLimitDownEdgeIndexFullScanRule::pattern() const { StatusOr PushLimitDownEdgeIndexFullScanRule::transform( OptContext *octx, const MatchedResult &matched) const { + auto *qctx = octx->qctx(); auto limitGroupNode = matched.node; auto indexScanGroupNode = matched.dependencies.front().node; const auto limit = static_cast(limitGroupNode->node()); const auto indexScan = static_cast(indexScanGroupNode->node()); - int64_t limitRows = limit->offset() + limit->count(); - if (indexScan->limit() >= 0 && limitRows >= indexScan->limit()) { + int64_t limitRows = limit->offset() + limit->count(qctx); + if (indexScan->limit(qctx) >= 0 && limitRows >= indexScan->limit(qctx)) { return TransformResult::noTransform(); } diff --git a/src/graph/optimizer/rule/PushLimitDownEdgeIndexPrefixScanRule.cpp b/src/graph/optimizer/rule/PushLimitDownEdgeIndexPrefixScanRule.cpp index c5488e6df7d..f34980bc979 100644 --- a/src/graph/optimizer/rule/PushLimitDownEdgeIndexPrefixScanRule.cpp +++ b/src/graph/optimizer/rule/PushLimitDownEdgeIndexPrefixScanRule.cpp @@ -36,14 +36,15 @@ const Pattern &PushLimitDownEdgeIndexPrefixScanRule::pattern() const { StatusOr PushLimitDownEdgeIndexPrefixScanRule::transform( OptContext *octx, const MatchedResult &matched) const { + auto *qctx = octx->qctx(); auto limitGroupNode = matched.node; auto indexScanGroupNode = matched.dependencies.front().node; const auto limit = static_cast(limitGroupNode->node()); const auto indexScan = static_cast(indexScanGroupNode->node()); - int64_t limitRows = limit->offset() + limit->count(); - if (indexScan->limit() >= 0 && limitRows >= indexScan->limit()) { + int64_t limitRows = limit->offset() + limit->count(qctx); + if (indexScan->limit(qctx) >= 0 && limitRows >= indexScan->limit(qctx)) { return TransformResult::noTransform(); } diff --git a/src/graph/optimizer/rule/PushLimitDownEdgeIndexRangeScanRule.cpp b/src/graph/optimizer/rule/PushLimitDownEdgeIndexRangeScanRule.cpp index a5ca98d103e..8dd8dc3f2ab 100644 --- a/src/graph/optimizer/rule/PushLimitDownEdgeIndexRangeScanRule.cpp +++ b/src/graph/optimizer/rule/PushLimitDownEdgeIndexRangeScanRule.cpp @@ -34,14 +34,15 @@ const Pattern &PushLimitDownEdgeIndexRangeScanRule::pattern() const { StatusOr PushLimitDownEdgeIndexRangeScanRule::transform( OptContext *octx, const MatchedResult &matched) const { + auto *qctx = octx->qctx(); auto limitGroupNode = matched.node; auto indexScanGroupNode = matched.dependencies.front().node; const auto limit = static_cast(limitGroupNode->node()); const auto indexScan = static_cast(indexScanGroupNode->node()); - int64_t limitRows = limit->offset() + limit->count(); - if (indexScan->limit() >= 0 && limitRows >= indexScan->limit()) { + int64_t limitRows = limit->offset() + limit->count(qctx); + if (indexScan->limit(qctx) >= 0 && limitRows >= indexScan->limit(qctx)) { return TransformResult::noTransform(); } diff --git a/src/graph/optimizer/rule/PushLimitDownGetNeighborsRule.cpp b/src/graph/optimizer/rule/PushLimitDownGetNeighborsRule.cpp index 85a2ee6ff92..0b3fba2244f 100644 --- a/src/graph/optimizer/rule/PushLimitDownGetNeighborsRule.cpp +++ b/src/graph/optimizer/rule/PushLimitDownGetNeighborsRule.cpp @@ -34,6 +34,7 @@ const Pattern &PushLimitDownGetNeighborsRule::pattern() const { StatusOr PushLimitDownGetNeighborsRule::transform( OptContext *octx, const MatchedResult &matched) const { + auto *qctx = octx->qctx(); auto limitGroupNode = matched.node; auto gnGroupNode = matched.dependencies.front().node; @@ -43,8 +44,8 @@ StatusOr PushLimitDownGetNeighborsRule::transform( if (!graph::ExpressionUtils::isEvaluableExpr(limit->countExpr())) { return TransformResult::noTransform(); } - int64_t limitRows = limit->offset() + limit->count(); - if (gn->limit() >= 0 && limitRows >= gn->limit()) { + int64_t limitRows = limit->offset() + limit->count(qctx); + if (gn->limit(qctx) >= 0 && limitRows >= gn->limit(qctx)) { return TransformResult::noTransform(); } diff --git a/src/graph/optimizer/rule/PushLimitDownIndexScanRule.cpp b/src/graph/optimizer/rule/PushLimitDownIndexScanRule.cpp index f1bf69c6770..089b7d042ed 100644 --- a/src/graph/optimizer/rule/PushLimitDownIndexScanRule.cpp +++ b/src/graph/optimizer/rule/PushLimitDownIndexScanRule.cpp @@ -32,14 +32,15 @@ const Pattern &PushLimitDownIndexScanRule::pattern() const { StatusOr PushLimitDownIndexScanRule::transform( OptContext *octx, const MatchedResult &matched) const { + auto *qctx = octx->qctx(); auto limitGroupNode = matched.node; auto indexScanGroupNode = matched.dependencies.front().node; const auto limit = static_cast(limitGroupNode->node()); const auto indexScan = static_cast(indexScanGroupNode->node()); - int64_t limitRows = limit->offset() + limit->count(); - if (indexScan->limit() >= 0 && limitRows >= indexScan->limit()) { + int64_t limitRows = limit->offset() + limit->count(qctx); + if (indexScan->limit(qctx) >= 0 && limitRows >= indexScan->limit(qctx)) { return TransformResult::noTransform(); } diff --git a/src/graph/optimizer/rule/PushLimitDownTagIndexFullScanRule.cpp b/src/graph/optimizer/rule/PushLimitDownTagIndexFullScanRule.cpp index 6a34a7b1bfe..c48defaaede 100644 --- a/src/graph/optimizer/rule/PushLimitDownTagIndexFullScanRule.cpp +++ b/src/graph/optimizer/rule/PushLimitDownTagIndexFullScanRule.cpp @@ -33,14 +33,15 @@ const Pattern &PushLimitDownTagIndexFullScanRule::pattern() const { StatusOr PushLimitDownTagIndexFullScanRule::transform( OptContext *octx, const MatchedResult &matched) const { + auto *qctx = octx->qctx(); auto limitGroupNode = matched.node; auto indexScanGroupNode = matched.dependencies.front().node; const auto limit = static_cast(limitGroupNode->node()); const auto indexScan = static_cast(indexScanGroupNode->node()); - int64_t limitRows = limit->offset() + limit->count(); - if (indexScan->limit() >= 0 && limitRows >= indexScan->limit()) { + int64_t limitRows = limit->offset() + limit->count(qctx); + if (indexScan->limit(qctx) >= 0 && limitRows >= indexScan->limit(qctx)) { return TransformResult::noTransform(); } diff --git a/src/graph/optimizer/rule/PushLimitDownTagIndexPrefixScanRule.cpp b/src/graph/optimizer/rule/PushLimitDownTagIndexPrefixScanRule.cpp index 4e2fbd8b02d..905492d3632 100644 --- a/src/graph/optimizer/rule/PushLimitDownTagIndexPrefixScanRule.cpp +++ b/src/graph/optimizer/rule/PushLimitDownTagIndexPrefixScanRule.cpp @@ -33,14 +33,15 @@ const Pattern &PushLimitDownTagIndexPrefixScanRule::pattern() const { StatusOr PushLimitDownTagIndexPrefixScanRule::transform( OptContext *octx, const MatchedResult &matched) const { + auto *qctx = octx->qctx(); auto limitGroupNode = matched.node; auto indexScanGroupNode = matched.dependencies.front().node; const auto limit = static_cast(limitGroupNode->node()); const auto indexScan = static_cast(indexScanGroupNode->node()); - int64_t limitRows = limit->offset() + limit->count(); - if (indexScan->limit() >= 0 && limitRows >= indexScan->limit()) { + int64_t limitRows = limit->offset() + limit->count(qctx); + if (indexScan->limit(qctx) >= 0 && limitRows >= indexScan->limit(qctx)) { return TransformResult::noTransform(); } diff --git a/src/graph/optimizer/rule/PushLimitDownTagIndexRangeScanRule.cpp b/src/graph/optimizer/rule/PushLimitDownTagIndexRangeScanRule.cpp index 209deac0482..7642108f715 100644 --- a/src/graph/optimizer/rule/PushLimitDownTagIndexRangeScanRule.cpp +++ b/src/graph/optimizer/rule/PushLimitDownTagIndexRangeScanRule.cpp @@ -33,14 +33,15 @@ const Pattern &PushLimitDownTagIndexRangeScanRule::pattern() const { StatusOr PushLimitDownTagIndexRangeScanRule::transform( OptContext *octx, const MatchedResult &matched) const { + auto *qctx = octx->qctx(); auto limitGroupNode = matched.node; auto indexScanGroupNode = matched.dependencies.front().node; const auto limit = static_cast(limitGroupNode->node()); const auto indexScan = static_cast(indexScanGroupNode->node()); - int64_t limitRows = limit->offset() + limit->count(); - if (indexScan->limit() >= 0 && limitRows >= indexScan->limit()) { + int64_t limitRows = limit->offset() + limit->count(qctx); + if (indexScan->limit(qctx) >= 0 && limitRows >= indexScan->limit(qctx)) { return TransformResult::noTransform(); } diff --git a/src/graph/optimizer/rule/PushStepLimitDownGetNeighborsRule.cpp b/src/graph/optimizer/rule/PushStepLimitDownGetNeighborsRule.cpp index 4cc457b95bc..9fd4d486eae 100644 --- a/src/graph/optimizer/rule/PushStepLimitDownGetNeighborsRule.cpp +++ b/src/graph/optimizer/rule/PushStepLimitDownGetNeighborsRule.cpp @@ -39,11 +39,12 @@ StatusOr PushStepLimitDownGetNeighborsRule::transform( const auto limit = static_cast(limitGroupNode->node()); const auto gn = static_cast(gnGroupNode->node()); - - if (gn->limitExpr() != nullptr && graph::ExpressionUtils::isEvaluableExpr(gn->limitExpr()) && - graph::ExpressionUtils::isEvaluableExpr(limit->countExpr())) { - int64_t limitRows = limit->offset() + limit->count(); - int64_t gnLimit = gn->limit(); + auto *qctx = octx->qctx(); + if (gn->limitExpr() != nullptr && + graph::ExpressionUtils::isEvaluableExpr(gn->limitExpr(), qctx) && + graph::ExpressionUtils::isEvaluableExpr(limit->countExpr(), qctx)) { + int64_t limitRows = limit->offset() + limit->count(qctx); + int64_t gnLimit = gn->limit(qctx); if (gnLimit >= 0 && limitRows >= gnLimit) { return TransformResult::noTransform(); } diff --git a/src/graph/optimizer/rule/PushStepSampleDownGetNeighborsRule.cpp b/src/graph/optimizer/rule/PushStepSampleDownGetNeighborsRule.cpp index 39beb7be84d..bc271ff09ac 100644 --- a/src/graph/optimizer/rule/PushStepSampleDownGetNeighborsRule.cpp +++ b/src/graph/optimizer/rule/PushStepSampleDownGetNeighborsRule.cpp @@ -39,11 +39,10 @@ StatusOr PushStepSampleDownGetNeighborsRule::transform const auto sample = static_cast(sampleGroupNode->node()); const auto gn = static_cast(gnGroupNode->node()); - if (gn->limitExpr() != nullptr && graph::ExpressionUtils::isEvaluableExpr(gn->limitExpr()) && graph::ExpressionUtils::isEvaluableExpr(sample->countExpr())) { int64_t limitRows = sample->count(); - int64_t gnLimit = gn->limit(); + int64_t gnLimit = gn->limit(octx->qctx()); if (gnLimit >= 0 && limitRows >= gnLimit) { return TransformResult::noTransform(); } diff --git a/src/graph/optimizer/rule/TopNRule.cpp b/src/graph/optimizer/rule/TopNRule.cpp index 17171fc4872..a9a29176a7b 100644 --- a/src/graph/optimizer/rule/TopNRule.cpp +++ b/src/graph/optimizer/rule/TopNRule.cpp @@ -43,7 +43,7 @@ StatusOr TopNRule::transform(OptContext *ctx, } auto qctx = ctx->qctx(); - auto topn = TopN::make(qctx, nullptr, sort->factors(), limit->offset(), limit->count()); + auto topn = TopN::make(qctx, nullptr, sort->factors(), limit->offset(), limit->count(qctx)); topn->setOutputVar(limit->outputVar()); topn->setInputVar(sort->inputVar()); topn->setColNames(sort->colNames()); diff --git a/src/graph/optimizer/rule/UnionAllIndexScanBaseRule.cpp b/src/graph/optimizer/rule/UnionAllIndexScanBaseRule.cpp index 2c3904b6df8..41a60f68186 100644 --- a/src/graph/optimizer/rule/UnionAllIndexScanBaseRule.cpp +++ b/src/graph/optimizer/rule/UnionAllIndexScanBaseRule.cpp @@ -90,8 +90,8 @@ StatusOr UnionAllIndexScanBaseRule::transform(OptContext* ctx, auto filter = static_cast(matched.planNode()); auto node = matched.planNode({0, 0}); auto scan = static_cast(node); - - auto metaClient = ctx->qctx()->getMetaClient(); + auto* qctx = ctx->qctx(); + auto metaClient = qctx->getMetaClient(); auto status = node->kind() == graph::PlanNode::Kind::kTagIndexFullScan ? metaClient->getTagIndexesFromCache(scan->space()) : metaClient->getEdgeIndexesFromCache(scan->space()); @@ -170,8 +170,8 @@ StatusOr UnionAllIndexScanBaseRule::transform(OptContext* ctx, idxCtxs.emplace_back(std::move(ictx)); } - auto scanNode = IndexScan::make(ctx->qctx(), nullptr); - OptimizerUtils::copyIndexScanData(scan, scanNode); + auto scanNode = IndexScan::make(qctx, nullptr); + OptimizerUtils::copyIndexScanData(scan, scanNode, qctx); scanNode->setIndexQueryContext(std::move(idxCtxs)); scanNode->setOutputVar(filter->outputVar()); scanNode->setColNames(filter->colNames()); diff --git a/src/graph/planner/plan/Query.cpp b/src/graph/planner/plan/Query.cpp index b45c4bb88a6..7402e9f26fa 100644 --- a/src/graph/planner/plan/Query.cpp +++ b/src/graph/planner/plan/Query.cpp @@ -18,11 +18,11 @@ using folly::stringPrintf; namespace nebula { namespace graph { -int64_t Explore::limit() const { - QueryExpressionContext ctx; - DCHECK(ExpressionUtils::isEvaluableExpr(limit_)); - return DCHECK_NOTNULL(limit_)->eval(ctx).getInt(); +int64_t Explore::limit(QueryContext* qctx) const { + DCHECK(ExpressionUtils::isEvaluableExpr(limit_, qctx)); + return DCHECK_NOTNULL(limit_)->eval(QueryExpressionContext(qctx->ectx())()).getInt(); } + std::unique_ptr Explore::explain() const { auto desc = SingleInputNode::explain(); addDescription("space", folly::to(space_), desc.get()); @@ -325,16 +325,16 @@ void Sort::cloneMembers(const Sort& p) { } // Get constant count value -int64_t Limit::count() const { +int64_t Limit::count(QueryContext* qctx) const { if (count_ == nullptr) { return -1; } - DCHECK(ExpressionUtils::isEvaluableExpr(count_)); - QueryExpressionContext ctx; - auto s = count_->eval(ctx).getInt(); + DCHECK(ExpressionUtils::isEvaluableExpr(count_, qctx)); + auto s = count_->eval(QueryExpressionContext(qctx->ectx())()).getInt(); DCHECK_GE(s, 0); return s; } + std::unique_ptr Limit::explain() const { auto desc = SingleInputNode::explain(); addDescription("offset", folly::to(offset_), desc.get()); diff --git a/src/graph/planner/plan/Query.h b/src/graph/planner/plan/Query.h index 076b7471b53..416314e5775 100644 --- a/src/graph/planner/plan/Query.h +++ b/src/graph/planner/plan/Query.h @@ -35,7 +35,7 @@ class Explore : public SingleInputNode { bool dedup() const { return dedup_; } // Get the constant limit value - int64_t limit() const; + int64_t limit(QueryContext* qctx) const; // Get the limit value in runtime int64_t limit(QueryExpressionContext& ctx) const { @@ -678,7 +678,7 @@ class Limit final : public SingleInputNode { int64_t offset() const { return offset_; } // Get constant count value - int64_t count() const; + int64_t count(QueryContext* qctx) const; // Get count in runtime int64_t count(QueryExpressionContext& ctx) const { if (count_ == nullptr) { diff --git a/src/graph/service/GraphService.cpp b/src/graph/service/GraphService.cpp index 3b58c3be851..6854aaa6411 100644 --- a/src/graph/service/GraphService.cpp +++ b/src/graph/service/GraphService.cpp @@ -114,6 +114,48 @@ void GraphService::signout(int64_t sessionId) { sessionManager_->removeSession(sessionId); } +folly::Future GraphService::future_executeWithParameter( + int64_t sessionId, + const std::string& query, + const std::unordered_map& parameterMap) { + auto ctx = std::make_unique>(); + ctx->setQuery(query); + ctx->setRunner(getThreadManager()); + ctx->setSessionMgr(sessionManager_.get()); + auto future = ctx->future(); + stats::StatsManager::addValue(kNumQueries); + // When the sessionId is 0, it means the clients to ping the connection is ok + if (sessionId == 0) { + ctx->resp().errorCode = ErrorCode::E_SESSION_INVALID; + ctx->resp().errorMsg = std::make_unique("Invalid session id"); + ctx->finish(); + return future; + } + auto cb = [this, sessionId, ctx = std::move(ctx), parameterMap = std::move(parameterMap)]( + StatusOr> ret) mutable { + if (!ret.ok()) { + LOG(ERROR) << "Get session for sessionId: " << sessionId << " failed: " << ret.status(); + ctx->resp().errorCode = ErrorCode::E_SESSION_INVALID; + ctx->resp().errorMsg.reset(new std::string(folly::stringPrintf( + "Get sessionId[%ld] failed: %s", sessionId, ret.status().toString().c_str()))); + return ctx->finish(); + } + auto sessionPtr = std::move(ret).value(); + if (sessionPtr == nullptr) { + LOG(ERROR) << "Get session for sessionId: " << sessionId << " is nullptr"; + ctx->resp().errorCode = ErrorCode::E_SESSION_INVALID; + ctx->resp().errorMsg.reset( + new std::string(folly::stringPrintf("SessionId[%ld] does not exist", sessionId))); + return ctx->finish(); + } + ctx->setSession(std::move(sessionPtr)); + ctx->setParameterMap(parameterMap); + queryEngine_->execute(std::move(ctx)); + }; + sessionManager_->findSession(sessionId, getThreadManager()).thenValue(std::move(cb)); + return future; +} + folly::Future GraphService::future_execute(int64_t sessionId, const std::string& query) { auto ctx = std::make_unique>(); @@ -155,7 +197,15 @@ folly::Future GraphService::future_execute(int64_t sessionId, folly::Future GraphService::future_executeJson(int64_t sessionId, const std::string& query) { - return future_execute(sessionId, query).thenValue([](ExecutionResponse&& resp) { + return future_executeJsonWithParameter( + sessionId, query, std::unordered_map{}); +} + +folly::Future GraphService::future_executeJsonWithParameter( + int64_t sessionId, + const std::string& query, + const std::unordered_map& parameterMap) { + return future_executeWithParameter(sessionId, query, parameterMap).thenValue([](auto&& resp) { return folly::toJson(resp.toJson()); }); } diff --git a/src/graph/service/GraphService.h b/src/graph/service/GraphService.h index c0326066bd5..bcb38993325 100644 --- a/src/graph/service/GraphService.h +++ b/src/graph/service/GraphService.h @@ -32,9 +32,19 @@ class GraphService final : public cpp2::GraphServiceSvIf { void signout(int64_t /*sessionId*/) override; + folly::Future future_executeWithParameter( + int64_t sessionId, + const std::string& stmt, + const std::unordered_map& parameterMap) override; + folly::Future future_execute(int64_t sessionId, const std::string& stmt) override; + folly::Future future_executeJsonWithParameter( + int64_t sessionId, + const std::string& stmt, + const std::unordered_map& parameterMap) override; + folly::Future future_executeJson(int64_t sessionId, const std::string& stmt) override; diff --git a/src/graph/service/RequestContext.h b/src/graph/service/RequestContext.h index a6ccc688543..ae669e64a49 100644 --- a/src/graph/service/RequestContext.h +++ b/src/graph/service/RequestContext.h @@ -67,6 +67,12 @@ class RequestContext final : public cpp::NonCopyable, public cpp::NonMovable { GraphSessionManager* sessionMgr() const { return sessionMgr_; } + void setParameterMap(std::unordered_map parameterMap) { + parameterMap_ = std::move(parameterMap); + } + + const std::unordered_map& parameterMap() const { return parameterMap_; } + private: time::Duration duration_; std::string query_; @@ -75,6 +81,7 @@ class RequestContext final : public cpp::NonCopyable, public cpp::NonMovable { std::shared_ptr session_; folly::Executor* runner_{nullptr}; GraphSessionManager* sessionMgr_{nullptr}; + std::unordered_map parameterMap_; }; } // namespace graph diff --git a/src/graph/util/ExpressionUtils.cpp b/src/graph/util/ExpressionUtils.cpp index ee3c7913ad0..688124e392b 100644 --- a/src/graph/util/ExpressionUtils.cpp +++ b/src/graph/util/ExpressionUtils.cpp @@ -10,6 +10,7 @@ #include "common/base/ObjectPool.h" #include "common/expression/PropertyExpression.h" #include "common/function/AggFunctionManager.h" +#include "graph/context/QueryContext.h" #include "graph/context/QueryExpressionContext.h" #include "graph/visitor/FoldConstantExprVisitor.h" @@ -63,10 +64,11 @@ std::vector ExpressionUtils::collectAll( return std::move(visitor).results(); } -bool ExpressionUtils::checkVarExprIfExist(const Expression *expr) { +bool ExpressionUtils::checkVarExprIfExist(const Expression *expr, const QueryContext *qctx) { auto vars = ExpressionUtils::collectAll(expr, {Expression::Kind::kVar}); for (auto *var : vars) { - if (!static_cast(var)->isInner()) { + auto *varExpr = static_cast(var); + if (!varExpr->isInner() && !qctx->existParameter(varExpr->var())) { return true; } } @@ -110,8 +112,8 @@ bool ExpressionUtils::isConstExpr(const Expression *expr) { Expression::Kind::kEdge}); } -bool ExpressionUtils::isEvaluableExpr(const Expression *expr) { - EvaluableExprVisitor visitor; +bool ExpressionUtils::isEvaluableExpr(const Expression *expr, const QueryContext *qctx) { + EvaluableExprVisitor visitor(qctx); const_cast(expr)->accept(&visitor); return visitor.ok(); } @@ -437,6 +439,7 @@ Expression *ExpressionUtils::rewriteRelExprHelper(const Expression *expr, e->kind() == Expression::Kind::kDivision) return false; auto arithExpr = static_cast(e); + return ExpressionUtils::isEvaluableExpr(arithExpr->left()) || ExpressionUtils::isEvaluableExpr(arithExpr->right()); }; diff --git a/src/graph/util/ExpressionUtils.h b/src/graph/util/ExpressionUtils.h index c6d1fa5b87b..8f9aae76504 100644 --- a/src/graph/util/ExpressionUtils.h +++ b/src/graph/util/ExpressionUtils.h @@ -44,7 +44,7 @@ class ExpressionUtils { static std::vector collectAll( const Expression* self, const std::unordered_set& expected); - static bool checkVarExprIfExist(const Expression* expr); + static bool checkVarExprIfExist(const Expression* expr, const QueryContext* qctx); static std::vector findAllStorage(const Expression* expr); @@ -53,7 +53,7 @@ class ExpressionUtils { // **Expression type check** static bool isConstExpr(const Expression* expr); - static bool isEvaluableExpr(const Expression* expr); + static bool isEvaluableExpr(const Expression* expr, const QueryContext* qctx = nullptr); static Expression* rewriteLabelAttr2TagProp(const Expression* expr); diff --git a/src/graph/validator/FetchEdgesValidator.cpp b/src/graph/validator/FetchEdgesValidator.cpp index d0f5c0a2b4e..df527c3af7a 100644 --- a/src/graph/validator/FetchEdgesValidator.cpp +++ b/src/graph/validator/FetchEdgesValidator.cpp @@ -104,7 +104,7 @@ Status FetchEdgesValidator::validateEdgeKey() { auto keys = sentence->keys()->keys(); edgeKeys.rows.reserve(keys.size()); for (const auto &key : keys) { - if (!ExpressionUtils::isEvaluableExpr(key->srcid())) { + if (!ExpressionUtils::isEvaluableExpr(key->srcid(), qctx_)) { return Status::SemanticError("`%s' is not evaluable.", key->srcid()->toString().c_str()); } auto src = key->srcid()->eval(ctx); @@ -115,7 +115,7 @@ Status FetchEdgesValidator::validateEdgeKey() { } auto ranking = key->rank(); - if (!ExpressionUtils::isEvaluableExpr(key->dstid())) { + if (!ExpressionUtils::isEvaluableExpr(key->dstid(), qctx_)) { return Status::SemanticError("`%s' is not evaluable.", key->dstid()->toString().c_str()); } auto dst = key->dstid()->eval(ctx); diff --git a/src/graph/validator/GroupByValidator.cpp b/src/graph/validator/GroupByValidator.cpp index 1c12f217450..9687ebff384 100644 --- a/src/graph/validator/GroupByValidator.cpp +++ b/src/graph/validator/GroupByValidator.cpp @@ -59,7 +59,7 @@ Status GroupByValidator::validateYield(const YieldClause* yieldClause) { if (colExpr->kind() == Expression::Kind::kAggregate) { auto* aggExpr = static_cast(colExpr); NG_RETURN_IF_ERROR(ExpressionUtils::checkAggExpr(aggExpr)); - } else if (!ExpressionUtils::isEvaluableExpr(colExpr)) { + } else if (!ExpressionUtils::isEvaluableExpr(colExpr, qctx_)) { yieldCols_.emplace_back(colExpr); } @@ -173,7 +173,7 @@ Status GroupByValidator::groupClauseSemanticCheck() { return false; }; for (auto* expr : yieldCols_) { - if (ExpressionUtils::isEvaluableExpr(expr)) { + if (ExpressionUtils::isEvaluableExpr(expr, qctx_)) { continue; } FindVisitor visitor(finder); diff --git a/src/graph/validator/LookupValidator.cpp b/src/graph/validator/LookupValidator.cpp index 6426b8975fc..001a9ac49d2 100644 --- a/src/graph/validator/LookupValidator.cpp +++ b/src/graph/validator/LookupValidator.cpp @@ -419,7 +419,7 @@ StatusOr LookupValidator::checkConstExpr(Expression* expr, const std::string& prop, const ExprKind kind) { auto* pool = expr->getObjPool(); - if (!ExpressionUtils::isEvaluableExpr(expr)) { + if (!ExpressionUtils::isEvaluableExpr(expr, qctx_)) { return Status::SemanticError("'%s' is not an evaluable expression.", expr->toString().c_str()); } auto schemaMgr = qctx_->schemaMng(); diff --git a/src/graph/validator/MatchValidator.cpp b/src/graph/validator/MatchValidator.cpp index 0327cd3411e..ea0073b81a0 100644 --- a/src/graph/validator/MatchValidator.cpp +++ b/src/graph/validator/MatchValidator.cpp @@ -531,7 +531,7 @@ StatusOr MatchValidator::makeEdgeSubFilter(MapExpression *map) con auto foldStatus = ExpressionUtils::foldConstantExpr(items[0].second); NG_RETURN_IF_ERROR(foldStatus); auto foldExpr = foldStatus.value(); - if (!ExpressionUtils::isEvaluableExpr(foldExpr)) { + if (!ExpressionUtils::isEvaluableExpr(foldExpr, qctx_)) { return Status::SemanticError("Props must be evaluable: `%s'", items[0].second->toString().c_str()); } @@ -542,7 +542,7 @@ StatusOr MatchValidator::makeEdgeSubFilter(MapExpression *map) con foldStatus = ExpressionUtils::foldConstantExpr(items[i].second); NG_RETURN_IF_ERROR(foldStatus); foldExpr = foldStatus.value(); - if (!ExpressionUtils::isEvaluableExpr(foldExpr)) { + if (!ExpressionUtils::isEvaluableExpr(foldExpr, qctx_)) { return Status::SemanticError("Props must be evaluable: `%s'", items[i].second->toString().c_str()); } @@ -574,7 +574,7 @@ StatusOr MatchValidator::makeNodeSubFilter(MapExpression *map, auto foldStatus = ExpressionUtils::foldConstantExpr(items[0].second); NG_RETURN_IF_ERROR(foldStatus); auto foldExpr = foldStatus.value(); - if (!ExpressionUtils::isEvaluableExpr(foldExpr)) { + if (!ExpressionUtils::isEvaluableExpr(foldExpr, qctx_)) { return Status::SemanticError("Props must be evaluable: `%s'", items[0].second->toString().c_str()); } @@ -585,7 +585,7 @@ StatusOr MatchValidator::makeNodeSubFilter(MapExpression *map, foldStatus = ExpressionUtils::foldConstantExpr(items[i].second); NG_RETURN_IF_ERROR(foldStatus); foldExpr = foldStatus.value(); - if (!ExpressionUtils::isEvaluableExpr(foldExpr)) { + if (!ExpressionUtils::isEvaluableExpr(foldExpr, qctx_)) { return Status::SemanticError("Props must be evaluable: `%s'", items[i].second->toString().c_str()); } @@ -642,11 +642,10 @@ Status MatchValidator::validatePagination(const Expression *skipExpr, int64_t skip = 0; int64_t limit = std::numeric_limits::max(); if (skipExpr != nullptr) { - if (!ExpressionUtils::isEvaluableExpr(skipExpr)) { + if (!ExpressionUtils::isEvaluableExpr(skipExpr, qctx_)) { return Status::SemanticError("SKIP should be instantly evaluable"); } - QueryExpressionContext ctx; - auto value = const_cast(skipExpr)->eval(ctx); + auto value = const_cast(skipExpr)->eval(QueryExpressionContext(qctx_->ectx())()); if (!value.isInt()) { return Status::SemanticError("SKIP should be of type integer"); } @@ -657,11 +656,10 @@ Status MatchValidator::validatePagination(const Expression *skipExpr, } if (limitExpr != nullptr) { - if (!ExpressionUtils::isEvaluableExpr(limitExpr)) { + if (!ExpressionUtils::isEvaluableExpr(limitExpr, qctx_)) { return Status::SemanticError("SKIP should be instantly evaluable"); } - QueryExpressionContext ctx; - auto value = const_cast(limitExpr)->eval(ctx); + auto value = const_cast(limitExpr)->eval(QueryExpressionContext(qctx_->ectx())()); if (!value.isInt()) { return Status::SemanticError("LIMIT should be of type integer"); } @@ -693,7 +691,9 @@ Status MatchValidator::validateOrderBy(const OrderFactors *factors, } for (auto &factor : factors->factors()) { - if (factor->expr()->kind() != Expression::Kind::kLabel) { + auto factorExpr = factor->expr(); + if (ExpressionUtils::isEvaluableExpr(factorExpr, qctx_)) continue; + if (factorExpr->kind() != Expression::Kind::kLabel) { return Status::SemanticError("Only column name can be used as sort item"); } auto &name = static_cast(factor->expr())->name(); @@ -742,7 +742,7 @@ Status MatchValidator::validateGroup(YieldClauseContext &yieldCtx) const { if (colExpr->kind() == Expression::Kind::kAggregate) { auto *aggExpr = static_cast(colExpr); NG_RETURN_IF_ERROR(ExpressionUtils::checkAggExpr(aggExpr)); - } else if (!ExpressionUtils::isEvaluableExpr(colExpr)) { + } else if (!ExpressionUtils::isEvaluableExpr(colExpr, qctx_)) { yieldCtx.groupKeys_.emplace_back(colExpr); } diff --git a/src/graph/validator/MutateValidator.cpp b/src/graph/validator/MutateValidator.cpp index 9fbb5500a85..4f09289e531 100644 --- a/src/graph/validator/MutateValidator.cpp +++ b/src/graph/validator/MutateValidator.cpp @@ -89,7 +89,7 @@ Status InsertVerticesValidator::prepareVertices() { if (propSize_ != row->values().size()) { return Status::SemanticError("Column count doesn't match value count."); } - if (!ExpressionUtils::isEvaluableExpr(row->id())) { + if (!ExpressionUtils::isEvaluableExpr(row->id(), qctx_)) { LOG(ERROR) << "Wrong vid expression `" << row->id()->toString() << "\""; return Status::SemanticError("Wrong vid expression `%s'", row->id()->toString().c_str()); } @@ -99,7 +99,7 @@ Status InsertVerticesValidator::prepareVertices() { // check value expr for (auto &value : row->values()) { - if (!ExpressionUtils::isEvaluableExpr(value)) { + if (!ExpressionUtils::isEvaluableExpr(value, qctx_)) { LOG(ERROR) << "Insert wrong value: `" << value->toString() << "'."; return Status::SemanticError("Insert wrong value: `%s'.", value->toString().c_str()); } @@ -203,13 +203,13 @@ Status InsertEdgesValidator::prepareEdges() { if (propNames_.size() != row->values().size()) { return Status::SemanticError("Column count doesn't match value count."); } - if (!ExpressionUtils::isEvaluableExpr(row->srcid())) { + if (!ExpressionUtils::isEvaluableExpr(row->srcid(), qctx_)) { LOG(ERROR) << "Wrong src vid expression `" << row->srcid()->toString() << "\""; return Status::SemanticError("Wrong src vid expression `%s'", row->srcid()->toString().c_str()); } - if (!ExpressionUtils::isEvaluableExpr(row->dstid())) { + if (!ExpressionUtils::isEvaluableExpr(row->dstid(), qctx_)) { LOG(ERROR) << "Wrong dst vid expression `" << row->dstid()->toString() << "\""; return Status::SemanticError("Wrong dst vid expression `%s'", row->dstid()->toString().c_str()); @@ -226,7 +226,7 @@ Status InsertEdgesValidator::prepareEdges() { // check value expr for (auto &value : row->values()) { - if (!ExpressionUtils::isEvaluableExpr(value)) { + if (!ExpressionUtils::isEvaluableExpr(value, qctx_)) { LOG(ERROR) << "Insert wrong value: `" << value->toString() << "'."; return Status::SemanticError("Insert wrong value: `%s'.", value->toString().c_str()); } diff --git a/src/graph/validator/Validator.cpp b/src/graph/validator/Validator.cpp index 687d4731492..fd4014ee707 100644 --- a/src/graph/validator/Validator.cpp +++ b/src/graph/validator/Validator.cpp @@ -437,7 +437,7 @@ Status Validator::validateStarts(const VerticesClause* clause, Starts& starts) { auto vidList = clause->vidList(); QueryExpressionContext ctx; for (auto* expr : vidList) { - if (!ExpressionUtils::isEvaluableExpr(expr)) { + if (!ExpressionUtils::isEvaluableExpr(expr, qctx_)) { return Status::SemanticError("`%s' is not an evaluable expression.", expr->toString().c_str()); } diff --git a/src/graph/visitor/CMakeLists.txt b/src/graph/visitor/CMakeLists.txt index bce16e2f074..c02f9d91f6a 100644 --- a/src/graph/visitor/CMakeLists.txt +++ b/src/graph/visitor/CMakeLists.txt @@ -14,6 +14,7 @@ nebula_add_library( RewriteVisitor.cpp FindVisitor.cpp VidExtractVisitor.cpp + EvaluableExprVisitor.cpp ) nebula_add_subdirectory(test) diff --git a/src/graph/visitor/DeduceTypeVisitor.cpp b/src/graph/visitor/DeduceTypeVisitor.cpp index 7421bc0e0bc..0ae98f328f2 100644 --- a/src/graph/visitor/DeduceTypeVisitor.cpp +++ b/src/graph/visitor/DeduceTypeVisitor.cpp @@ -186,7 +186,7 @@ void DeduceTypeVisitor::visit(TypeCastingExpression *expr) { return; } - EvaluableExprVisitor visitor; + EvaluableExprVisitor visitor(qctx_); expr->operand()->accept(&visitor); if (!visitor.ok()) { @@ -488,7 +488,7 @@ void DeduceTypeVisitor::visit(InputPropertyExpression *expr) { void DeduceTypeVisitor::visit(VariablePropertyExpression *expr) { const auto &var = expr->sym(); - if (!vctx_->existVar(var)) { + if (!vctx_->existVar(var) && !qctx_->existParameter(var)) { status_ = Status::SemanticError( "`%s', not exist variable `%s'", expr->toString().c_str(), var.c_str()); return; diff --git a/src/graph/visitor/EvaluableExprVisitor.cpp b/src/graph/visitor/EvaluableExprVisitor.cpp new file mode 100644 index 00000000000..0169334ab02 --- /dev/null +++ b/src/graph/visitor/EvaluableExprVisitor.cpp @@ -0,0 +1,67 @@ +/* Copyright (c) 2021 vesoft inc. All rights reserved. + * + * This source code is licensed under Apache 2.0 License. + */ + +#include "graph/visitor/EvaluableExprVisitor.h" + +#include "graph/context/QueryContext.h" + +namespace nebula { +namespace graph { + +EvaluableExprVisitor::EvaluableExprVisitor(const QueryContext *qctx) : qctx_(qctx) {} + +void EvaluableExprVisitor::visit(ConstantExpression *) { isEvaluable_ = true; } + +void EvaluableExprVisitor::visit(LabelExpression *) { isEvaluable_ = false; } + +void EvaluableExprVisitor::visit(UUIDExpression *) { isEvaluable_ = false; } + +void EvaluableExprVisitor::visit(VariableExpression *expr) { + isEvaluable_ = (qctx_ && qctx_->existParameter(expr->var())) ? true : false; +} + +void EvaluableExprVisitor::visit(VersionedVariableExpression *) { isEvaluable_ = false; } + +void EvaluableExprVisitor::visit(TagPropertyExpression *) { isEvaluable_ = false; } + +void EvaluableExprVisitor::visit(EdgePropertyExpression *) { isEvaluable_ = false; } + +void EvaluableExprVisitor::visit(InputPropertyExpression *) { isEvaluable_ = false; } + +void EvaluableExprVisitor::visit(VariablePropertyExpression *expr) { + isEvaluable_ = (qctx_ && qctx_->existParameter(static_cast(expr)->sym())) + ? true + : false; +} + +void EvaluableExprVisitor::visit(DestPropertyExpression *) { isEvaluable_ = false; } + +void EvaluableExprVisitor::visit(SourcePropertyExpression *) { isEvaluable_ = false; } + +void EvaluableExprVisitor::visit(EdgeSrcIdExpression *) { isEvaluable_ = false; } + +void EvaluableExprVisitor::visit(EdgeTypeExpression *) { isEvaluable_ = false; } + +void EvaluableExprVisitor::visit(EdgeRankExpression *) { isEvaluable_ = false; } + +void EvaluableExprVisitor::visit(EdgeDstIdExpression *) { isEvaluable_ = false; } + +void EvaluableExprVisitor::visit(VertexExpression *) { isEvaluable_ = false; } + +void EvaluableExprVisitor::visit(EdgeExpression *) { isEvaluable_ = false; } + +void EvaluableExprVisitor::visit(ColumnExpression *) { isEvaluable_ = false; } + +void EvaluableExprVisitor::visit(SubscriptRangeExpression *) { isEvaluable_ = false; } + +void EvaluableExprVisitor::visitBinaryExpr(BinaryExpression *expr) { + expr->left()->accept(this); + if (isEvaluable_) { + expr->right()->accept(this); + } +} + +} // namespace graph +} // namespace nebula diff --git a/src/graph/visitor/EvaluableExprVisitor.h b/src/graph/visitor/EvaluableExprVisitor.h index 9c1beb7c7d8..241e720e8b4 100644 --- a/src/graph/visitor/EvaluableExprVisitor.h +++ b/src/graph/visitor/EvaluableExprVisitor.h @@ -10,61 +10,58 @@ namespace nebula { namespace graph { +class QueryContext; class EvaluableExprVisitor : public ExprVisitorImpl { public: + explicit EvaluableExprVisitor(const QueryContext *qctx = nullptr); bool ok() const override { return isEvaluable_; } private: using ExprVisitorImpl::visit; - void visit(ConstantExpression *) override { isEvaluable_ = true; } + void visit(ConstantExpression *) override; - void visit(LabelExpression *) override { isEvaluable_ = false; } + void visit(LabelExpression *) override; - void visit(UUIDExpression *) override { isEvaluable_ = false; } + void visit(UUIDExpression *) override; - void visit(VariableExpression *) override { isEvaluable_ = false; } + void visit(VariableExpression *) override; - void visit(VersionedVariableExpression *) override { isEvaluable_ = false; } + void visit(VersionedVariableExpression *) override; - void visit(TagPropertyExpression *) override { isEvaluable_ = false; } + void visit(TagPropertyExpression *) override; - void visit(EdgePropertyExpression *) override { isEvaluable_ = false; } + void visit(EdgePropertyExpression *) override; - void visit(InputPropertyExpression *) override { isEvaluable_ = false; } + void visit(InputPropertyExpression *) override; - void visit(VariablePropertyExpression *) override { isEvaluable_ = false; } + void visit(VariablePropertyExpression *) override; - void visit(DestPropertyExpression *) override { isEvaluable_ = false; } + void visit(DestPropertyExpression *) override; - void visit(SourcePropertyExpression *) override { isEvaluable_ = false; } + void visit(SourcePropertyExpression *) override; - void visit(EdgeSrcIdExpression *) override { isEvaluable_ = false; } + void visit(EdgeSrcIdExpression *) override; - void visit(EdgeTypeExpression *) override { isEvaluable_ = false; } + void visit(EdgeTypeExpression *) override; - void visit(EdgeRankExpression *) override { isEvaluable_ = false; } + void visit(EdgeRankExpression *) override; - void visit(EdgeDstIdExpression *) override { isEvaluable_ = false; } + void visit(EdgeDstIdExpression *) override; - void visit(VertexExpression *) override { isEvaluable_ = false; } + void visit(VertexExpression *) override; - void visit(EdgeExpression *) override { isEvaluable_ = false; } + void visit(EdgeExpression *) override; - void visit(ColumnExpression *) override { isEvaluable_ = false; } + void visit(ColumnExpression *) override; - void visit(SubscriptRangeExpression *) override { isEvaluable_ = false; } + void visit(SubscriptRangeExpression *) override; - void visitBinaryExpr(BinaryExpression *expr) override { - expr->left()->accept(this); - // Evaluable sub-expression should be obscured by the non-evaluable sub-expression. - if (isEvaluable_) { - expr->right()->accept(this); - } - } + void visitBinaryExpr(BinaryExpression *) override; bool isEvaluable_{true}; + const QueryContext *qctx_{nullptr}; }; } // namespace graph diff --git a/src/interface/graph.thrift b/src/interface/graph.thrift index 8a70850fa09..98204d058cd 100644 --- a/src/interface/graph.thrift +++ b/src/interface/graph.thrift @@ -115,9 +115,10 @@ service GraphService { oneway void signout(1: i64 sessionId) ExecutionResponse execute(1: i64 sessionId, 2: binary stmt) - + ExecutionResponse executeWithParameter(1: i64 sessionId, 2: binary stmt, 3: map(cpp.template = "std::unordered_map") parameterMap) // Same as execute(), but response will be a json string binary executeJson(1: i64 sessionId, 2: binary stmt) - + binary executeJsonWithParameter(1: i64 sessionId, 2: binary stmt, 3: map(cpp.template = "std::unordered_map") parameterMap) + VerifyClientVersionResp verifyClientVersion(1: VerifyClientVersionReq req) } diff --git a/src/parser/parser.yy b/src/parser/parser.yy index 04f1bf1699d..c00dc134e3d 100644 --- a/src/parser/parser.yy +++ b/src/parser/parser.yy @@ -938,7 +938,12 @@ vertex_prop_expression var_prop_expression : VARIABLE DOT name_label { - $$ = VariablePropertyExpression::make(qctx->objPool(), *$1, *$3); + if (!qctx->existParameter(*$1)) { + $$ = VariablePropertyExpression::make(qctx->objPool(), *$1, *$3); + } else { + $$ = AttributeExpression::make(qctx->objPool(), + VariableExpression::make(qctx->objPool(),*$1), ConstantExpression::make(qctx->objPool(),*$3)); + } delete $1; delete $3; } @@ -1262,6 +1267,9 @@ truncate_clause $$ = nullptr; } | KW_SAMPLE expression { + if(graph::ExpressionUtils::findAny($2, {Expression::Kind::kVar})) { + throw nebula::GraphParser::syntax_error(@2, "Parameter is not supported in sample clause"); + } $$ = new TruncateClause($2, true); } | KW_LIMIT expression { @@ -1292,6 +1300,9 @@ from_clause $$ = new FromClause($2); } | KW_FROM vid_ref_expression { + if(graph::ExpressionUtils::findAny($2,{Expression::Kind::kVar})) { + throw nebula::GraphParser::syntax_error(@2, "Parameter is not supported in from clause"); + } $$ = new FromClause($2); } ; @@ -1321,6 +1332,15 @@ vid $$ = ConstantExpression::make(qctx->objPool(), *$1); delete $1; } + | VARIABLE { + $$ = nullptr; + UNUSED($1); + if (qctx->existParameter(*$1)) { + throw nebula::GraphParser::syntax_error(@1, "Parameter is not supported in vid"); + } else { + throw nebula::GraphParser::syntax_error(@1, "Variable is not supported in vid"); + } + } ; unary_integer @@ -1340,6 +1360,9 @@ vid_ref_expression $$ = $1; } | var_prop_expression { + if(graph::ExpressionUtils::findAny($1,{Expression::Kind::kVar})) { + throw nebula::GraphParser::syntax_error(@1, "Parameter is not supported in vid"); + } $$ = $1; } ; @@ -1472,13 +1495,13 @@ yield_column delete $3; } | expression { - if (graph::ExpressionUtils::checkVarExprIfExist($1)) { + if (graph::ExpressionUtils::checkVarExprIfExist($1, qctx)) { throw nebula::GraphParser::syntax_error(@1, "Direct output of variable is prohibited"); } $$ = new YieldColumn($1); } | expression KW_AS name_label { - if (graph::ExpressionUtils::checkVarExprIfExist($1)) { + if (graph::ExpressionUtils::checkVarExprIfExist($1, qctx)) { delete $3; throw nebula::GraphParser::syntax_error(@1, "Direct output of variable is prohibited"); } @@ -2198,6 +2221,9 @@ to_clause $$ = new ToClause($2); } | KW_TO vid_ref_expression { + if(graph::ExpressionUtils::findAny($2,{Expression::Kind::kVar})) { + throw nebula::GraphParser::syntax_error(@2, "Parameter is not supported in to clause"); + } $$ = new ToClause($2); } ; @@ -2750,7 +2776,11 @@ set_sentence assignment_sentence : VARIABLE ASSIGN set_sentence { - $$ = new AssignmentSentence($1, $3); + if (qctx->existParameter(*$1)) { + throw nebula::GraphParser::syntax_error(@1, "Variable definition conflicts with a parameter"); + } else { + $$ = new AssignmentSentence($1, $3); + } } ; diff --git a/tests/Makefile b/tests/Makefile index 51ade78f0c8..f3ee45f3c83 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -29,9 +29,9 @@ test_j = $(test_without_skip) -n$(J) install-deps: pip3 install --user -U setuptools wheel -i $(PYPI_MIRROR) pip3 install --user -r $(CURR_DIR)/requirements.txt -i $(PYPI_MIRROR) - install-nebula-py: install-deps - git clone --branch master https://github.com/vesoft-inc/nebula-python $(CURR_DIR)/nebula-python +# TODO: just for test (czp) + git clone --branch param-cmd https://github.com/czpmango/nebula-python $(CURR_DIR)/nebula-python cd $(CURR_DIR)/nebula-python \ && pip3 install --user . -i $(PYPI_MIRROR) rm -rf $(CURR_DIR)/nebula-python diff --git a/tests/common/utils.py b/tests/common/utils.py index 69e284e8f8a..d2ec19a95df 100644 --- a/tests/common/utils.py +++ b/tests/common/utils.py @@ -20,6 +20,8 @@ from tests.common.path_value import PathVal from tests.common.types import SpaceDesc +# just for cypher parameter test +params={} def utf8b(s: str): return bytes(s, encoding='utf-8') @@ -344,8 +346,7 @@ def wrapper(*args, **kwargs): @retry(30) def try_execute(sess: Session, stmt: str): - return sess.execute(stmt) - + return sess.execute_parameter(stmt, params) def return_if_not_leader_changed(resp) -> bool: if not resp: @@ -359,8 +360,7 @@ def return_if_not_leader_changed(resp) -> bool: @retry(30, return_if_not_leader_changed) def process_leader_changed(sess: Session, stmt: str): - return sess.execute(stmt) - + return sess.execute_parameter(stmt, params) def response(sess: Session, stmt: str, need_try: bool = False): try: diff --git a/tests/tck/conftest.py b/tests/tck/conftest.py index 7aa95a0a9b0..a93cb81dd2f 100644 --- a/tests/tck/conftest.py +++ b/tests/tck/conftest.py @@ -10,8 +10,9 @@ import csv import re import threading +import json -from nebula2.common.ttypes import Value, ErrorCode +from nebula2.common.ttypes import NList, NMap, Value, ErrorCode from nebula2.data.DataObject import ValueWrapper from pytest_bdd import given, parsers, then, when @@ -28,6 +29,7 @@ check_resp, response, resp_ok, + params, ) from tests.common.nebula_service import NebulaService from tests.tck.utils.table import dataset, table @@ -116,6 +118,50 @@ def wait_indexes_ready(sess): def graph_spaces(): return dict(result_set=None) +@given(parse('parameters: {parameters}')) +def preload_parameters( + parameters +): + try: + paramMap = json.loads(parameters) + for (k,v) in paramMap.items(): + params[k]=value(v) + except: + raise ValueError("preload parameters failed!") + + +# construct python-type to nebula.Value +def value(any): + v = Value() + if (isinstance(any, bool)): + v.set_bVal(any) + elif (isinstance(any, int)): + v.set_iVal(any) + elif (isinstance(any, str)): + v.set_sVal(any) + elif (isinstance(any, float)): + v.set_fVal(any) + elif (isinstance(any, list)): + v.set_lVal(list2Nlist(any)) + elif (isinstance(any, dict)): + v.set_mVal(map2NMap(any)) + else: + raise TypeError("Do not support convert "+str(type(any))+" to nebula.Value") + return v + +def list2Nlist(list): + nlist = NList() + nlist.values = [] + for item in list: + nlist.values.append(value(item)) + return nlist + +def map2NMap(map): + nmap = NMap() + nmap.kvs={} + for k,v in map.items(): + nmap.kvs[k]=value(v) + return nmap @given(parse('a graph with space named "{space}"')) def preload_space( diff --git a/tests/tck/features/go/GO.IntVid.feature b/tests/tck/features/go/GO.IntVid.feature index 01da5b09d03..757b31efc45 100644 --- a/tests/tck/features/go/GO.IntVid.feature +++ b/tests/tck/features/go/GO.IntVid.feature @@ -190,7 +190,7 @@ Feature: IntegerVid Go Sentence """ GO FROM $var OVER like YIELD like._dst """ - Then a SyntaxError should be raised at runtime: syntax error near `OVER' + Then a SyntaxError should be raised at runtime: Variable is not supported in vid near `$var' Scenario: Integer Vid distinct When executing query: diff --git a/tests/tck/features/go/GO.feature b/tests/tck/features/go/GO.feature index 3ed44e7a4a2..6c332aa9159 100644 --- a/tests/tck/features/go/GO.feature +++ b/tests/tck/features/go/GO.feature @@ -216,7 +216,7 @@ Feature: Go Sentence """ GO FROM $var OVER like YIELD like._dst """ - Then a SyntaxError should be raised at runtime: syntax error near `OVER' + Then a SyntaxError should be raised at runtime: Variable is not supported in vid near `$var' Scenario: distinct When executing query: diff --git a/tests/tck/features/go/GoYieldVertexEdge.feature b/tests/tck/features/go/GoYieldVertexEdge.feature index 81b0e8c1129..418d739e072 100644 --- a/tests/tck/features/go/GoYieldVertexEdge.feature +++ b/tests/tck/features/go/GoYieldVertexEdge.feature @@ -231,7 +231,7 @@ Feature: Go Yield Vertex And Edge Sentence """ GO FROM $var OVER like YIELD edge as e """ - Then a SyntaxError should be raised at runtime: syntax error near `OVER' + Then a SyntaxError should be raised at runtime: Variable is not supported in vid near `$var' Scenario: distinct map and set When executing query: diff --git a/tests/tck/features/yield/parameter.feature b/tests/tck/features/yield/parameter.feature new file mode 100644 index 00000000000..e975484630f --- /dev/null +++ b/tests/tck/features/yield/parameter.feature @@ -0,0 +1,141 @@ +# Copyright (c) 2021 vesoft inc. All rights reserved. +# +# This source code is licensed under Apache 2.0 License +Feature: Parameter + + Background: + Given a graph with space named "nba" + Given parameters: {"p1":1,"p2":true,"p3":"Tim Duncan","p4":3.3,"p5":[1,true,3],"p6":{"a":3,"b":false,"c":"Tim Duncan"},"p7":{"a":{"b":{"c":"Tim Duncan","d":[1,2,3,true,"Tim Duncan"]}}}} + + Scenario: return parameters + When executing query: + """ + RETURN abs($p1)+1 AS ival, $p2 and false AS bval, $p3+"ef" AS sval, round($p4)+1.1 AS fval, $p5 AS lval, $p6.a AS mval, all(item in $p7.a.b.d where item<4 or ((item>0) is null)) AS pval + """ + Then the result should be, in any order: + | ival | bval | sval | fval | lval | mval | pval | + | 2 | false | "Tim Duncanef" | 4.1 | [1,true,3] | 3 | true | + + Scenario: cypher with parameters + When executing query: + """ + MATCH (v:player)-[:like]->(n) WHERE id(v)==$p3 and n.age>$p1+29 + RETURN n.name AS dst LIMIT $p1+1 + """ + Then the result should be, in any order: + | dst | + | "Manu Ginobili" | + | "Tony Parker" | + When executing query: + """ + MATCH (v:player)-[:like]->(n{name:$p7.a.b.c}) + RETURN n.name AS dst LIMIT $p7.a.b.d[0] + """ + Then the result should be, in any order: + | dst | + | "Tim Duncan" | + When executing query: + """ + UNWIND $p5 AS c + WITH $p6.a AS a, $p6.b AS b, c + RETURN a,b,c + """ + Then the result should be, in any order: + | a | b | c | + | 3 | false | 1 | + | 3 | false | true | + | 3 | false | 3 | + When executing query: + """ + UNWIND abs($p1)+1 AS ival + WITH ival AS ival, $p2 and false AS bval, $p3+"ef" AS sval, round($p4)+1.1 AS fval + RETURN * + """ + Then the result should be, in any order: + | sval | fval | ival | bval | + | "Tim Duncanef" | 4.1 | 2 | false | + When executing query: + """ + MATCH (v:player) + WITH v AS v WHERE v.name in [$p1,$p2,$p3,"Tony Parker",$p4,$p5,$p6] + RETURN v.name AS v ORDER BY v, $p3 LIMIT $p1 + """ + Then the result should be, in order: + | v | + | "Tim Duncan" | + + Scenario: ngql with parameters + When executing query: + """ + $p1=GO FROM "Tim Duncan" OVER like WHERE like.likeness>$p1 yield like._dst as dst; + GO FROM $p1.dst OVER like YIELD DISTINCT $$.player.name AS name + """ + Then a SyntaxError should be raised at runtime: Variable definition conflicts with a parameter near `$p1' + When executing query: + """ + $var=GO FROM $p4 OVER like WHERE like.likeness>$p1 yield like._dst as dst; + GO FROM $p1.dst OVER like YIELD DISTINCT $$.player.name AS name + """ + Then a SyntaxError should be raised at runtime: Parameter is not supported in vid near `$p4' + When executing query: + """ + GO FROM $p3,$p4 OVER like + """ + Then a SyntaxError should be raised at runtime: Parameter is not supported in vid near `$p3' + When executing query: + """ + FETCH PROP ON player $nonexist + """ + Then a SyntaxError should be raised at runtime: Variable is not supported in vid near `$nonexist' + When executing query: + """ + FETCH PROP ON player $p3,$p4 + """ + Then a SyntaxError should be raised at runtime: Parameter is not supported in vid near `$p3' + When executing query: + """ + find noloop path from $p3 to $p2 over like + """ + Then a SyntaxError should be raised at runtime: Parameter is not supported in vid near `$p3' + When executing query: + """ + find all path from $p3 to $p2 over like + """ + Then a SyntaxError should be raised at runtime: Parameter is not supported in vid near `$p3' + When executing query: + """ + find shortest path from $p3 to $p2 over like + """ + Then a SyntaxError should be raised at runtime: Parameter is not supported in vid near `$p3' + When executing query: + """ + GET SUBGRAPH FROM $p3 BOTH like + """ + Then a SyntaxError should be raised at runtime: Parameter is not supported in vid near `$p3' + When executing query: + """ + find shortest path from $p3 to $p2 over like + """ + Then a SyntaxError should be raised at runtime: Parameter is not supported in vid near `$p3' + When executing query: + """ + find shortest path from $p3 to $p2 over like + """ + Then a SyntaxError should be raised at runtime: Parameter is not supported in vid near `$p3' + When executing query: + """ + GO 1 TO 2 STEPS FROM "Tim Duncan" OVER like YIELD like._dst AS dst SAMPLE [1,$p1] + """ + Then a SyntaxError should be raised at runtime: Parameter is not supported in sample clause near `[1,$p1]' + + Scenario: error check + When executing query: + """ + LOOKUP ON player WHERE player.age>$p2+43 + """ + Then a SemanticError should be raised at runtime: Column type error : age + When executing query: + """ + MATCH (v:player) RETURN v LIMIT $p6 + """ + Then a SemanticError should be raised at runtime: LIMIT should be of type integer