From b4f83ebc9c4cbf9ad9d7452797aeb45eea39edf4 Mon Sep 17 00:00:00 2001 From: Zhijie Zhang <767407581@qq.com> Date: Tue, 29 Oct 2024 15:15:04 +0800 Subject: [PATCH] Enable MULTIUPDATE and LOOKUP | UPDATE (#5953) * enable multiupdate and lookup & update * deleted keyword LOOKUP|UPDATE and replace MULTIUPDATE with UPDATE * add tck tests * combined update, multiupdate & refupdate into one routine * first revision after code review * second round revision * third revision * edit error message --------- Co-authored-by: yuxuan.wang <48981679+Salieri-004@users.noreply.github.com> --- src/graph/executor/mutate/DeleteExecutor.cpp | 14 +- src/graph/executor/mutate/UpdateExecutor.cpp | 282 +++++++++++++----- src/graph/executor/mutate/UpdateExecutor.h | 2 +- src/graph/planner/plan/Mutate.cpp | 4 +- src/graph/planner/plan/Mutate.h | 90 ++++-- src/graph/service/PermissionCheck.cpp | 3 +- src/graph/validator/MutateValidator.cpp | 240 +++++++++++---- src/graph/validator/MutateValidator.h | 19 +- .../validator/test/MutateValidatorTest.cpp | 8 +- src/parser/EdgeKey.h | 31 ++ src/parser/MutateSentences.cpp | 20 +- src/parser/MutateSentences.h | 64 +++- src/parser/parser.yy | 45 ++- .../delete/DeleteVertex.IntVid.feature | 5 +- .../tck/features/lookup/LookUpUpdate.feature | 132 ++++++++ tests/tck/features/update/MultiUpdate.feature | 282 ++++++++++++++++++ 16 files changed, 1046 insertions(+), 195 deletions(-) create mode 100644 tests/tck/features/lookup/LookUpUpdate.feature create mode 100644 tests/tck/features/update/MultiUpdate.feature diff --git a/src/graph/executor/mutate/DeleteExecutor.cpp b/src/graph/executor/mutate/DeleteExecutor.cpp index 15ef49a36b8..d7cdb849ee8 100644 --- a/src/graph/executor/mutate/DeleteExecutor.cpp +++ b/src/graph/executor/mutate/DeleteExecutor.cpp @@ -45,9 +45,9 @@ folly::Future DeleteVerticesExecutor::deleteVertices() { continue; } if (!SchemaUtil::isValidVid(val, *spaceInfo.spaceDesc.vid_type_ref())) { - std::stringstream ss; - ss << "Wrong vid type `" << val.type() << "', value `" << val.toString() << "'"; - return Status::Error(ss.str()); + auto errorMsg = fmt::format( + "Wrong vid type `{}', value `{}'", Value::toString(val.type()), val.toString()); + return Status::Error(errorMsg); } vertices.emplace_back(std::move(val)); } @@ -103,9 +103,9 @@ folly::Future DeleteTagsExecutor::deleteTags() { continue; } if (!SchemaUtil::isValidVid(val, *spaceInfo.spaceDesc.vid_type_ref())) { - std::stringstream ss; - ss << "Wrong vid type `" << val.type() << "', value `" << val.toString() << "'"; - return Status::Error(ss.str()); + auto errorMsg = fmt::format( + "Wrong vid type `{}', value `{}'", Value::toString(val.type()), val.toString()); + return Status::Error(errorMsg); } delTag.id_ref() = val; delTag.tags_ref() = dtNode->tagIds(); @@ -147,7 +147,7 @@ folly::Future DeleteEdgesExecutor::deleteEdges() { DCHECK(!inputVar.empty()); auto& inputResult = ectx_->getResult(inputVar); auto iter = inputResult.iter(); - edgeKeys.reserve(iter->size()); + edgeKeys.reserve(iter->size() * 2); QueryExpressionContext ctx(ectx_); for (; iter->valid(); iter->next()) { storage::cpp2::EdgeKey edgeKey; diff --git a/src/graph/executor/mutate/UpdateExecutor.cpp b/src/graph/executor/mutate/UpdateExecutor.cpp index 3b820ac471a..9111b10ec76 100644 --- a/src/graph/executor/mutate/UpdateExecutor.cpp +++ b/src/graph/executor/mutate/UpdateExecutor.cpp @@ -13,7 +13,7 @@ using nebula::storage::StorageClient; namespace nebula { namespace graph { -StatusOr UpdateBaseExecutor::handleResult(DataSet &&data) { +Status UpdateBaseExecutor::handleMultiResult(DataSet &result, DataSet &&data) { if (data.colNames.size() <= 1) { if (yieldNames_.empty()) { return Status::OK(); @@ -26,8 +26,7 @@ StatusOr UpdateBaseExecutor::handleResult(DataSet &&data) { << data.colNames.size() - 1; return Status::Error("Wrong return prop size"); } - DataSet result; - result.colNames = std::move(yieldNames_); + result.colNames = yieldNames_; for (auto &row : data.rows) { std::vector columns; for (auto i = 1u; i < row.values.size(); i++) { @@ -35,106 +34,247 @@ StatusOr UpdateBaseExecutor::handleResult(DataSet &&data) { } result.rows.emplace_back(std::move(columns)); } - return result; + return Status::OK(); } folly::Future UpdateVertexExecutor::execute() { SCOPED_TIMER(&execTime_); - auto *uvNode = asNode(node()); - yieldNames_ = uvNode->getYieldNames(); + auto *urvNode = asNode(node()); + + auto vidRef = urvNode->getVidRef(); + std::vector vertices; + const auto &spaceInfo = qctx()->rctx()->session()->space(); + + if (vidRef != nullptr) { + auto inputVar = urvNode->inputVar(); + DCHECK(!inputVar.empty()); + auto &inputResult = ectx_->getResult(inputVar); + auto iter = inputResult.iter(); + vertices.reserve(iter->size()); + QueryExpressionContext ctx(ectx_); + for (; iter->valid(); iter->next()) { + auto val = Expression::eval(vidRef, ctx(iter.get())); + if (val.isNull() || val.empty()) { + continue; + } + if (!SchemaUtil::isValidVid(val, *spaceInfo.spaceDesc.vid_type_ref())) { + auto errorMsg = fmt::format( + "Wrong vid type `{}', value `{}'", Value::toString(val.type()), val.toString()); + return Status::Error(errorMsg); + } + vertices.emplace_back(std::move(val)); + } + } + + if (vertices.empty()) { + return Status::OK(); + } + + std::vector>> futures; + futures.reserve(vertices.size()); + + yieldNames_ = urvNode->getYieldNames(); time::Duration updateVertTime; auto plan = qctx()->plan(); auto sess = qctx()->rctx()->session(); StorageClient::CommonRequestParam param( - uvNode->getSpaceId(), sess->id(), plan->id(), plan->isProfileEnabled()); - return qctx() - ->getStorageClient() - ->updateVertex(param, - uvNode->getVId(), - uvNode->getTagId(), - uvNode->getUpdatedProps(), - uvNode->getInsertable(), - uvNode->getReturnProps(), - uvNode->getCondition()) + urvNode->getSpaceId(), sess->id(), plan->id(), plan->isProfileEnabled()); + + for (auto &vId : vertices) { + futures.emplace_back(qctx() + ->getStorageClient() + ->updateVertex(param, + vId, + urvNode->getTagId(), + urvNode->getUpdatedProps(), + urvNode->getInsertable(), + urvNode->getReturnProps(), + urvNode->getCondition()) + .via(runner())); + } + + return folly::collectAll(futures) .via(runner()) .ensure([updateVertTime]() { - VLOG(1) << "Update vertice time: " << updateVertTime.elapsedInUSec() << "us"; + VLOG(1) << "updateVertTime: " << updateVertTime.elapsedInUSec() << "us"; }) - .thenValue([this](StatusOr resp) { + .thenValue([this](std::vector>> results) { memory::MemoryCheckGuard guard; SCOPED_TIMER(&execTime_); - if (!resp.ok()) { - LOG(WARNING) << "Update vertices fail: " << resp.status(); - return resp.status(); - } - auto value = std::move(resp).value(); - for (auto &code : value.get_result().get_failed_parts()) { - NG_RETURN_IF_ERROR(handleErrorCode(code.get_code(), code.get_part_id())); - } - if (value.props_ref().has_value()) { - auto status = handleResult(std::move(*value.props_ref())); - if (!status.ok()) { - return status.status(); + DataSet finalResult; + for (auto &result : results) { + if (result.hasException()) { + LOG(WARNING) << "Update vertex request threw an exception."; + return Status::Error("Exception occurred during update."); } - return finish(ResultBuilder() - .value(std::move(status).value()) - .iter(Iterator::Kind::kDefault) - .build()); + + if (!result.value().ok()) { + LOG(WARNING) << "Update vertex failed: " << result.value().status(); + return result.value().status(); + } + + auto value = std::move(result.value()).value(); + for (auto &code : value.get_result().get_failed_parts()) { + NG_RETURN_IF_ERROR(handleErrorCode(code.get_code(), code.get_part_id())); + } + + if (value.props_ref().has_value()) { + auto status = handleMultiResult(finalResult, std::move(*value.props_ref())); + if (!status.ok()) { + return status; + } + } + } + + if (finalResult.colNames.empty()) { + return Status::OK(); + } else { + return finish( + ResultBuilder().value(std::move(finalResult)).iter(Iterator::Kind::kDefault).build()); } - return Status::OK(); }); } folly::Future UpdateEdgeExecutor::execute() { SCOPED_TIMER(&execTime_); - auto *ueNode = asNode(node()); - storage::cpp2::EdgeKey edgeKey; - edgeKey.src_ref() = ueNode->getSrcId(); - edgeKey.ranking_ref() = ueNode->getRank(); - edgeKey.edge_type_ref() = ueNode->getEdgeType(); - edgeKey.dst_ref() = ueNode->getDstId(); - yieldNames_ = ueNode->getYieldNames(); - + auto *ureNode = asNode(node()); + auto *edgeKeyRef = DCHECK_NOTNULL(ureNode->getEdgeKeyRef()); + auto edgeType = ureNode->getEdgeType(); time::Duration updateEdgeTime; + + yieldNames_ = ureNode->getYieldNames(); + + const auto &spaceInfo = qctx()->rctx()->session()->space(); + auto inputVar = ureNode->inputVar(); + DCHECK(!inputVar.empty()); + auto &inputResult = ectx_->getResult(inputVar); + auto iter = inputResult.iter(); + + std::vector edgeKeys; + std::vector reverse_edgeKeys; + edgeKeys.reserve(iter->size()); + reverse_edgeKeys.reserve(iter->size()); + + QueryExpressionContext ctx(ectx_); + for (; iter->valid(); iter->next()) { + auto srcId = Expression::eval(edgeKeyRef->srcid(), ctx(iter.get())); + if (!SchemaUtil::isValidVid(srcId, *spaceInfo.spaceDesc.vid_type_ref())) { + std::stringstream ss; + ss << "Wrong srcId type `" << srcId.type() << "`, value `" << srcId.toString() << "'"; + return Status::Error(ss.str()); + } + auto dstId = Expression::eval(edgeKeyRef->dstid(), ctx(iter.get())); + if (!SchemaUtil::isValidVid(dstId, *spaceInfo.spaceDesc.vid_type_ref())) { + std::stringstream ss; + ss << "Wrong dstId type `" << dstId.type() << "', value `" << dstId.toString() << "'"; + return Status::Error(ss.str()); + } + auto rank = Expression::eval(edgeKeyRef->rank(), ctx(iter.get())); + if (!rank.isInt()) { + std::stringstream ss; + ss << "Wrong rank type `" << rank.type() << "', value `" << rank.toString() << "'"; + return Status::Error(ss.str()); + } + DCHECK(edgeKeyRef->type()); + auto type = Expression::eval(edgeKeyRef->type(), ctx(iter.get())); + if (!type.isInt()) { + std::stringstream ss; + ss << "Wrong edge type `" << type.type() << "', value `" << type.toString() << "'"; + return Status::Error(ss.str()); + } + storage::cpp2::EdgeKey edgeKey; + edgeKey.src_ref() = srcId; + edgeKey.dst_ref() = dstId; + edgeKey.ranking_ref() = rank.getInt(); + edgeKey.edge_type_ref() = edgeType; + + storage::cpp2::EdgeKey reverse_edgeKey; + reverse_edgeKey.src_ref() = std::move(dstId); + reverse_edgeKey.dst_ref() = std::move(srcId); + reverse_edgeKey.ranking_ref() = rank.getInt(); + reverse_edgeKey.edge_type_ref() = -edgeType; + + edgeKeys.emplace_back(std::move(edgeKey)); + reverse_edgeKeys.emplace_back(std::move(reverse_edgeKey)); + } + + if (edgeKeys.empty()) { + return Status::OK(); + } + auto plan = qctx()->plan(); StorageClient::CommonRequestParam param( - ueNode->getSpaceId(), qctx()->rctx()->session()->id(), plan->id(), plan->isProfileEnabled()); + ureNode->getSpaceId(), qctx()->rctx()->session()->id(), plan->id(), plan->isProfileEnabled()); param.useExperimentalFeature = false; - return qctx() - ->getStorageClient() - ->updateEdge(param, - edgeKey, - ueNode->getUpdatedProps(), - ueNode->getInsertable(), - ueNode->getReturnProps(), - ueNode->getCondition()) + + std::vector>> futures; + futures.reserve(edgeKeys.size() + reverse_edgeKeys.size()); + + for (auto &edgeKey : edgeKeys) { + futures.emplace_back(qctx() + ->getStorageClient() + ->updateEdge(param, + edgeKey, + ureNode->getUpdatedProps(), + ureNode->getInsertable(), + {}, + ureNode->getCondition()) + .via(runner())); + } + + for (auto &edgeKey : reverse_edgeKeys) { + futures.emplace_back(qctx() + ->getStorageClient() + ->updateEdge(param, + edgeKey, + ureNode->getUpdatedProps(), + ureNode->getInsertable(), + ureNode->getReturnProps(), + ureNode->getCondition()) + .via(runner())); + } + + return folly::collectAll(futures) .via(runner()) .ensure([updateEdgeTime]() { - VLOG(1) << "Update edge time: " << updateEdgeTime.elapsedInUSec() << "us"; + VLOG(1) << "updateEdgeTime: " << updateEdgeTime.elapsedInUSec() << "us"; }) - .thenValue([this](StatusOr resp) { + .thenValue([this](std::vector>> results) { memory::MemoryCheckGuard guard; SCOPED_TIMER(&execTime_); - if (!resp.ok()) { - LOG(WARNING) << "Update edge failed: " << resp.status(); - return resp.status(); - } - auto value = std::move(resp).value(); - for (auto &code : value.get_result().get_failed_parts()) { - NG_RETURN_IF_ERROR(handleErrorCode(code.get_code(), code.get_part_id())); - } - if (value.props_ref().has_value()) { - auto status = handleResult(std::move(*value.props_ref())); - if (!status.ok()) { - return status.status(); + DataSet finalResult; + for (auto &result : results) { + if (result.hasException()) { + LOG(WARNING) << "Update edge request threw an exception."; + return Status::Error("Exception occurred during update."); + } + + if (!result.value().ok()) { + LOG(WARNING) << "Update edge failed: " << result.value().status(); + return result.value().status(); + } + + auto value = std::move(result.value()).value(); + for (auto &code : value.get_result().get_failed_parts()) { + NG_RETURN_IF_ERROR(handleErrorCode(code.get_code(), code.get_part_id())); + } + + if (value.props_ref().has_value() && value.props_ref()->colNames.size() > 1) { + auto status = handleMultiResult(finalResult, std::move(*value.props_ref())); + if (!status.ok()) { + return status; + } } - return finish(ResultBuilder() - .value(std::move(status).value()) - .iter(Iterator::Kind::kDefault) - .build()); } - return Status::OK(); + + if (finalResult.colNames.empty()) { + return Status::OK(); + } else { + return finish( + ResultBuilder().value(std::move(finalResult)).iter(Iterator::Kind::kDefault).build()); + } }); } + } // namespace graph } // namespace nebula diff --git a/src/graph/executor/mutate/UpdateExecutor.h b/src/graph/executor/mutate/UpdateExecutor.h index 9cb13e8d86b..09c2d375d15 100644 --- a/src/graph/executor/mutate/UpdateExecutor.h +++ b/src/graph/executor/mutate/UpdateExecutor.h @@ -18,7 +18,7 @@ class UpdateBaseExecutor : public StorageAccessExecutor { virtual ~UpdateBaseExecutor() {} protected: - StatusOr handleResult(DataSet &&data); + Status handleMultiResult(DataSet &result, DataSet &&data); protected: std::vector yieldNames_; diff --git a/src/graph/planner/plan/Mutate.cpp b/src/graph/planner/plan/Mutate.cpp index 8bddf3da506..021d85255d0 100644 --- a/src/graph/planner/plan/Mutate.cpp +++ b/src/graph/planner/plan/Mutate.cpp @@ -58,9 +58,7 @@ std::unique_ptr UpdateVertex::explain() const { std::unique_ptr UpdateEdge::explain() const { auto desc = Update::explain(); - addDescription("srcId", srcId_.toString(), desc.get()); - addDescription("dstId", dstId_.toString(), desc.get()); - addDescription("rank", folly::to(rank_), desc.get()); + addDescription("edgeKeyRef", edgeKeyRef_ ? edgeKeyRef_->toString() : "", desc.get()); addDescription("edgeType", folly::to(edgeType_), desc.get()); return desc; } diff --git a/src/graph/planner/plan/Mutate.h b/src/graph/planner/plan/Mutate.h index 4ee4e8d7b4c..6edf1dcd205 100644 --- a/src/graph/planner/plan/Mutate.h +++ b/src/graph/planner/plan/Mutate.h @@ -152,7 +152,7 @@ class InsertEdges final : public SingleDependencyNode { bool useChainInsert_{false}; }; -class Update : public SingleDependencyNode { +class Update : public SingleInputNode { public: bool getInsertable() const { return insertable_; @@ -196,7 +196,7 @@ class Update : public SingleDependencyNode { std::vector returnProps, std::string condition, std::vector yieldNames) - : SingleDependencyNode(qctx, kind, input), + : SingleInputNode(qctx, kind, input), spaceId_(spaceId), schemaName_(std::move(name)), insertable_(insertable), @@ -241,12 +241,41 @@ class UpdateVertex final : public Update { std::move(yieldNames)); } + static UpdateVertex* make(QueryContext* qctx, + PlanNode* input, + GraphSpaceID spaceId, + std::string name, + Expression* vidRef, + TagID tagId, + bool insertable, + std::vector updatedProps, + std::vector returnProps, + std::string condition, + std::vector yieldNames) { + return qctx->objPool()->makeAndAdd(qctx, + input, + spaceId, + std::move(name), + vidRef, + tagId, + insertable, + std::move(updatedProps), + std::move(returnProps), + std::move(condition), + std::move(yieldNames)); + } + std::unique_ptr explain() const override; + // no used any more const Value& getVId() const { return vId_; } + Expression* getVidRef() const { + return vidRef_; + } + TagID getTagId() const { return tagId_; } @@ -277,8 +306,33 @@ class UpdateVertex final : public Update { vId_(std::move(vId)), tagId_(tagId) {} + UpdateVertex(QueryContext* qctx, + PlanNode* input, + GraphSpaceID spaceId, + std::string name, + Expression* vidRef, + TagID tagId, + bool insertable, + std::vector updatedProps, + std::vector returnProps, + std::string condition, + std::vector yieldNames) + : Update(qctx, + Kind::kUpdateVertex, + input, + spaceId, + std::move(name), + insertable, + std::move(updatedProps), + std::move(returnProps), + std::move(condition), + std::move(yieldNames)), + vidRef_(vidRef), + tagId_(tagId) {} + private: Value vId_; + Expression* vidRef_{nullptr}; TagID tagId_{-1}; }; @@ -288,10 +342,8 @@ class UpdateEdge final : public Update { PlanNode* input, GraphSpaceID spaceId, std::string name, - Value srcId, - Value dstId, + EdgeKeyRef* edgeKeyRef, EdgeType edgeType, - int64_t rank, bool insertable, std::vector updatedProps, std::vector returnProps, @@ -301,10 +353,8 @@ class UpdateEdge final : public Update { input, spaceId, std::move(name), - std::move(srcId), - std::move(dstId), + edgeKeyRef, edgeType, - rank, insertable, std::move(updatedProps), std::move(returnProps), @@ -314,16 +364,8 @@ class UpdateEdge final : public Update { std::unique_ptr explain() const override; - const Value& getSrcId() const { - return srcId_; - } - - const Value& getDstId() const { - return dstId_; - } - - int64_t getRank() const { - return rank_; + EdgeKeyRef* getEdgeKeyRef() const { + return edgeKeyRef_; } int64_t getEdgeType() const { @@ -340,10 +382,8 @@ class UpdateEdge final : public Update { PlanNode* input, GraphSpaceID spaceId, std::string name, - Value srcId, - Value dstId, + EdgeKeyRef* edgeKeyRef, EdgeType edgeType, - int64_t rank, bool insertable, std::vector updatedProps, std::vector returnProps, @@ -359,15 +399,11 @@ class UpdateEdge final : public Update { std::move(returnProps), std::move(condition), std::move(yieldNames)), - srcId_(std::move(srcId)), - dstId_(std::move(dstId)), - rank_(rank), + edgeKeyRef_(edgeKeyRef), edgeType_(edgeType) {} private: - Value srcId_; - Value dstId_; - int64_t rank_{0}; + EdgeKeyRef* edgeKeyRef_{nullptr}; EdgeType edgeType_{-1}; }; diff --git a/src/graph/service/PermissionCheck.cpp b/src/graph/service/PermissionCheck.cpp index db5b9fd834a..ba97217afea 100644 --- a/src/graph/service/PermissionCheck.cpp +++ b/src/graph/service/PermissionCheck.cpp @@ -23,7 +23,8 @@ namespace graph { * kYield, kOrderBy, kFetchVertices, kFind * kFetchEdges, kFindPath, kLimit, KGroupBy, kReturn * Write data: kBuildTagIndex, kBuildEdgeIndex, - * kInsertVertex, kUpdateVertex, kInsertEdge, + * kInsertVertex, kUpdateVertex, + * kInsertEdge, * kUpdateEdge, kDeleteVertex, kDeleteEdges, kAdminJob(other) * Special operation : kShow, kChangePassword */ diff --git a/src/graph/validator/MutateValidator.cpp b/src/graph/validator/MutateValidator.cpp index 9eb2b6413ed..f79cdae7243 100644 --- a/src/graph/validator/MutateValidator.cpp +++ b/src/graph/validator/MutateValidator.cpp @@ -318,10 +318,11 @@ Status DeleteVerticesValidator::validateImpl() { auto type = deduceExprType(vidRef_); NG_RETURN_IF_ERROR(type); if (type.value() != vidType_) { - std::stringstream ss; - ss << "The vid `" << vidRef_->toString() << "' should be type of `" << vidType_ - << "', but was`" << type.value() << "'"; - return Status::SemanticError(ss.str()); + auto errorMsg = fmt::format("The vid `{}' should be type of `{}', but was `{}'", + vidRef_->toString(), + Value::toString(vidType_), + Value::toString(type.value())); + return Status::SemanticError(errorMsg); } } else { auto vIds = sentence->vertices()->vidList(); @@ -460,10 +461,11 @@ Status DeleteTagsValidator::validateImpl() { auto type = deduceExprType(vidRef_); NG_RETURN_IF_ERROR(type); if (type.value() != vidType_) { - std::stringstream ss; - ss << "The vid `" << vidRef_->toString() << "' should be type of `" << vidType_ - << "', but was`" << type.value() << "'"; - return Status::SemanticError(ss.str()); + auto errorMsg = fmt::format("The vid `{}' should be type of `{}', but was `{}'", + vidRef_->toString(), + Value::toString(vidType_), + Value::toString(type.value())); + return Status::SemanticError(errorMsg); } } else { auto vIds = sentence->vertices()->vidList(); @@ -673,9 +675,9 @@ Status UpdateValidator::getCondition() { auto type = typeStatus.value(); if (type != Value::Type::BOOL && type != Value::Type::NULLVALUE && type != Value::Type::__EMPTY__) { - std::stringstream ss; - ss << "`" << filter->toString() << "', expected Boolean, but was `" << type << "'"; - return Status::SemanticError(ss.str()); + auto errorMsg = fmt::format( + "`{}', expected Boolean, but was `{}'", filter->toString(), Value::toString(type)); + return Status::SemanticError(errorMsg); } condition_ = filter->encode(); } @@ -836,12 +838,37 @@ Expression *UpdateValidator::rewriteSymExpr(Expression *expr, Status UpdateVertexValidator::validateImpl() { auto sentence = static_cast(sentence_); - auto idRet = SchemaUtil::toVertexID(sentence->getVid(), vidType_); - if (!idRet.ok()) { - LOG(ERROR) << idRet.status(); - return std::move(idRet).status(); + // vid != nullptr + if (sentence->getVid() != nullptr) { + auto idRet = SchemaUtil::toVertexID(sentence->getVid(), vidType_); + if (!idRet.ok()) { + LOG(ERROR) << idRet.status(); + return std::move(idRet).status(); + } + vId_ = std::move(idRet).value(); + vertices_.emplace_back(vId_); + } else if (sentence->vertices()->isRef()) { + vidRef_ = sentence->vertices()->ref(); + auto type = deduceExprType(vidRef_); + NG_RETURN_IF_ERROR(type); + if (type.value() != vidType_) { + auto errorMsg = fmt::format("The vid `{}' should be type of `{}', but was `{}'", + vidRef_->toString(), + Value::toString(vidType_), + Value::toString(type.value())); + return Status::SemanticError(errorMsg); + } + } else { + // get vertices + for (auto &vid : sentence->vertices()->vidList()) { + auto idRet = SchemaUtil::toVertexID(vid, vidType_); + if (!idRet.ok()) { + LOG(ERROR) << idRet.status(); + return std::move(idRet).status(); + } + vertices_.emplace_back(std::move(idRet).value()); + } } - vId_ = std::move(idRet).value(); NG_RETURN_IF_ERROR(initProps()); auto ret = qctx_->schemaMng()->toTagID(spaceId_, name_); if (!ret.ok()) { @@ -852,12 +879,41 @@ Status UpdateVertexValidator::validateImpl() { return Status::OK(); } +// same as DeleteVerticesValidator +std::string UpdateVertexValidator::buildVIds() { + auto input = vctx_->anonVarGen()->getVar(); + DataSet ds; + ds.colNames.emplace_back(kVid); + for (auto &vid : vertices_) { + Row row; + row.values.emplace_back(vid); + ds.rows.emplace_back(std::move(row)); + } + qctx_->ectx()->setResult(input, ResultBuilder().value(Value(std::move(ds))).build()); + auto *pool = qctx_->objPool(); + auto *vIds = VariablePropertyExpression::make(pool, input, kVid); + vidRef_ = vIds; + return input; +} + Status UpdateVertexValidator::toPlan() { + std::string vidVar; + if (!vertices_.empty() && vidRef_ == nullptr) { + vidVar = buildVIds(); + } else if (vidRef_ != nullptr && vidRef_->kind() == Expression::Kind::kVarProperty) { + vidVar = static_cast(vidRef_)->sym(); + } else if (vidRef_ != nullptr && vidRef_->kind() == Expression::Kind::kInputProperty) { + vidVar = inputVarName_; + } + + auto *dedupVid = Dedup::make(qctx_, nullptr); + dedupVid->setInputVar(vidVar); + auto *update = UpdateVertex::make(qctx_, - nullptr, + dedupVid, spaceId_, std::move(name_), - vId_, + vidRef_, tagId_, insertable_, std::move(updatedProps_), @@ -865,64 +921,142 @@ Status UpdateVertexValidator::toPlan() { std::move(condition_), std::move(yieldColNames_)); root_ = update; - tail_ = root_; + tail_ = dedupVid; return Status::OK(); } Status UpdateEdgeValidator::validateImpl() { auto sentence = static_cast(sentence_); - auto srcIdRet = SchemaUtil::toVertexID(sentence->getSrcId(), vidType_); + spaceId_ = vctx_->whichSpace().id; + + NG_RETURN_IF_ERROR(initProps()); + auto ret = qctx_->schemaMng()->toEdgeType(spaceId_, name_); + if (!ret.ok()) { + LOG(ERROR) << "No schema found for " << name_ << " : " << ret.status(); + return Status::SemanticError("No schema found for `%s'", name_.c_str()); + } + edgeType_ = ret.value(); + + if (sentence->getSrcId() != nullptr && sentence->getDstId() != nullptr) { + NG_RETURN_IF_ERROR( + processEdgeKeys(sentence->getSrcId(), sentence->getDstId(), sentence->getRank())); + } else if (sentence->isRef()) { + auto *pool = qctx_->objPool(); + edgeKeyRefs_.emplace_back(sentence->edgeKeyRef()); + (*edgeKeyRefs_.begin())->setType(ConstantExpression::make(pool, edgeType_)); + NG_RETURN_IF_ERROR(checkInput()); + } else { + for (auto &edgekey : sentence->getEdgeKeys()->keys()) { + NG_RETURN_IF_ERROR(processEdgeKeys(edgekey->srcid(), edgekey->dstid(), edgekey->rank())); + } + } + + if (!sentence->isRef()) { + assert(!edgeIds_.empty()); + buildEdgeKeyRef(); + } + + return Status::OK(); +} + +Status UpdateEdgeValidator::processEdgeKeys(Expression *src_id, + Expression *dst_id, + const EdgeRanking &rank) { + auto srcIdRet = SchemaUtil::toVertexID(src_id, vidType_); if (!srcIdRet.ok()) { LOG(ERROR) << srcIdRet.status(); return srcIdRet.status(); } - srcId_ = std::move(srcIdRet).value(); - auto dstIdRet = SchemaUtil::toVertexID(sentence->getDstId(), vidType_); + auto dstIdRet = SchemaUtil::toVertexID(dst_id, vidType_); if (!dstIdRet.ok()) { LOG(ERROR) << dstIdRet.status(); return dstIdRet.status(); } - dstId_ = std::move(dstIdRet).value(); - rank_ = sentence->getRank(); - NG_RETURN_IF_ERROR(initProps()); - auto ret = qctx_->schemaMng()->toEdgeType(spaceId_, name_); - if (!ret.ok()) { - LOG(ERROR) << "No schema found for " << name_ << " : " << ret.status(); - return Status::SemanticError("No schema found for `%s'", name_.c_str()); + EdgeId edgeId = EdgeId(std::move(srcIdRet).value(), std::move(dstIdRet).value(), rank); + + edgeIds_.emplace_back(std::move(edgeId)); + return Status::OK(); +} + +Status UpdateEdgeValidator::buildEdgeKeyRef() { + edgeKeyVar_ = vctx_->anonVarGen()->getVar(); + DataSet ds({kSrc, kType, kRank, kDst}); + for (auto &edgeId : edgeIds_) { + Row row; + row.emplace_back(edgeId.srcid()); + row.emplace_back(edgeType_); + row.emplace_back(edgeId.rank()); + row.emplace_back(edgeId.dstid()); + ds.emplace_back(std::move(row)); } - edgeType_ = ret.value(); + + qctx_->ectx()->setResult(edgeKeyVar_, ResultBuilder().value(Value(std::move(ds))).build()); + auto *pool = qctx_->objPool(); + auto *srcIdExpr = InputPropertyExpression::make(pool, kSrc); + auto *typeExpr = InputPropertyExpression::make(pool, kType); + auto *rankExpr = InputPropertyExpression::make(pool, kRank); + auto *dstIdExpr = InputPropertyExpression::make(pool, kDst); + auto *edgeKeyRef = qctx_->objPool()->makeAndAdd(srcIdExpr, dstIdExpr, rankExpr); + edgeKeyRef->setType(typeExpr); + + edgeKeyRefs_.emplace_back(edgeKeyRef); return Status::OK(); } Status UpdateEdgeValidator::toPlan() { - auto *outNode = UpdateEdge::make(qctx_, - nullptr, + auto *dedup = Dedup::make(qctx_, nullptr); + dedup->setInputVar(edgeKeyVar_); + + auto *ureNode = UpdateEdge::make(qctx_, + dedup, spaceId_, - name_, - srcId_, - dstId_, + std::move(name_), + edgeKeyRefs_.front(), edgeType_, - rank_, insertable_, updatedProps_, - {}, + returnProps_, condition_, - {}); - auto *inNode = UpdateEdge::make(qctx_, - outNode, - spaceId_, - std::move(name_), - std::move(dstId_), - std::move(srcId_), - -edgeType_, - rank_, - insertable_, - std::move(updatedProps_), - std::move(returnProps_), - std::move(condition_), - std::move(yieldColNames_)); - root_ = inNode; - tail_ = outNode; + yieldColNames_); + root_ = ureNode; + tail_ = dedup; + return Status::OK(); +} + +Status UpdateEdgeValidator::checkInput() { + CHECK(!edgeKeyRefs_.empty()); + auto &edgeKeyRef = *edgeKeyRefs_.begin(); + NG_LOG_AND_RETURN_IF_ERROR(deduceProps(edgeKeyRef->srcid(), exprProps_)); + NG_LOG_AND_RETURN_IF_ERROR(deduceProps(edgeKeyRef->dstid(), exprProps_)); + NG_LOG_AND_RETURN_IF_ERROR(deduceProps(edgeKeyRef->rank(), exprProps_)); + + if (!exprProps_.srcTagProps().empty() || !exprProps_.dstTagProps().empty() || + !exprProps_.edgeProps().empty()) { + return Status::SyntaxError("Only support input and variable."); + } + + if (!exprProps_.inputProps().empty() && !exprProps_.varProps().empty()) { + return Status::SemanticError("Not support both input and variable."); + } + + if (!exprProps_.varProps().empty() && exprProps_.varProps().size() > 1) { + return Status::SemanticError("Only one variable allowed to use."); + } + + auto status = deduceExprType(edgeKeyRef->srcid()); + NG_RETURN_IF_ERROR(status); + + status = deduceExprType(edgeKeyRef->dstid()); + NG_RETURN_IF_ERROR(status); + + status = deduceExprType(edgeKeyRef->rank()); + NG_RETURN_IF_ERROR(status); + + if (edgeKeyRef->srcid()->kind() == Expression::Kind::kVarProperty) { + edgeKeyVar_ = static_cast(edgeKeyRef->srcid())->sym(); + } else if (edgeKeyRef->srcid()->kind() == Expression::Kind::kInputProperty) { + edgeKeyVar_ = inputVarName_; + } return Status::OK(); } diff --git a/src/graph/validator/MutateValidator.h b/src/graph/validator/MutateValidator.h index 5bcd6912567..f851b6d46f0 100644 --- a/src/graph/validator/MutateValidator.h +++ b/src/graph/validator/MutateValidator.h @@ -178,10 +178,14 @@ class UpdateVertexValidator final : public UpdateValidator { private: Status validateImpl() override; + std::string buildVIds(); + Status toPlan() override; private: Value vId_; + std::vector vertices_; + Expression* vidRef_{nullptr}; TagID tagId_{-1}; }; @@ -195,12 +199,21 @@ class UpdateEdgeValidator final : public UpdateValidator { Status toPlan() override; + Status checkInput(); + + Status processEdgeKeys(Expression* src_id, Expression* dst_id, const EdgeRanking& rank); + + Status buildEdgeKeyRef(); + private: - Value srcId_; - Value dstId_; - EdgeRanking rank_{0}; EdgeType edgeType_{-1}; + + std::vector edgeIds_; + std::vector edgeKeyRefs_; + std::string edgeKeyVar_; + ExpressionProps exprProps_; }; + } // namespace graph } // namespace nebula #endif // GRAPH_VALIDATOR_MUTATEVALIDATOR_H_ diff --git a/src/graph/validator/test/MutateValidatorTest.cpp b/src/graph/validator/test/MutateValidatorTest.cpp index a90290bc4cc..4a9d5334c25 100644 --- a/src/graph/validator/test/MutateValidatorTest.cpp +++ b/src/graph/validator/test/MutateValidatorTest.cpp @@ -186,7 +186,7 @@ TEST_F(MutateValidatorTest, UpdateVertexTest) { "SET person.age = $^.person.age + 1 " "WHEN $^.person.age == 18 " "YIELD $^.person.name AS name, $^.person.age AS age"; - ASSERT_TRUE(checkResult(cmd, {PK::kUpdateVertex, PK::kStart})); + ASSERT_TRUE(checkResult(cmd, {PK::kUpdateVertex, PK::kDedup, PK::kStart})); } // 2.0 syntax succeed { @@ -195,7 +195,7 @@ TEST_F(MutateValidatorTest, UpdateVertexTest) { "SET age = age + 1 " "WHEN age == 18 " "YIELD name AS name, age AS age"; - ASSERT_TRUE(checkResult(cmd, {PK::kUpdateVertex, PK::kStart})); + ASSERT_TRUE(checkResult(cmd, {PK::kUpdateVertex, PK::kDedup, PK::kStart})); } } @@ -221,7 +221,7 @@ TEST_F(MutateValidatorTest, UpdateEdgeTest) { "SET end = like.end + 1 " "WHEN like.start >= 2010 " "YIELD like.start AS start, like.end AS end"; - ASSERT_TRUE(checkResult(cmd, {PK::kUpdateEdge, PK::kUpdateEdge, PK::kStart})); + ASSERT_TRUE(checkResult(cmd, {PK::kUpdateEdge, PK::kDedup, PK::kStart})); } // 2.0 syntax succeed { @@ -230,7 +230,7 @@ TEST_F(MutateValidatorTest, UpdateEdgeTest) { "SET end = end + 1 " "WHEN start >= 2010 " "YIELD start AS start, end AS end"; - ASSERT_TRUE(checkResult(cmd, {PK::kUpdateEdge, PK::kUpdateEdge, PK::kStart})); + ASSERT_TRUE(checkResult(cmd, {PK::kUpdateEdge, PK::kDedup, PK::kStart})); } } } // namespace graph diff --git a/src/parser/EdgeKey.h b/src/parser/EdgeKey.h index f5859eea855..f5c026e5c72 100644 --- a/src/parser/EdgeKey.h +++ b/src/parser/EdgeKey.h @@ -13,6 +13,7 @@ #include #include +#include "common/datatypes/Value.h" #include "common/thrift/ThriftTypes.h" namespace nebula { @@ -47,6 +48,36 @@ class EdgeKey final { EdgeRanking rank_; }; +class EdgeId final { + public: + EdgeId(Value srcid, Value dstid, EdgeRanking rank) { + srcid_ = std::move(srcid); + dstid_ = std::move(dstid); + rank_ = rank; + } + + const Value &srcid() const { + return srcid_; + } + + const Value &dstid() const { + return dstid_; + } + + EdgeRanking rank() const { + return rank_; + } + + std::string toString() const { + return srcid_.toString() + "->" + dstid_.toString() + "@" + std::to_string(rank_); + } + + private: + Value srcid_; + Value dstid_; + EdgeRanking rank_; +}; + class EdgeKeys final { public: EdgeKeys() = default; diff --git a/src/parser/MutateSentences.cpp b/src/parser/MutateSentences.cpp index 09d4596b339..2b2bf237740 100644 --- a/src/parser/MutateSentences.cpp +++ b/src/parser/MutateSentences.cpp @@ -204,7 +204,11 @@ std::string UpdateVertexSentence::toString() const { if (name_ != nullptr) { buf += "ON " + *name_ + " "; } - buf += vid_->toString(); + if (vid_ != nullptr) { + buf += vid_->toString(); + } else { + buf += vertices_->toString(); + } buf += " SET "; buf += updateList_->toString(); if (whenClause_ != nullptr) { @@ -228,10 +232,16 @@ std::string UpdateEdgeSentence::toString() const { buf += "UPDATE "; } buf += "EDGE "; - buf += srcId_->toString(); - buf += "->"; - buf += dstId_->toString(); - buf += "@" + std::to_string(rank_); + if (srcId_ != nullptr) { + buf += srcId_->toString(); + buf += "->"; + buf += dstId_->toString(); + buf += "@" + std::to_string(rank_); + } else if (isRef()) { + buf += edgeKeyRef_->toString(); + } else { + buf += edgeKeys_->toString(); + } buf += " OF " + *name_; buf += " SET "; buf += updateList_->toString(); diff --git a/src/parser/MutateSentences.h b/src/parser/MutateSentences.h index 42347cb0b78..bbfbc1767c1 100644 --- a/src/parser/MutateSentences.h +++ b/src/parser/MutateSentences.h @@ -417,25 +417,37 @@ class UpdateBaseSentence : public Sentence { class UpdateVertexSentence final : public UpdateBaseSentence { public: + // compatible with 1.0 UpdateVertexSentence(Expression *vid, - std::string *tagName, UpdateList *updateList, WhenClause *whenClause, YieldClause *yieldClause, bool isInsertable = false) - : UpdateBaseSentence(updateList, whenClause, yieldClause, tagName, isInsertable) { + : UpdateBaseSentence(updateList, whenClause, yieldClause, nullptr, isInsertable) { kind_ = Kind::kUpdateVertex; vid_ = vid; } - UpdateVertexSentence(Expression *vid, + UpdateVertexSentence(VertexIDList *vidList, + std::string *tagName, UpdateList *updateList, WhenClause *whenClause, YieldClause *yieldClause, bool isInsertable = false) - : UpdateBaseSentence(updateList, whenClause, yieldClause, nullptr, isInsertable) { + : UpdateBaseSentence(updateList, whenClause, yieldClause, tagName, isInsertable), + vertices_(new VerticesClause(vidList)) { + kind_ = Kind::kUpdateVertex; + } + + UpdateVertexSentence(Expression *vid_ref, + std::string *tagName, + UpdateList *updateList, + WhenClause *whenClause, + YieldClause *yieldClause, + bool isInsertable = false) + : UpdateBaseSentence(updateList, whenClause, yieldClause, tagName, isInsertable), + vertices_(new VerticesClause(vid_ref)) { kind_ = Kind::kUpdateVertex; - vid_ = vid; } ~UpdateVertexSentence() {} @@ -448,6 +460,10 @@ class UpdateVertexSentence final : public UpdateBaseSentence { return vid_; } + const VerticesClause *vertices() const { + return vertices_.get(); + } + const UpdateList *updateList() const { return updateList_.get(); } @@ -464,6 +480,7 @@ class UpdateVertexSentence final : public UpdateBaseSentence { private: Expression *vid_{nullptr}; + std::unique_ptr vertices_; }; class UpdateEdgeSentence final : public UpdateBaseSentence { @@ -483,6 +500,36 @@ class UpdateEdgeSentence final : public UpdateBaseSentence { rank_ = rank; } + UpdateEdgeSentence(EdgeKeys *edge_keys, + std::string *edgeName, + UpdateList *updateList, + WhenClause *whenClause, + YieldClause *yieldClause, + bool isInsertable = false) + : UpdateBaseSentence(updateList, whenClause, yieldClause, edgeName, isInsertable) { + edgeKeys_.reset(edge_keys); + kind_ = Kind::kUpdateEdge; + } + + UpdateEdgeSentence(EdgeKeyRef *ref, + std::string *edgeName, + UpdateList *updateList, + WhenClause *whenClause, + YieldClause *yieldClause, + bool isInsertable = false) + : UpdateBaseSentence(updateList, whenClause, yieldClause, edgeName, isInsertable) { + edgeKeyRef_.reset(ref); + kind_ = Kind::kUpdateEdge; + } + + EdgeKeys *getEdgeKeys() const { + return edgeKeys_.get(); + } + + EdgeKeyRef *edgeKeyRef() const { + return edgeKeyRef_.get(); + } + Expression *getSrcId() const { return srcId_; } @@ -495,12 +542,19 @@ class UpdateEdgeSentence final : public UpdateBaseSentence { return rank_; } + bool isRef() const { + return edgeKeyRef_ != nullptr; + } + std::string toString() const override; private: Expression *srcId_{nullptr}; Expression *dstId_{nullptr}; int64_t rank_{0L}; + + std::unique_ptr edgeKeys_; + std::unique_ptr edgeKeyRef_; }; class DeleteVerticesSentence final : public Sentence { diff --git a/src/parser/parser.yy b/src/parser/parser.yy index c54370886a3..e038b0c23af 100644 --- a/src/parser/parser.yy +++ b/src/parser/parser.yy @@ -393,7 +393,8 @@ using namespace nebula; %type mutate_sentence %type insert_vertex_sentence insert_edge_sentence %type delete_vertex_sentence delete_edge_sentence delete_tag_sentence delete_vertex_with_edge_sentence -%type update_vertex_sentence update_edge_sentence +%type update_vertex_sentence +%type update_edge_sentence %type download_sentence ingest_sentence %type traverse_sentence unwind_sentence @@ -2203,6 +2204,7 @@ lookup_sentence } ; + order_factor : expression { $$ = new OrderFactor($1, OrderFactor::ASCEND); @@ -2982,6 +2984,8 @@ traverse_sentence | unwind_sentence { $$ = $1; } | show_sentence { $$ = $1; } | kill_session_sentence { $$ = $1; } + | update_vertex_sentence { $$ = $1; } + | update_edge_sentence { $$ = $1; } ; piped_sentence @@ -3209,6 +3213,7 @@ update_item update_vertex_sentence // ======== Begin: Compatible with 1.0 ========= + // keep the syntex with 1.0 : KW_UPDATE KW_VERTEX vid KW_SET update_list when_clause yield_clause { auto sentence = new UpdateVertexSentence($3, $5, $6, $7); $$ = sentence; @@ -3218,11 +3223,26 @@ update_vertex_sentence $$ = sentence; } // ======== End: Compatible with 1.0 ========= - | KW_UPDATE KW_VERTEX KW_ON name_label vid KW_SET update_list when_clause yield_clause { + + | KW_UPDATE KW_VERTEX KW_ON name_label vid_list KW_SET update_list when_clause yield_clause { + auto sentence = new UpdateVertexSentence($5, $4, $7, $8, $9); + $$ = sentence; + } + | KW_UPSERT KW_VERTEX KW_ON name_label vid_list KW_SET update_list when_clause yield_clause { + auto sentence = new UpdateVertexSentence($5, $4, $7, $8, $9, true); + $$ = sentence; + } + | KW_UPDATE KW_VERTEX KW_ON name_label vid_ref_expression KW_SET update_list when_clause yield_clause { + if(graph::ExpressionUtils::findAny($5,{Expression::Kind::kVar})) { + throw nebula::GraphParser::syntax_error(@5, "Parameter is not supported in update clause"); + } auto sentence = new UpdateVertexSentence($5, $4, $7, $8, $9); $$ = sentence; } - | KW_UPSERT KW_VERTEX KW_ON name_label vid KW_SET update_list when_clause yield_clause { + | KW_UPSERT KW_VERTEX KW_ON name_label vid_ref_expression KW_SET update_list when_clause yield_clause { + if(graph::ExpressionUtils::findAny($5,{Expression::Kind::kVar})) { + throw nebula::GraphParser::syntax_error(@5, "Parameter is not supported in update clause"); + } auto sentence = new UpdateVertexSentence($5, $4, $7, $8, $9, true); $$ = sentence; } @@ -3251,28 +3271,29 @@ update_edge_sentence $$ = sentence; } // ======== End: Compatible with 1.0 ========= - | KW_UPDATE KW_EDGE KW_ON name_label vid R_ARROW vid + | KW_UPDATE KW_EDGE KW_ON name_label edge_keys KW_SET update_list when_clause yield_clause { - auto sentence = new UpdateEdgeSentence($5, $7, 0, $4, $9, $10, $11); + auto sentence = new UpdateEdgeSentence($5, $4, $7, $8, $9); $$ = sentence; } - | KW_UPSERT KW_EDGE KW_ON name_label vid R_ARROW vid + | KW_UPDATE KW_EDGE KW_ON name_label edge_key_ref KW_SET update_list when_clause yield_clause { - auto sentence = new UpdateEdgeSentence($5, $7, 0, $4, $9, $10, $11, true); + auto sentence = new UpdateEdgeSentence($5, $4, $7, $8, $9); $$ = sentence; } - | KW_UPDATE KW_EDGE KW_ON name_label vid R_ARROW vid AT rank + | KW_UPSERT KW_EDGE KW_ON name_label edge_keys KW_SET update_list when_clause yield_clause { - auto sentence = new UpdateEdgeSentence($5, $7, $9, $4, $11, $12, $13); + auto sentence = new UpdateEdgeSentence($5, $4, $7, $8, $9, true); $$ = sentence; } - | KW_UPSERT KW_EDGE KW_ON name_label vid R_ARROW vid AT rank + | KW_UPSERT KW_EDGE KW_ON name_label edge_key_ref KW_SET update_list when_clause yield_clause { - auto sentence = new UpdateEdgeSentence($5, $7, $9, $4, $11, $12, $13, true); + auto sentence = new UpdateEdgeSentence($5, $4, $7, $8, $9, true); $$ = sentence; } ; + delete_vertex_sentence : KW_DELETE KW_VERTEX vid_list { auto sentence = new DeleteVerticesSentence($3, false); @@ -3945,8 +3966,6 @@ query_unique_identifier mutate_sentence : insert_vertex_sentence { $$ = $1; } | insert_edge_sentence { $$ = $1; } - | update_vertex_sentence { $$ = $1; } - | update_edge_sentence { $$ = $1; } | download_sentence { $$ = $1; } | ingest_sentence { $$ = $1; } | admin_job_sentence { $$ = $1; } diff --git a/tests/tck/features/delete/DeleteVertex.IntVid.feature b/tests/tck/features/delete/DeleteVertex.IntVid.feature index 19cec5b2d80..5fa06cfb051 100644 --- a/tests/tck/features/delete/DeleteVertex.IntVid.feature +++ b/tests/tck/features/delete/DeleteVertex.IntVid.feature @@ -260,11 +260,12 @@ Feature: Delete int vid of vertex | like._dst | Scenario: delete with pipe failed, because of the wrong vid type + Given load "nba_int_vid" csv data to a new space When executing query: """ - USE nba_int_vid;YIELD "Tom" as id | DELETE VERTEX $-.id WITH EDGE; + YIELD "Tom" as id | DELETE VERTEX $-.id WITH EDGE; """ - Then a SemanticError should be raised at runtime: The vid `$-.id' should be type of `INT', but was`STRING' + Then a SemanticError should be raised at runtime: The vid `$-.id' should be type of `INT', but was `STRING' Then drop the used space Scenario: delete with var, get result by go diff --git a/tests/tck/features/lookup/LookUpUpdate.feature b/tests/tck/features/lookup/LookUpUpdate.feature new file mode 100644 index 00000000000..c7ff7bb57cb --- /dev/null +++ b/tests/tck/features/lookup/LookUpUpdate.feature @@ -0,0 +1,132 @@ +# Copyright (c) 2024 vesoft inc. All rights reserved. +# +# This source code is licensed under Apache 2.0 License. +Feature: lookup and update + + Background: + Given an empty graph + And create a space with following options: + | partition_num | 1 | + | replica_factor | 1 | + | vid_type | FIXED_STRING(32) | + + Scenario: LookupTest lookup and update vertex + Given having executed: + """ + CREATE TAG lookup_tag_1(col1 int, col2 int, col3 int); + CREATE TAG lookup_tag_2(col1 bool, col2 int, col3 double, col4 bool); + CREATE TAG INDEX t_index_1 ON lookup_tag_1(col1, col2, col3); + CREATE TAG INDEX t_index_2 ON lookup_tag_2(col2, col3, col4); + CREATE TAG INDEX t_index_3 ON lookup_tag_1(col2, col3); + CREATE TAG INDEX t_index_4 ON lookup_tag_2(col3, col4); + CREATE TAG INDEX t_index_5 ON lookup_tag_2(col4); + """ + And wait 6 seconds + When executing query and retrying it on failure every 6 seconds for 3 times: + """ + INSERT VERTEX + lookup_tag_1(col1, col2, col3) + VALUES + "200":(200, 200, 200), + "201":(201, 201, 201), + "202":(202, 202, 202) + """ + Then the execution should be successful + When executing query: + """ + LOOKUP ON lookup_tag_1 WHERE lookup_tag_1.col2 == 200 YIELD id(vertex) as id + """ + Then the result should be, in any order: + | id | + | "200" | + When executing query: + """ + LOOKUP ON lookup_tag_1 WHERE lookup_tag_1.col2 == 200 YIELD id(vertex) as id | UPDATE VERTEX ON lookup_tag_1 $-.id SET col2 = 201 + """ + Then the execution should be successful + When executing query: + """ + LOOKUP ON lookup_tag_1 WHERE lookup_tag_1.col2 == 201 YIELD id(vertex) as id + """ + Then the result should be, in any order: + | id | + | "200" | + | "201" | + When executing query: + """ + LOOKUP ON lookup_tag_1 WHERE lookup_tag_1.col2 == 201 YIELD id(vertex) as id | UPDATE VERTEX ON lookup_tag_1 $-.id SET col2 = col2 - 1 + """ + Then the execution should be successful + When executing query: + """ + LOOKUP ON lookup_tag_1 WHERE lookup_tag_1.col2 == 200 YIELD id(vertex) as id + """ + Then the result should be, in any order: + | id | + | "200" | + | "201" | + When executing query: + """ + LOOKUP ON lookup_tag_1 WHERE lookup_tag_1.col2 == 202 YIELD id(vertex) as id | UPSERT VERTEX ON lookup_tag_1 $-.id SET col2 = col2 + 1 + """ + Then the execution should be successful + When executing query: + """ + LOOKUP ON lookup_tag_1 WHERE lookup_tag_1.col2 == 203 YIELD id(vertex) as id + """ + Then the result should be, in any order: + | id | + | "202" | + + Scenario: LookupTest lookup and update edge + Given having executed: + """ + CREATE EDGE lookup_edge_1(col1 int, col2 int, col3 int); + CREATE EDGE INDEX e_index_1 ON lookup_edge_1(col1, col2, col3); + CREATE EDGE INDEX e_index_3 ON lookup_edge_1(col2, col3); + """ + And wait 6 seconds + When executing query and retrying it on failure every 6 seconds for 3 times: + """ + INSERT EDGE + lookup_edge_1(col1, col2, col3) + VALUES + '200' -> '201'@0:(201, 201, 201), + '200' -> '202'@0:(202, 202, 202) + """ + Then the execution should be successful + When executing query: + """ + LOOKUP ON lookup_edge_1 WHERE lookup_edge_1.col2 > 200 YIELD src(edge) as src, dst(edge) as dst, rank(edge) as rank | + UPDATE EDGE ON lookup_edge_1 $-.src ->$-.dst@$-.rank SET col3 = 203 + """ + Then the execution should be successful + When executing query: + """ + LOOKUP ON lookup_edge_1 YIELD + lookup_edge_1.col1 AS col1, + lookup_edge_1.col2 AS col2, + lookup_edge_1.col3 + """ + Then the result should be, in any order: + | col1 | col2 | lookup_edge_1.col3 | + | 201 | 201 | 203 | + | 202 | 202 | 203 | + When executing query: + """ + LOOKUP ON lookup_edge_1 WHERE lookup_edge_1.col2 > 200 YIELD src(edge) as src, dst(edge) as dst, rank(edge) as rank | + UPSERT EDGE ON lookup_edge_1 $-.src ->$-.dst@$-.rank SET col3 = 204 + """ + Then the execution should be successful + When executing query: + """ + LOOKUP ON lookup_edge_1 YIELD + lookup_edge_1.col1 AS col1, + lookup_edge_1.col2 AS col2, + lookup_edge_1.col3 + """ + Then the result should be, in any order: + | col1 | col2 | lookup_edge_1.col3 | + | 201 | 201 | 204 | + | 202 | 202 | 204 | + Then drop the used space diff --git a/tests/tck/features/update/MultiUpdate.feature b/tests/tck/features/update/MultiUpdate.feature new file mode 100644 index 00000000000..8eecc7173dc --- /dev/null +++ b/tests/tck/features/update/MultiUpdate.feature @@ -0,0 +1,282 @@ +# Copyright (c) 2024 vesoft inc. All rights reserved. +# +# This source code is licensed under Apache 2.0 License. +Feature: Multi Update string vid of vertex and edge + + Background: Prepare space + Given an empty graph + And create a space with following options: + | partition_num | 1 | + | replica_factor | 1 | + | vid_type | FIXED_STRING(200) | + And having executed: + """ + CREATE TAG IF NOT EXISTS course(name string, credits int); + CREATE TAG IF NOT EXISTS building(name string); + CREATE TAG IF NOT EXISTS student(name string, age int, gender string); + CREATE TAG IF NOT EXISTS student_default( + name string NOT NULL, + age int NOT NULL, + gender string DEFAULT "one", + birthday int DEFAULT 2010 + ); + CREATE EDGE IF NOT EXISTS like(likeness double); + CREATE EDGE IF NOT EXISTS select(grade int, year int); + CREATE EDGE IF NOT EXISTS select_default(grade int NOT NULL,year TIMESTAMP DEFAULT 1546308000); + """ + And having executed: + """ + INSERT VERTEX + student(name, age, gender) + VALUES + "200":("Monica", 16, "female"), + "201":("Mike", 18, "male"), + "202":("Jane", 17, "female"); + INSERT VERTEX + course(name, credits), + building(name) + VALUES + "101":("Math", 3, "No5"), + "102":("English", 6, "No11"); + INSERT VERTEX course(name, credits) VALUES "103":("CS", 5); + INSERT EDGE + select(grade, year) + VALUES + "200"->"101"@0:(5, 2018), + "200"->"102"@0:(3, 2018), + "201"->"102"@0:(3, 2019), + "202"->"102"@0:(3, 2019); + INSERT EDGE + like(likeness) + VALUES + "200"->"201"@0:(92.5), + "201"->"200"@0:(85.6), + "201"->"202"@0:(93.2); + """ + + Scenario: multi update test + When executing query: + """ + UPDATE VERTEX ON course "101", "102" + SET credits = credits + 1; + """ + Then the execution should be successful + When executing query: + """ + UPDATE VERTEX ON course "101", "102" + SET credits = credits + 1 + WHEN name == "Math" AND credits > 2 + """ + Then the execution should be successful + When executing query: + """ + UPDATE VERTEX ON course "101", "102" + SET credits = credits + 1 + YIELD name AS Name, credits AS Credits + """ + Then the result should be, in any order: + | Name | Credits | + | 'Math' | 6 | + | 'English' | 8 | + When executing query: + """ + UPDATE VERTEX ON course "101", "102" + SET credits = credits + 1 + WHEN name == "Math" AND credits > 2 + YIELD name AS Name, credits AS Credits + """ + Then the result should be, in any order: + | Name | Credits | + | 'Math' | 7 | + | 'English' | 8 | + When executing query: + """ + UPDATE VERTEX ON course "101", "102" + SET credits = credits + 1 + WHEN name == "nonexistent" AND credits > 2 + YIELD name AS Name, credits AS Credits + """ + Then the result should be, in any order: + | Name | Credits | + | 'Math' | 7 | + | 'English' | 8 | + When executing query: + """ + FETCH PROP ON select "200"->"101"@0 YIELD select.grade, select.year + """ + Then the result should be, in any order: + | select.grade | select.year | + | 5 | 2018 | + When executing query: + """ + FETCH PROP ON select "200" -> "102"@0 YIELD select.grade, select.year + """ + Then the result should be, in any order: + | select.grade | select.year | + | 3 | 2018 | + # update edge succeeded + When executing query: + """ + UPDATE EDGE ON select "200"->"101"@0, "200"->"102"@0 + SET grade = grade + 1, year = 2000; + """ + Then the execution should be successful + When executing query: + """ + FETCH PROP ON select "200"->"101"@0 YIELD select.grade, select.year + """ + Then the result should be, in any order: + | select.grade | select.year | + | 6 | 2000 | + When executing query: + """ + FETCH PROP ON select "200"->"102"@0 YIELD select.grade, select.year + """ + Then the result should be, in any order: + | select.grade | select.year | + | 4 | 2000 | + When executing query: + """ + GO FROM "101" OVER select REVERSELY YIELD select.grade, select.year + """ + Then the result should be, in any order: + | select.grade | select.year | + | 6 | 2000 | + # 2.0 test, filter out + When executing query: + """ + UPDATE EDGE ON select "200"->"101"@0, "200"->"102"@0 + SET grade = grade + 1, year = 2000 + WHEN grade > 1024 + """ + Then the execution should be successful + # set filter + When executing query: + """ + UPDATE EDGE ON select "200"->"101"@0, "200"->"102"@0 + SET grade = grade + 1, year = 2000 WHEN grade > 4 + """ + Then the execution should be successful + # set yield + When executing query: + """ + UPDATE EDGE ON select "200"->"101"@0, "200"->"102"@0 + SET grade = grade + 1, year = 2018 + YIELD grade AS Grade, year AS Year + """ + Then the result should be, in any order: + | Grade | Year | + | 8 | 2018 | + | 5 | 2018 | + # filter and yield + When executing query: + """ + UPDATE EDGE ON select "200"->"101"@0, "200"->"102"@0 + SET grade = select.grade + 1, year = 2019 + WHEN select.grade > 6 + YIELD select.grade AS Grade, select.year AS Year + """ + Then the result should be, in any order: + | Grade | Year | + | 9 | 2019 | + | 5 | 2018 | + # set filter out and yield + When executing query: + """ + UPDATE EDGE ON select "200"->"101"@0, "200"->"102"@0 + SET grade = grade + 1, year = 2019 + WHEN grade > 233333333333 + YIELD grade AS Grade, year AS Year + """ + Then the result should be, in any order: + | Grade | Year | + | 9 | 2019 | + | 5 | 2018 | + # update vertex: the item is TagName.PropName = Expression in SET clause + When executing query: + """ + UPDATE VERTEX ON course "101", "102" + SET credits = credits + 1, name = "No9" + """ + Then the execution should be successful + # make sure TagName and PropertyName must exist in all clauses + When executing query: + """ + UPDATE VERTEX ON nonexistentTag "101" + SET credits = credits + 1 + """ + Then a SemanticError should be raised at runtime: No schema found for `nonexistentTag' + # make sure EdgeName and PropertyName must exist in all clauses + When executing query: + """ + UPDATE EDGE ON select "200"->"101"@0, "200"->"102"@0 + SET nonexistentProperty = grade + 1, year = 2019 + """ + Then a ExecutionError should be raised at runtime: Storage Error: Edge prop not found. + # make sure the edge_type must not exist + When executing query: + """ + UPDATE EDGE ON nonexistentEdgeTypeName "200"->"101"@0, "200"->"102"@0 + SET grade = grade + 1, year = 2019 + """ + Then a SemanticError should be raised at runtime: No schema found for `nonexistentEdgeTypeName' + When executing query: + """ + FETCH PROP ON course "103" YIELD course.name, course.credits + """ + Then the result should be, in any order: + | course.name | course.credits | + | "CS" | 5 | + When executing query: + """ + UPDATE VERTEX ON course "103" + SET credits = credits + 1 + WHEN name == "CS" AND credits > 2 + YIELD name AS Name, credits AS Credits + """ + Then the result should be, in any order: + | Name | Credits | + | 'CS' | 6 | + # when tag ON vertex not exists, update failed + When executing query: + """ + UPDATE VERTEX ON course "103", "104" SET credits = credits + 1 + WHEN name == "CS" AND credits > 2 + YIELD name AS Name, credits AS Credits + """ + Then a ExecutionError should be raised at runtime: Storage Error: Vertex or edge not found. + # Insertable failed, "111" is nonexistent, name and age without default value + When executing query: + """ + UPSERT VERTEX ON student_default "111", "112" + SET name = "Tom", age = age + 8 + YIELD name AS Name, age AS Age + """ + Then a ExecutionError should be raised at runtime: Storage Error: Invalid field value: may be the filed is not NULL or without default value or wrong schema. + # Insertable failed, "111" is nonexistent, name without default value + When executing query: + """ + UPSERT VERTEX ON student_default "111", "112" + SET gender = "two", age = 10 + YIELD name AS Name, gender AS Gender + """ + Then a ExecutionError should be raised at runtime: Storage Error: Invalid field value: may be the filed is not NULL or without default value or wrong schema. + # update student_default.age with string value + When executing query: + """ + UPSERT VERTEX ON student_default "113", "114" + SET name = "Ann", age = "10" + YIELD name AS Name, gender AS Gender + """ + Then a ExecutionError should be raised at runtime: Storage Error: Invalid data, may be wrong value type. + When executing query: + """ + UPSERT VERTEX ON student_default "115", "116" + SET name = "Kate", age = 12 + WHEN gender == "two" + YIELD name AS Name, age AS Age, gender AS gender + """ + Then the result should be, in any order: + | Name | Age | gender | + | 'Kate' | 12 | 'one' | + | 'Kate' | 12 | 'one' |