diff --git a/src/common/expression/CMakeLists.txt b/src/common/expression/CMakeLists.txt index 1cca3b5321b..0e45b544375 100644 --- a/src/common/expression/CMakeLists.txt +++ b/src/common/expression/CMakeLists.txt @@ -32,6 +32,7 @@ nebula_add_library( PredicateExpression.cpp ListComprehensionExpression.cpp ReduceExpression.cpp + ParameterExpression.cpp ) nebula_add_subdirectory(test) diff --git a/src/common/expression/ExprVisitor.h b/src/common/expression/ExprVisitor.h index abcb96f0ac2..8bdd5f0e5b5 100644 --- a/src/common/expression/ExprVisitor.h +++ b/src/common/expression/ExprVisitor.h @@ -21,6 +21,7 @@ #include "common/expression/LabelExpression.h" #include "common/expression/ListComprehensionExpression.h" #include "common/expression/LogicalExpression.h" +#include "common/expression/ParameterExpression.h" #include "common/expression/PathBuildExpression.h" #include "common/expression/PredicateExpression.h" #include "common/expression/PropertyExpression.h" @@ -89,6 +90,8 @@ class ExprVisitor { virtual void visit(ReduceExpression *expr) = 0; // subscript range expression virtual void visit(SubscriptRangeExpression *expr) = 0; + // parameter expression + virtual void visit(ParameterExpression *expr) = 0; }; } // namespace nebula diff --git a/src/common/expression/Expression.cpp b/src/common/expression/Expression.cpp index 2178b268842..10bf0476e82 100644 --- a/src/common/expression/Expression.cpp +++ b/src/common/expression/Expression.cpp @@ -22,6 +22,7 @@ #include "common/expression/LabelExpression.h" #include "common/expression/ListComprehensionExpression.h" #include "common/expression/LogicalExpression.h" +#include "common/expression/ParameterExpression.h" #include "common/expression/PathBuildExpression.h" #include "common/expression/PredicateExpression.h" #include "common/expression/PropertyExpression.h" @@ -497,6 +498,11 @@ Expression* Expression::decode(ObjectPool* pool, Expression::Decoder& decoder) { exp->resetFrom(decoder); return exp; } + case Expression::Kind::kParam: { + exp = ParameterExpression::make(pool); + exp->resetFrom(decoder); + return exp; + } case Expression::Kind::kTSPrefix: case Expression::Kind::kTSWildcard: case Expression::Kind::kTSRegexp: @@ -719,6 +725,9 @@ std::ostream& operator<<(std::ostream& os, Expression::Kind kind) { case Expression::Kind::kReduce: os << "Reduce"; break; + case Expression::Kind::kParam: + os << "Parameter"; + break; } return os; } diff --git a/src/common/expression/Expression.h b/src/common/expression/Expression.h index 5bee6009a21..ecb8e215f01 100644 --- a/src/common/expression/Expression.h +++ b/src/common/expression/Expression.h @@ -108,6 +108,7 @@ class Expression { kIsNotEmpty, kSubscriptRange, + kParam, }; Expression(ObjectPool* pool, Kind kind); diff --git a/src/common/expression/ParameterExpression.cpp b/src/common/expression/ParameterExpression.cpp new file mode 100644 index 00000000000..a6cc9db87c3 --- /dev/null +++ b/src/common/expression/ParameterExpression.cpp @@ -0,0 +1,30 @@ +/* Copyright (c) 2021 vesoft inc. All rights reserved. + * + * This source code is licensed under Apache 2.0 License, + * attached with Common Clause Condition 1.0, found in the LICENSES directory. + */ + +#include "common/expression/ParameterExpression.h" + +#include "common/expression/ExprVisitor.h" + +namespace nebula { + +const Value& ParameterExpression::eval(ExpressionContext& ectx) { return ectx.getVar(name_); } + +std::string ParameterExpression::toString() const { return '$' + name_; } + +bool ParameterExpression::operator==(const Expression& rhs) const { + return kind_ == rhs.kind() && '$' + name_ == rhs.toString(); +} + +void ParameterExpression::writeTo(Encoder& encoder) const { + encoder << kind_; + encoder << name_; +} + +void ParameterExpression::resetFrom(Decoder& decoder) { name_ = decoder.readStr(); } + +void ParameterExpression::accept(ExprVisitor* visitor) { visitor->visit(this); } + +} // namespace nebula diff --git a/src/common/expression/ParameterExpression.h b/src/common/expression/ParameterExpression.h new file mode 100644 index 00000000000..b478f6fe905 --- /dev/null +++ b/src/common/expression/ParameterExpression.h @@ -0,0 +1,47 @@ +/* Copyright (c) 2021 vesoft inc. All rights reserved. + * + * This source code is licensed under Apache 2.0 License, + * attached with Common Clause Condition 1.0, found in the LICENSES directory. + */ + +#pragma once + +#include "common/expression/Expression.h" + +// The ParameterExpression use for parameterized statement +namespace nebula { + +class ParameterExpression : public Expression { + public: + ParameterExpression& operator=(const ParameterExpression& rhs) = delete; + ParameterExpression& operator=(ParameterExpression&&) = delete; + + static ParameterExpression* make(ObjectPool* pool, const std::string& name = "") { + return pool->add(new ParameterExpression(pool, name)); + } + + bool operator==(const Expression& rhs) const override; + + const Value& eval(ExpressionContext& ctx) override; + + const std::string& name() const { return name_; } + + std::string toString() const override; + + void accept(ExprVisitor* visitor) override; + + Expression* clone() const override { return ParameterExpression::make(pool_, name()); } + + protected: + explicit ParameterExpression(ObjectPool* pool, const std::string& name = "") + : Expression(pool, Kind::kParam), name_(name) {} + + void writeTo(Encoder& encoder) const override; + void resetFrom(Decoder& decoder) override; + + protected: + std::string name_; + Value result_; +}; + +} // namespace nebula diff --git a/src/common/expression/test/CMakeLists.txt b/src/common/expression/test/CMakeLists.txt index 20ba9d99f2a..75f31983a4e 100644 --- a/src/common/expression/test/CMakeLists.txt +++ b/src/common/expression/test/CMakeLists.txt @@ -286,6 +286,25 @@ nebula_add_test( ${THRIFT_LIBRARIES} ) +nebula_add_test( + NAME param_expression_test + SOURCES ParameterExpressionTest.cpp + OBJECTS + $ + $ + $ + $ + $ + $ + $ + $ + $ + $ + LIBRARIES + gtest + ${THRIFT_LIBRARIES} +) + nebula_add_test( NAME list_comprehension_expression_test SOURCES ListComprehensionExpressionTest.cpp diff --git a/src/common/expression/test/EncodeDecodeTest.cpp b/src/common/expression/test/EncodeDecodeTest.cpp index b5d46c5425c..353bc3ed7fb 100644 --- a/src/common/expression/test/EncodeDecodeTest.cpp +++ b/src/common/expression/test/EncodeDecodeTest.cpp @@ -303,6 +303,12 @@ TEST(ExpressionEncodeDecode, LabelExpression) { ASSERT_EQ(*origin, *decoded); } +TEST(ExpressionEncodeDecode, ParameterExpression) { + auto origin = ParameterExpression::make(&pool, "p1"); + auto decoded = Expression::decode(&pool, Expression::encode(*origin)); + ASSERT_EQ(*origin, *decoded); +} + TEST(ExpressionEncodeDecode, CaseExpression) { { // CASE 23 WHEN 24 THEN 1 END diff --git a/src/common/expression/test/ExpressionContextMock.cpp b/src/common/expression/test/ExpressionContextMock.cpp index 7b049e72505..45a08f695dc 100644 --- a/src/common/expression/test/ExpressionContextMock.cpp +++ b/src/common/expression/test/ExpressionContextMock.cpp @@ -43,6 +43,8 @@ std::unordered_map ExpressionContextMock::vals_ = { {"path_edge2", Value(Edge("2", "3", 1, "edge", 0, {}))}, {"path_v2", Value(Vertex("3", {}))}, {"path_edge3", Value(Edge("3", "4", 1, "edge", 0, {}))}, + {"param1", Value(1)}, + {"param2", Value(List(std::vector{1, 2, 3, 4, 5, 6, 7, 8}))}, }; Value ExpressionContextMock::getColumn(int32_t index) const { diff --git a/src/common/expression/test/ParameterExpressionTest.cpp b/src/common/expression/test/ParameterExpressionTest.cpp new file mode 100644 index 00000000000..e5d17f61f76 --- /dev/null +++ b/src/common/expression/test/ParameterExpressionTest.cpp @@ -0,0 +1,31 @@ +/* Copyright (c) 2021 vesoft inc. All rights reserved. + * + * This source code is licensed under Apache 2.0 License, + * attached with Common Clause Condition 1.0, found in the LICENSES directory. + */ +#include "common/expression/test/TestBase.h" + +namespace nebula { + +class ParameterExpressionTest : public ExpressionTest {}; + +TEST_F(ParameterExpressionTest, ParamExprToString) { + auto expr = ParameterExpression::make(&pool, "param1"); + ASSERT_EQ("$param1", expr->toString()); +} + +TEST_F(ParameterExpressionTest, ParamEvaluate) { + auto expr = ParameterExpression::make(&pool, "param1"); + auto value = Expression::eval(expr, gExpCtxt); + ASSERT_TRUE(value.isInt()); + ASSERT_EQ(1, value.getInt()); +} +} // namespace nebula + +int main(int argc, char **argv) { + testing::InitGoogleTest(&argc, argv); + folly::init(&argc, &argv, true); + google::SetStderrLogging(google::INFO); + + return RUN_ALL_TESTS(); +} diff --git a/src/common/expression/test/TestBase.h b/src/common/expression/test/TestBase.h index 2ba5a6c629a..a79aa13d97c 100644 --- a/src/common/expression/test/TestBase.h +++ b/src/common/expression/test/TestBase.h @@ -34,6 +34,7 @@ #include "common/expression/LabelExpression.h" #include "common/expression/ListComprehensionExpression.h" #include "common/expression/LogicalExpression.h" +#include "common/expression/ParameterExpression.h" #include "common/expression/PathBuildExpression.h" #include "common/expression/PredicateExpression.h" #include "common/expression/PropertyExpression.h" diff --git a/src/graph/context/QueryContext.cpp b/src/graph/context/QueryContext.cpp index 46f7fe02af6..2e7499e8dc9 100644 --- a/src/graph/context/QueryContext.cpp +++ b/src/graph/context/QueryContext.cpp @@ -30,6 +30,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 c85aed49221..2814d08913d 100644 --- a/src/graph/context/QueryContext.h +++ b/src/graph/context/QueryContext.h @@ -97,6 +97,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 e6962ab5d2d..cb9029212fb 100644 --- a/src/graph/context/QueryExpressionContext.h +++ b/src/graph/context/QueryExpressionContext.h @@ -54,7 +54,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/context/ast/QueryAstContext.h b/src/graph/context/ast/QueryAstContext.h index fed455a2198..426bb8464c8 100644 --- a/src/graph/context/ast/QueryAstContext.h +++ b/src/graph/context/ast/QueryAstContext.h @@ -19,6 +19,7 @@ enum FromType { kInstantExpr, kVariable, kPipe, + kParam, }; struct Starts { @@ -27,6 +28,7 @@ struct Starts { Expression* originalSrc{nullptr}; std::string userDefinedVarName; std::string runtimeVidName; + Expression* runtimeVid{nullptr}; std::vector vids; }; diff --git a/src/graph/planner/ngql/FetchVerticesPlanner.cpp b/src/graph/planner/ngql/FetchVerticesPlanner.cpp index 2782ab622e6..8cf43fe2b32 100644 --- a/src/graph/planner/ngql/FetchVerticesPlanner.cpp +++ b/src/graph/planner/ngql/FetchVerticesPlanner.cpp @@ -36,7 +36,9 @@ StatusOr FetchVerticesPlanner::transform(AstContext* astCtx) { auto& starts = fetchCtx_->from; std::string vidsVar; - if (!starts.vids.empty() && starts.originalSrc == nullptr) { + if (starts.runtimeVid != nullptr) { + starts.src = starts.runtimeVid; + } else if (!starts.vids.empty() && starts.originalSrc == nullptr) { PlannerUtil::buildConstantInput(qctx, starts, vidsVar); } else { starts.src = starts.originalSrc; @@ -55,7 +57,9 @@ StatusOr FetchVerticesPlanner::transform(AstContext* astCtx) { buildVertexProps(fetchCtx_->exprProps.tagProps()), {}, fetchCtx_->distinct); - getVertices->setInputVar(vidsVar); + if (!vidsVar.empty()) { + getVertices->setInputVar(vidsVar); + } subPlan.root = Project::make(qctx, getVertices, fetchCtx_->yieldExpr); if (fetchCtx_->distinct) { diff --git a/src/graph/planner/ngql/GoPlanner.cpp b/src/graph/planner/ngql/GoPlanner.cpp index fc0a38ddda3..1997951d41b 100644 --- a/src/graph/planner/ngql/GoPlanner.cpp +++ b/src/graph/planner/ngql/GoPlanner.cpp @@ -405,7 +405,9 @@ SubPlan GoPlanner::oneStepPlan(SubPlan& startVidPlan) { gn->setVertexProps(buildVertexProps(goCtx_->exprProps.srcTagProps())); gn->setEdgeProps(buildEdgeProps(false)); gn->setSrc(goCtx_->from.src); - gn->setInputVar(goCtx_->vidsVar); + if (!goCtx_->vidsVar.empty()) { + gn->setInputVar(goCtx_->vidsVar); + } auto* sampleLimit = buildSampleLimit(gn, 1 /* one step */); @@ -533,7 +535,8 @@ SubPlan GoPlanner::mToNStepsPlan(SubPlan& startVidPlan) { StatusOr GoPlanner::transform(AstContext* astCtx) { goCtx_ = static_cast(astCtx); auto qctx = goCtx_->qctx; - goCtx_->joinInput = goCtx_->from.fromType != FromType::kInstantExpr; + auto fromType = goCtx_->from.fromType; + goCtx_->joinInput = fromType != FromType::kInstantExpr && fromType != FromType::kParam; goCtx_->joinDst = !goCtx_->exprProps.dstTagProps().empty(); SubPlan startPlan = PlannerUtil::buildStart(qctx, goCtx_->from, goCtx_->vidsVar); diff --git a/src/graph/service/GraphService.cpp b/src/graph/service/GraphService.cpp index bad422047b6..0c1d6f81bcc 100644 --- a/src/graph/service/GraphService.cpp +++ b/src/graph/service/GraphService.cpp @@ -116,6 +116,13 @@ void GraphService::signout(int64_t sessionId) { folly::Future GraphService::future_execute(int64_t sessionId, const std::string& query) { + return future_executeWithParameter(sessionId, query, std::unordered_map{}); +} + +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()); @@ -129,7 +136,7 @@ folly::Future GraphService::future_execute(int64_t sessionId, ctx->finish(); return future; } - auto cb = [this, sessionId, ctx = std::move(ctx)]( + 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(); @@ -147,6 +154,7 @@ folly::Future GraphService::future_execute(int64_t 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)); @@ -155,9 +163,17 @@ folly::Future GraphService::future_execute(int64_t sessionId, folly::Future GraphService::future_executeJson(int64_t sessionId, const std::string& query) { - auto rawResp = future_execute(sessionId, query).get(); - auto respJsonObj = rawResp.toJson(); - return folly::toJson(respJsonObj); + 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()); + }); } bool GraphService::auth(const std::string& username, const std::string& password) { diff --git a/src/graph/service/GraphService.h b/src/graph/service/GraphService.h index 634d821fb9a..76355cf82de 100644 --- a/src/graph/service/GraphService.h +++ b/src/graph/service/GraphService.h @@ -36,12 +36,22 @@ class GraphService final : public cpp2::GraphServiceSvIf { folly::Future future_execute(int64_t sessionId, const std::string& stmt) override; + folly::Future future_executeWithParameter( + 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; folly::Future future_verifyClientVersion( const cpp2::VerifyClientVersionReq& req) override; + folly::Future future_executeJsonWithParameter( + int64_t sessionId, + const std::string& stmt, + const std::unordered_map& parameterMap) override; + private: bool auth(const std::string& username, const std::string& password); diff --git a/src/graph/service/RequestContext.h b/src/graph/service/RequestContext.h index e74eaf05c3f..9a1a3ec03d5 100644 --- a/src/graph/service/RequestContext.h +++ b/src/graph/service/RequestContext.h @@ -68,6 +68,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_; @@ -76,6 +82,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/PlannerUtil.cpp b/src/graph/util/PlannerUtil.cpp index f6613f55a06..57d3b093791 100644 --- a/src/graph/util/PlannerUtil.cpp +++ b/src/graph/util/PlannerUtil.cpp @@ -56,7 +56,9 @@ SubPlan PlannerUtil::buildRuntimeInput(QueryContext* qctx, Starts& starts) { // static SubPlan PlannerUtil::buildStart(QueryContext* qctx, Starts& starts, std::string& vidsVar) { SubPlan subPlan; - if (!starts.vids.empty() && starts.originalSrc == nullptr) { + if (starts.runtimeVid) { + starts.src = starts.runtimeVid; + } else if (!starts.vids.empty() && starts.originalSrc == nullptr) { buildConstantInput(qctx, starts, vidsVar); } else { subPlan = buildRuntimeInput(qctx, starts); diff --git a/src/graph/validator/GoValidator.cpp b/src/graph/validator/GoValidator.cpp index 571278a1706..9ce1b05f915 100644 --- a/src/graph/validator/GoValidator.cpp +++ b/src/graph/validator/GoValidator.cpp @@ -242,7 +242,7 @@ Status GoValidator::buildColumns() { const auto& from = goCtx_->from; if (dstTagProps.empty() && inputProps.empty() && varProps.empty() && - from.fromType == FromType::kInstantExpr) { + (from.fromType == FromType::kInstantExpr || from.fromType == FromType::kParam)) { return Status::OK(); } diff --git a/src/graph/validator/MatchValidator.cpp b/src/graph/validator/MatchValidator.cpp index aae111617b9..cbd154721bd 100644 --- a/src/graph/validator/MatchValidator.cpp +++ b/src/graph/validator/MatchValidator.cpp @@ -548,7 +548,7 @@ StatusOr MatchValidator::makeSubFilter(const std::string &alias, DCHECK(!items.empty()); // TODO(dutor) Check if evaluable and evaluate - if (items[0].second->kind() != Expression::Kind::kConstant) { + if (!ExpressionUtils::isEvaluableExpr(items[0].second)) { return Status::SemanticError("Props must be constant: `%s'", items[0].second->toString().c_str()); } @@ -558,7 +558,7 @@ StatusOr MatchValidator::makeSubFilter(const std::string &alias, pool, LabelExpression::make(pool, alias), ConstantExpression::make(pool, items[0].first)), items[0].second->clone()); for (auto i = 1u; i < items.size(); i++) { - if (items[i].second->kind() != Expression::Kind::kConstant) { + if (!ExpressionUtils::isEvaluableExpr(items[i].second)) { return Status::SemanticError("Props must be constant: `%s'", items[i].second->toString().c_str()); } @@ -636,8 +636,7 @@ Status MatchValidator::validatePagination(const Expression *skipExpr, if (!evaluableExpr(limitExpr)) { 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"); } @@ -669,7 +668,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)) 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(); diff --git a/src/graph/validator/Validator.cpp b/src/graph/validator/Validator.cpp index 761c0d691f7..c5f2b97fa76 100644 --- a/src/graph/validator/Validator.cpp +++ b/src/graph/validator/Validator.cpp @@ -428,14 +428,23 @@ Status Validator::validateStarts(const VerticesClause* clause, Starts& starts) { } if (clause->isRef()) { auto* src = clause->ref(); - if (src->kind() != Expression::Kind::kInputProperty && - src->kind() != Expression::Kind::kVarProperty) { + auto kind = src->kind(); + if (kind != Expression::Kind::kInputProperty && kind != Expression::Kind::kVarProperty && + kind != Expression::Kind::kParam) { return Status::SemanticError( "`%s', Only input and variable expression is acceptable" " when starts are evaluated at runtime.", src->toString().c_str()); } - starts.fromType = src->kind() == Expression::Kind::kInputProperty ? kPipe : kVariable; + // starts.fromType = src->kind() == Expression::Kind::kInputProperty ? kPipe : kVariable; + auto srcKind = src->kind(); + if (srcKind == Expression::Kind::kParam) { + starts.fromType = kParam; + } else if (srcKind == Expression::Kind::kInputProperty) { + starts.fromType = kPipe; + } else { + starts.fromType = kVariable; + } auto type = deduceExprType(src); if (!type.ok()) { return type.status(); @@ -447,6 +456,10 @@ Status Validator::validateStarts(const VerticesClause* clause, Starts& starts) { << apache::thrift::util::enumNameSafe(vidType) << ", but was`" << type.value() << "'"; return Status::SemanticError(ss.str()); } + if (starts.fromType == kParam) { + starts.runtimeVid = src; + return Status::OK(); + } starts.originalSrc = src; auto* propExpr = static_cast(src); if (starts.fromType == kVariable) { diff --git a/src/graph/visitor/DeducePropsVisitor.h b/src/graph/visitor/DeducePropsVisitor.h index 7578f792a68..63cf1ced34c 100644 --- a/src/graph/visitor/DeducePropsVisitor.h +++ b/src/graph/visitor/DeducePropsVisitor.h @@ -100,6 +100,7 @@ class DeducePropsVisitor : public ExprVisitorImpl { void visit(VertexExpression* expr) override; void visit(EdgeExpression* expr) override; void visit(ColumnExpression* expr) override; + void visit(ParameterExpression*) override {} void visitEdgePropExpr(PropertyExpression* expr); void reportError(const Expression* expr); diff --git a/src/graph/visitor/DeduceTypeVisitor.cpp b/src/graph/visitor/DeduceTypeVisitor.cpp index f03ccd3b5a3..a847c6ba63e 100644 --- a/src/graph/visitor/DeduceTypeVisitor.cpp +++ b/src/graph/visitor/DeduceTypeVisitor.cpp @@ -116,6 +116,10 @@ void DeduceTypeVisitor::visit(ConstantExpression *expr) { type_ = expr->eval(ctx(nullptr)).type(); } +void DeduceTypeVisitor::visit(ParameterExpression *expr) { + type_ = expr->eval(QueryExpressionContext(qctx_->ectx())()).type(); +} + void DeduceTypeVisitor::visit(UnaryExpression *expr) { expr->operand()->accept(this); if (!ok()) return; @@ -489,7 +493,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/DeduceTypeVisitor.h b/src/graph/visitor/DeduceTypeVisitor.h index a129873206a..04969b9c732 100644 --- a/src/graph/visitor/DeduceTypeVisitor.h +++ b/src/graph/visitor/DeduceTypeVisitor.h @@ -85,6 +85,8 @@ class DeduceTypeVisitor final : public ExprVisitor { void visit(ReduceExpression *expr) override; // subscript range void visit(SubscriptRangeExpression *expr) override; + // parameter expression + void visit(ParameterExpression *expr) override; void visitVertexPropertyExpr(PropertyExpression *expr); diff --git a/src/graph/visitor/EvaluableExprVisitor.h b/src/graph/visitor/EvaluableExprVisitor.h index 8c9270e5640..69082556050 100644 --- a/src/graph/visitor/EvaluableExprVisitor.h +++ b/src/graph/visitor/EvaluableExprVisitor.h @@ -29,6 +29,11 @@ class EvaluableExprVisitor : public ExprVisitorImpl { void visit(VersionedVariableExpression *) override { isEvaluable_ = false; } + void visit(ParameterExpression *) override { + // TODO: ParameterExpression is evaluable but not foldable + isEvaluable_ = true; + } + void visit(TagPropertyExpression *) override { isEvaluable_ = false; } void visit(EdgePropertyExpression *) override { isEvaluable_ = false; } diff --git a/src/graph/visitor/ExtractFilterExprVisitor.cpp b/src/graph/visitor/ExtractFilterExprVisitor.cpp index e43d48062fb..49ccb020844 100644 --- a/src/graph/visitor/ExtractFilterExprVisitor.cpp +++ b/src/graph/visitor/ExtractFilterExprVisitor.cpp @@ -13,6 +13,11 @@ void ExtractFilterExprVisitor::visit(ConstantExpression *) { canBePushed_ = true void ExtractFilterExprVisitor::visit(LabelExpression *) { canBePushed_ = false; } +void ExtractFilterExprVisitor::visit(ParameterExpression *) { + // TODO: support parameter push down + canBePushed_ = false; +} + void ExtractFilterExprVisitor::visit(UUIDExpression *) { canBePushed_ = false; } void ExtractFilterExprVisitor::visit(VariableExpression *expr) { diff --git a/src/graph/visitor/ExtractFilterExprVisitor.h b/src/graph/visitor/ExtractFilterExprVisitor.h index 72051d10d95..d2412b1ce38 100644 --- a/src/graph/visitor/ExtractFilterExprVisitor.h +++ b/src/graph/visitor/ExtractFilterExprVisitor.h @@ -44,6 +44,7 @@ class ExtractFilterExprVisitor final : public ExprVisitorImpl { void visit(LogicalExpression *) override; void visit(ColumnExpression *) override; void visit(SubscriptRangeExpression *) override; + void visit(ParameterExpression *) override; private: ObjectPool *pool_; diff --git a/src/graph/visitor/ExtractPropExprVisitor.cpp b/src/graph/visitor/ExtractPropExprVisitor.cpp index 6f60c26873a..16004fcb7ab 100644 --- a/src/graph/visitor/ExtractPropExprVisitor.cpp +++ b/src/graph/visitor/ExtractPropExprVisitor.cpp @@ -31,6 +31,8 @@ void ExtractPropExprVisitor::visit(VariableExpression* expr) { UNUSED(expr); } void ExtractPropExprVisitor::visit(SubscriptExpression* expr) { reportError(expr); } +void ExtractPropExprVisitor::visit(ParameterExpression*) {} + void ExtractPropExprVisitor::visit(LabelExpression* expr) { reportError(expr); } void ExtractPropExprVisitor::visit(LabelAttributeExpression* expr) { reportError(expr); } diff --git a/src/graph/visitor/ExtractPropExprVisitor.h b/src/graph/visitor/ExtractPropExprVisitor.h index afd311e34be..fd2420cb5d6 100644 --- a/src/graph/visitor/ExtractPropExprVisitor.h +++ b/src/graph/visitor/ExtractPropExprVisitor.h @@ -58,6 +58,7 @@ class ExtractPropExprVisitor final : public ExprVisitorImpl { void visit(SubscriptExpression *) override; // column expression void visit(ColumnExpression *) override; + void visit(ParameterExpression *) override; void visitVertexEdgePropExpr(PropertyExpression *); void visitPropertyExpr(PropertyExpression *); diff --git a/src/graph/visitor/FindVisitor.cpp b/src/graph/visitor/FindVisitor.cpp index 5f8142a5003..2ce88d2f4ee 100644 --- a/src/graph/visitor/FindVisitor.cpp +++ b/src/graph/visitor/FindVisitor.cpp @@ -192,6 +192,8 @@ void FindVisitor::visit(SubscriptRangeExpression* expr) { } } +void FindVisitor::visit(ParameterExpression* expr) { findInCurrentExpr(expr); } + void FindVisitor::visitBinaryExpr(BinaryExpression* expr) { findInCurrentExpr(expr); if (!needFindAll_ && !foundExprs_.empty()) return; diff --git a/src/graph/visitor/FindVisitor.h b/src/graph/visitor/FindVisitor.h index 3553c551734..ce8a7db7cb3 100644 --- a/src/graph/visitor/FindVisitor.h +++ b/src/graph/visitor/FindVisitor.h @@ -67,6 +67,7 @@ class FindVisitor final : public ExprVisitorImpl { void visit(ListComprehensionExpression* expr) override; void visit(SubscriptRangeExpression* expr) override; void visit(LogicalExpression* expr) override; + void visit(ParameterExpression* expr) override; void visitBinaryExpr(BinaryExpression* expr) override; void findInCurrentExpr(Expression* expr); diff --git a/src/graph/visitor/FoldConstantExprVisitor.cpp b/src/graph/visitor/FoldConstantExprVisitor.cpp index a075d7eef6b..a84e2cde910 100644 --- a/src/graph/visitor/FoldConstantExprVisitor.cpp +++ b/src/graph/visitor/FoldConstantExprVisitor.cpp @@ -275,6 +275,11 @@ void FoldConstantExprVisitor::visit(DestPropertyExpression *expr) { canBeFolded_ = false; } +void FoldConstantExprVisitor::visit(ParameterExpression *expr) { + UNUSED(expr); + canBeFolded_ = false; +} + void FoldConstantExprVisitor::visit(SourcePropertyExpression *expr) { UNUSED(expr); canBeFolded_ = false; diff --git a/src/graph/visitor/FoldConstantExprVisitor.h b/src/graph/visitor/FoldConstantExprVisitor.h index adae9393e03..2597a142762 100644 --- a/src/graph/visitor/FoldConstantExprVisitor.h +++ b/src/graph/visitor/FoldConstantExprVisitor.h @@ -75,6 +75,8 @@ class FoldConstantExprVisitor final : public ExprVisitor { void visit(ReduceExpression *expr) override; // subscript range expression void visit(SubscriptRangeExpression *expr) override; + // parameter expression + void visit(ParameterExpression *expr) override; void visitBinaryExpr(BinaryExpression *expr); Expression *fold(Expression *expr); diff --git a/src/graph/visitor/RewriteSymExprVisitor.cpp b/src/graph/visitor/RewriteSymExprVisitor.cpp index 52bf0dd578e..983a08ebefc 100644 --- a/src/graph/visitor/RewriteSymExprVisitor.cpp +++ b/src/graph/visitor/RewriteSymExprVisitor.cpp @@ -19,6 +19,11 @@ void RewriteSymExprVisitor::visit(ConstantExpression *expr) { expr_ = nullptr; } +void RewriteSymExprVisitor::visit(ParameterExpression *expr) { + UNUSED(expr); + expr_ = nullptr; +} + void RewriteSymExprVisitor::visit(UnaryExpression *expr) { expr->operand()->accept(this); if (expr_) { diff --git a/src/graph/visitor/RewriteSymExprVisitor.h b/src/graph/visitor/RewriteSymExprVisitor.h index 25795ba209d..84c4628ee97 100644 --- a/src/graph/visitor/RewriteSymExprVisitor.h +++ b/src/graph/visitor/RewriteSymExprVisitor.h @@ -75,6 +75,7 @@ class RewriteSymExprVisitor final : public ExprVisitor { void visit(ReduceExpression *expr) override; // subscript range expression void visit(SubscriptRangeExpression *expr) override; + void visit(ParameterExpression *expr) override; private: void visitBinaryExpr(BinaryExpression *expr); diff --git a/src/graph/visitor/RewriteVisitor.h b/src/graph/visitor/RewriteVisitor.h index 1a688b5c2a5..be7410c0038 100644 --- a/src/graph/visitor/RewriteVisitor.h +++ b/src/graph/visitor/RewriteVisitor.h @@ -83,6 +83,7 @@ class RewriteVisitor final : public ExprVisitorImpl { void visit(VertexExpression*) override {} void visit(EdgeExpression*) override {} void visit(ColumnExpression*) override {} + void visit(ParameterExpression*) override {} void visitBinaryExpr(BinaryExpression*) override; bool care(Expression::Kind kind); diff --git a/src/graph/visitor/VidExtractVisitor.h b/src/graph/visitor/VidExtractVisitor.h index 5f7f480ac79..e03e2cfc306 100644 --- a/src/graph/visitor/VidExtractVisitor.h +++ b/src/graph/visitor/VidExtractVisitor.h @@ -101,6 +101,7 @@ class VidExtractVisitor final : public ExprVisitor { void visit(ReduceExpression *expr) override; // subscript range expression void visit(SubscriptRangeExpression *expr) override; + void visit(ParameterExpression *) override {} private: void visitBinaryExpr(BinaryExpression *expr); diff --git a/src/interface/graph.thrift b/src/interface/graph.thrift index 08412492830..30ed79c215d 100644 --- a/src/interface/graph.thrift +++ b/src/interface/graph.thrift @@ -116,9 +116,11 @@ 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 8c56bec6a34..8706d32573b 100644 --- a/src/parser/parser.yy +++ b/src/parser/parser.yy @@ -544,7 +544,11 @@ expression delete $1; } | VARIABLE { - $$ = VariableExpression::make(qctx->objPool(), *$1); + if (qctx->existParameter(*$1)) { + $$ = ParameterExpression::make(qctx->objPool(), *$1); + } else { + $$ = VariableExpression::make(qctx->objPool(), *$1); + } delete $1; } | compound_expression { @@ -940,7 +944,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(), + ParameterExpression::make(qctx->objPool(),*$1), ConstantExpression::make(qctx->objPool(),*$3)); + } delete $1; delete $3; } @@ -1280,6 +1289,15 @@ from_clause | KW_FROM vid_ref_expression { $$ = new FromClause($2); } + | KW_FROM VARIABLE { + if (qctx->existParameter(*$2)) { + $$ = new FromClause(ParameterExpression::make(qctx->objPool(), *$2)); + } else { + delete($2); + throw nebula::GraphParser::syntax_error(@1, "can not go from a nonexist parameter"); + } + delete($2); + } ; vid_list @@ -2003,6 +2021,27 @@ fetch_vertices_sentence | KW_FETCH KW_PROP KW_ON STAR vid_ref_expression yield_clause { $$ = new FetchVerticesSentence($5, $6); } + | KW_FETCH KW_PROP KW_ON name_label_list VARIABLE yield_clause { + auto* param = ParameterExpression::make(qctx->objPool(), *$5); + if (!qctx->existParameter(*$5)) { + delete($4); + delete($5); + delete($6); + throw nebula::GraphParser::syntax_error(@5, "Invalid parameter "); + } + $$ = new FetchVerticesSentence($4, param, $6); + delete($5); + } + | KW_FETCH KW_PROP KW_ON STAR VARIABLE yield_clause { + auto* param = ParameterExpression::make(qctx->objPool(), *$5); + if (!qctx->existParameter(*$5)) { + delete($5); + delete($6); + throw nebula::GraphParser::syntax_error(@5, "Invalid parameter "); + } + $$ = new FetchVerticesSentence(param, $6); + delete($5); + } ; edge_key @@ -2684,7 +2723,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/src/storage/query/QueryBaseProcessor-inl.h b/src/storage/query/QueryBaseProcessor-inl.h index 1622a66c1c5..e96667885ff 100644 --- a/src/storage/query/QueryBaseProcessor-inl.h +++ b/src/storage/query/QueryBaseProcessor-inl.h @@ -577,7 +577,8 @@ nebula::cpp2::ErrorCode QueryBaseProcessor::checkExp(const Expression case Expression::Kind::kTSFuzzy: case Expression::Kind::kAggregate: case Expression::Kind::kSubscriptRange: - case Expression::Kind::kVersionedVar: { + case Expression::Kind::kVersionedVar: + case Expression::Kind::kParam: { LOG(ERROR) << "Unimplemented expression type! kind = " << exp->kind(); return nebula::cpp2::ErrorCode::E_INVALID_FILTER; } diff --git a/tests/common/utils.py b/tests/common/utils.py index 722298e10b7..238fcbd0de3 100644 --- a/tests/common/utils.py +++ b/tests/common/utils.py @@ -21,6 +21,9 @@ 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') @@ -346,8 +349,8 @@ def wrapper(*args, **kwargs): @retry(30) -def try_execute(sess: Session, stmt: str): - return sess.execute(stmt) +def try_execute(sess: Session, stmt: str): + return sess.execute_parameter(stmt, params) def return_if_not_leader_changed(resp) -> bool: @@ -362,7 +365,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): diff --git a/tests/tck/conftest.py b/tests/tck/conftest.py index fc2d8f67685..801606b77d1 100644 --- a/tests/tck/conftest.py +++ b/tests/tck/conftest.py @@ -11,8 +11,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.tck.utils.table import dataset, table from tests.tck.utils.nbv import murmurhash2 @@ -116,6 +118,51 @@ 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( request, diff --git a/tests/tck/features/go/GO.IntVid.feature b/tests/tck/features/go/GO.IntVid.feature index 1bb89bfca70..87a90ea0e11 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 """ - Then a SyntaxError should be raised at runtime: syntax error near `OVER' + Then a SyntaxError should be raised at runtime: can not go from a nonexist parameter near `FROM' Scenario: Integer Vid distinct When executing query: diff --git a/tests/tck/features/go/GO.feature b/tests/tck/features/go/GO.feature index f01af51b7f6..ecc18362e89 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 """ - Then a SyntaxError should be raised at runtime: syntax error near `OVER' + Then a SyntaxError should be raised at runtime: can not go from a nonexist parameter near `FROM' Scenario: distinct When executing query: diff --git a/tests/tck/features/go/GoYieldVertexEdge.feature b/tests/tck/features/go/GoYieldVertexEdge.feature index b38ebc7f876..a953c2b3425 100644 --- a/tests/tck/features/go/GoYieldVertexEdge.feature +++ b/tests/tck/features/go/GoYieldVertexEdge.feature @@ -224,7 +224,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: can not go from a nonexist parameter near `FROM' @skip # reason we not support hash hash hash from now on. line 67 in Value.cpp diff --git a/tests/tck/features/yield/parameter.feature b/tests/tck/features/yield/parameter.feature new file mode 100644 index 00000000000..8bd0d2e99ec --- /dev/null +++ b/tests/tck/features/yield/parameter.feature @@ -0,0 +1,121 @@ +# Copyright (c) 2021 vesoft inc. All rights reserved. +# +# This source code is licensed under Apache 2.0 License, +# attached with Common Clause Condition 1.0, found in the LICENSES directory. +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}} + + 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 + """ + Then the result should be, in any order: + | ival | bval | sval | fval | lval | mval | + | 2 | false | "Tim Duncanef" | 4.1 | [1,true,3] | 3 | + + Scenario: go with parameters + When executing query: + """ + $var=GO FROM $p3 OVER like YIELD like._dst AS dst, $p3+$p1+$p4 AS params, $$.player.age AS age; + GO FROM $var.dst OVER like YIELD DISTINCT $var.dst, $var.params, $var.age + """ + Then the result should be, in any order: + | $var.dst | $var.params | $var.age | + | "Manu Ginobili" | "Tim Duncan13.3" | 41 | + | "Tony Parker" | "Tim Duncan13.3" | 36 | + + 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: + """ + 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: fetch with parameters + When executing query: + """ + FETCH PROP ON * $p3 + """ + Then the result should be, in any order: + | vertices_ | + | ("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"}) | + + Scenario: error check + 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: + """ + FETCH PROP ON player $nonexist + """ + Then a SyntaxError should be raised at runtime: Invalid parameter near `$nonexist' + 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 SemanticError should be raised at runtime: `$p4', the srcs should be type of FIXED_STRING, but was`FLOAT' + When executing query: + """ + GO FROM $p3 OVER like WHERE like.likeness>toFloat($p6) yield like._dst as dst; + """ + Then a SemanticError should be raised at runtime: `toFloat($p6)' is not a valid expression : Parameter's type error + When executing query: + """ + MATCH (v:player) RETURN v LIMIT $p6 + """ + Then a SemanticError should be raised at runtime: LIMIT should be of type integer + + @skip + Scenario: lookup with parameters + When executing query: + """ + # parameter push-down not supported yet + LOOKUP ON player WHERE player.age>$p2+43 + """ + Then the result should be, in any order: + | VertexID | + | "Grant Hill" | + | "Jason Kidd" | + | "Shaquile O'Neal" | + | "Steve Nash" |