diff --git a/src/exec/CMakeLists.txt b/src/exec/CMakeLists.txt index e6c409f642dd330a9408b17968ecaefb2dbc9907..ab8ca5069f3c4a6c63ebf7f8fb63669fd1437d69 100644 --- a/src/exec/CMakeLists.txt +++ b/src/exec/CMakeLists.txt @@ -33,6 +33,7 @@ nebula_add_library( maintain/EdgeExecutor.cpp mutate/InsertExecutor.cpp mutate/DeleteExecutor.cpp + mutate/UpdateExecutor.cpp ) nebula_add_subdirectory(query/test) diff --git a/src/exec/Executor.cpp b/src/exec/Executor.cpp index 66ffcbc8138a5d09efa75aba3ec7fbc0cb58b391..d7c4cc120568da08e18895761e3e3e820002fb93 100644 --- a/src/exec/Executor.cpp +++ b/src/exec/Executor.cpp @@ -24,6 +24,7 @@ #include "exec/maintain/TagExecutor.h" #include "exec/mutate/InsertExecutor.h" #include "exec/mutate/DeleteExecutor.h" +#include "exec/mutate/UpdateExecutor.h" #include "exec/query/AggregateExecutor.h" #include "exec/query/DataCollectExecutor.h" #include "exec/query/DataJoinExecutor.h" @@ -384,6 +385,20 @@ Executor *Executor::makeExecutor(const PlanNode *node, exec->dependsOn(input); break; } + case PlanNode::Kind::kUpdateVertex: { + auto updateV = asNode<UpdateVertex>(node); + auto input = makeExecutor(updateV->dep(), qctx, visited); + exec = new UpdateVertexExecutor(updateV, qctx); + exec->dependsOn(input); + break; + } + case PlanNode::Kind::kUpdateEdge: { + auto updateE = asNode<UpdateEdge>(node); + auto input = makeExecutor(updateE->dep(), qctx, visited); + exec = new UpdateEdgeExecutor(updateE, qctx); + exec->dependsOn(input); + break; + } case PlanNode::Kind::kUnknown: default: LOG(FATAL) << "Unknown plan node kind " << static_cast<int32_t>(node->kind()); diff --git a/src/exec/mutate/UpdateExecutor.cpp b/src/exec/mutate/UpdateExecutor.cpp new file mode 100644 index 0000000000000000000000000000000000000000..d00a25862df50fac1291c5921c81af89567af7ab --- /dev/null +++ b/src/exec/mutate/UpdateExecutor.cpp @@ -0,0 +1,169 @@ +/* Copyright (c) 2020 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 "UpdateExecutor.h" +#include "planner/Mutate.h" +#include "util/SchemaUtil.h" +#include "context/QueryContext.h" +#include "util/ScopedTimer.h" + + +namespace nebula { +namespace graph { + +StatusOr<DataSet> UpdateBaseExecutor::handleResult(DataSet &&data) { + if (data.colNames.size() <= 1) { + if (yieldNames_.empty()) { + return Status::OK(); + } + LOG(ERROR) << "Empty return props"; + return Status::Error("Empty return props"); + } + + if (yieldNames_.size() != data.colNames.size() - 1) { + LOG(ERROR) << "Expect colName size is " << yieldNames_.size() + << ", return colName size is " << data.colNames.size() - 1; + return Status::Error("Wrong return prop size"); + } + DataSet result; + result.colNames = std::move(yieldNames_); + for (auto &row : data.rows) { + std::vector<Value> columns; + for (auto i = 1u; i < row.values.size(); i++) { + columns.emplace_back(std::move(row.values[i])); + } + result.rows.emplace_back(std::move(columns)); + } + return result; +} + +Status UpdateBaseExecutor::handleErrorCode(nebula::storage::cpp2::ErrorCode code, + PartitionID partId) { + switch (code) { + case storage::cpp2::ErrorCode::E_INVALID_FIELD_VALUE: + return Status::Error( + "Invalid field value: may be the filed without default value or wrong schema"); + case storage::cpp2::ErrorCode::E_INVALID_FILTER: + return Status::Error("Invalid filter."); + case storage::cpp2::ErrorCode::E_INVALID_UPDATER: + return Status::Error("Invalid Update col or yield col."); + case storage::cpp2::ErrorCode::E_TAG_NOT_FOUND: + return Status::Error("Tag `%s' not found.", schemaName_.c_str()); + case storage::cpp2::ErrorCode::E_TAG_PROP_NOT_FOUND: + return Status::Error("Tag prop not found."); + case storage::cpp2::ErrorCode::E_EDGE_NOT_FOUND: + return Status::Error("Edge `%s' not found.", schemaName_.c_str()); + case storage::cpp2::ErrorCode::E_EDGE_PROP_NOT_FOUND: + return Status::Error("Edge prop not found."); + case storage::cpp2::ErrorCode::E_INVALID_DATA: + return Status::Error("Invalid data, may be wrong value type."); + case storage::cpp2::ErrorCode::E_NOT_NULLABLE: + return Status::Error("The not null field cannot be null."); + case storage::cpp2::ErrorCode::E_FIELD_UNSET: + return Status::Error("The not null field doesn't have a default value."); + case storage::cpp2::ErrorCode::E_OUT_OF_RANGE: + return Status::Error("Out of range value."); + case storage::cpp2::ErrorCode::E_ATOMIC_OP_FAILED: + return Status::Error("Atomic operation failed."); + case storage::cpp2::ErrorCode::E_FILTER_OUT: + return Status::OK(); + default: + auto status = Status::Error("Unknown error, part: %d, error code: %d.", + partId, static_cast<int32_t>(code)); + LOG(ERROR) << status; + return status; + } + return Status::OK(); +} + +folly::Future<Status> UpdateVertexExecutor::execute() { + SCOPED_TIMER(&execTime_); + auto *uvNode = asNode<UpdateVertex>(node()); + yieldNames_ = uvNode->getYieldNames(); + schemaName_ = uvNode->getName(); + time::Duration updateVertTime; + return qctx()->getStorageClient()->updateVertex(uvNode->getSpaceId(), + uvNode->getVId(), + uvNode->getTagId(), + uvNode->getUpdatedProps(), + uvNode->getInsertable(), + uvNode->getReturnProps(), + uvNode->getCondition()) + .via(runner()) + .ensure([updateVertTime]() { + VLOG(1) << "Update vertice time: " << updateVertTime.elapsedInUSec() << "us"; + }) + .then([this](StatusOr<storage::cpp2::UpdateResponse> resp) { + SCOPED_TIMER(&execTime_); + if (!resp.ok()) { + LOG(ERROR) << 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.__isset.props) { + auto status = handleResult(std::move(*value.get_props())); + if (!status.ok()) { + return status.status(); + } + return finish(ResultBuilder() + .value(std::move(status).value()) + .iter(Iterator::Kind::kDefault) + .finish()); + } + return Status::OK(); + }); +} + +folly::Future<Status> UpdateEdgeExecutor::execute() { + SCOPED_TIMER(&execTime_); + auto *ueNode = asNode<UpdateEdge>(node()); + schemaName_ = ueNode->getName(); + storage::cpp2::EdgeKey edgeKey; + edgeKey.set_src(ueNode->getSrcId()); + edgeKey.set_ranking(ueNode->getRank()); + edgeKey.set_edge_type(ueNode->getEdgeType()); + edgeKey.set_dst(ueNode->getDstId()); + yieldNames_ = ueNode->getYieldNames(); + + time::Duration updateEdgeTime; + return qctx()->getStorageClient()->updateEdge(ueNode->getSpaceId(), + edgeKey, + ueNode->getUpdatedProps(), + ueNode->getInsertable(), + ueNode->getReturnProps(), + ueNode->getCondition()) + .via(runner()) + .ensure([updateEdgeTime]() { + VLOG(1) << "Update edge time: " << updateEdgeTime.elapsedInUSec() << "us"; + }) + .then([this](StatusOr<storage::cpp2::UpdateResponse> resp) { + SCOPED_TIMER(&execTime_); + if (!resp.ok()) { + LOG(ERROR) << "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.__isset.props) { + auto status = handleResult(std::move(*value.get_props())); + if (!status.ok()) { + return status.status(); + } + return finish(ResultBuilder() + .value(std::move(status).value()) + .iter(Iterator::Kind::kDefault) + .finish()); + } + return Status::OK(); + }); +} +} // namespace graph +} // namespace nebula diff --git a/src/exec/mutate/UpdateExecutor.h b/src/exec/mutate/UpdateExecutor.h new file mode 100644 index 0000000000000000000000000000000000000000..99bef8cccc9cf6c0ab548f6fb9585337ba398ec7 --- /dev/null +++ b/src/exec/mutate/UpdateExecutor.h @@ -0,0 +1,54 @@ +/* Copyright (c) 2020 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. + */ + +#ifndef EXEC_MUTATE_UPDATEEXECUTOR_H_ +#define EXEC_MUTATE_UPDATEEXECUTOR_H_ + +#include "common/base/StatusOr.h" +#include "exec/Executor.h" + +namespace nebula { +namespace graph { + +class UpdateBaseExecutor : public Executor { +public: + UpdateBaseExecutor(const std::string &execName, + const PlanNode *node, + QueryContext *ectx) + : Executor(execName, node, ectx) {} + + virtual ~UpdateBaseExecutor() {} + +protected: + StatusOr<DataSet> handleResult(DataSet &&data); + + Status handleErrorCode(nebula::storage::cpp2::ErrorCode code, PartitionID partId); + +protected: + std::vector<std::string> yieldNames_; + std::string schemaName_; +}; + +class UpdateVertexExecutor final : public UpdateBaseExecutor { +public: + UpdateVertexExecutor(const PlanNode *node, QueryContext *ectx) + : UpdateBaseExecutor("UpdateVertexExecutor", node, ectx) {} + + folly::Future<Status> execute() override; +}; + +class UpdateEdgeExecutor final : public UpdateBaseExecutor { +public: + UpdateEdgeExecutor(const PlanNode *node, QueryContext *ectx) + : UpdateBaseExecutor("UpdateEdgeExecutor", node, ectx) {} + + folly::Future<Status> execute() override; +}; + +} // namespace graph +} // namespace nebula + +#endif // EXEC_MUTATE_UPDATEEXECUTOR_H_ diff --git a/src/mock/CMakeLists.txt b/src/mock/CMakeLists.txt index 1ab91a0d8ed75c5451c7d484432fc4126862a92c..6fd56e8bd21ba315822c4830cb334c46b34e4e07 100644 --- a/src/mock/CMakeLists.txt +++ b/src/mock/CMakeLists.txt @@ -6,13 +6,13 @@ nebula_add_library( mock_obj OBJECT - MetaCache.cpp - StorageCache.cpp - MockMetaServiceHandler.cpp - MockStorageServiceHandler.cpp - test/TestMain.cpp - test/TestEnv.cpp - test/TestBase.cpp + MetaCache.cpp + StorageCache.cpp + MockMetaServiceHandler.cpp + MockStorageServiceHandler.cpp + test/TestMain.cpp + test/TestEnv.cpp + test/TestBase.cpp ) -nebula_add_subdirectory(test) \ No newline at end of file +nebula_add_subdirectory(test) diff --git a/src/parser/AdminSentences.h b/src/parser/AdminSentences.h index 1acfbe41b44cee3334da2f8bc2f7103e7291d539..40fc531de92a1c38d1dde3378973bf0d0a2de677 100644 --- a/src/parser/AdminSentences.h +++ b/src/parser/AdminSentences.h @@ -130,23 +130,23 @@ public: optValue_ = val; } - int64_t asInt() { + int64_t asInt() const { return boost::get<int64_t>(optValue_); } - const std::string& asString() { + const std::string& asString() const { return boost::get<std::string>(optValue_); } - bool isInt() { + bool isInt() const { return optValue_.which() == 0; } - bool isString() { + bool isString() const { return optValue_.which() == 1; } - int64_t getPartitionNum() { + int64_t getPartitionNum() const { if (isInt()) { return asInt(); } else { @@ -155,7 +155,7 @@ public: } } - int64_t getReplicaFactor() { + int64_t getReplicaFactor() const { if (isInt()) { return asInt(); } else { @@ -164,7 +164,7 @@ public: } } - int32_t getVidSize() { + int32_t getVidSize() const { if (isInt()) { return asInt(); } else { @@ -173,7 +173,7 @@ public: } } - std::string getCharset() { + std::string getCharset() const { if (isString()) { return asString(); } else { @@ -182,7 +182,7 @@ public: } } - std::string getCollate() { + std::string getCollate() const { if (isString()) { return asString(); } else { @@ -191,7 +191,7 @@ public: } } - OptionType getOptType() { + OptionType getOptType() const { return optType_; } diff --git a/src/parser/MutateSentences.cpp b/src/parser/MutateSentences.cpp index 40017d7a640c732c1f94b38cec70540519216c6c..3f5a1b3807121f9727ab01fb31c293a92eda96c0 100644 --- a/src/parser/MutateSentences.cpp +++ b/src/parser/MutateSentences.cpp @@ -156,7 +156,12 @@ std::string InsertEdgesSentence::toString() const { std::string UpdateItem::toString() const { std::string buf; buf.reserve(256); - buf += *field_; + if (fieldStr_ != nullptr) { + buf += *fieldStr_; + } else if (fieldExpr_ != nullptr) { + buf += fieldExpr_->toString(); + } + buf += "="; buf += value_->toString(); return buf; @@ -196,7 +201,6 @@ StatusOr<std::string> UpdateList::toEvaledString() const { return buf; } - std::string UpdateVertexSentence::toString() const { std::string buf; buf.reserve(256); @@ -206,6 +210,7 @@ std::string UpdateVertexSentence::toString() const { buf += "UPDATE "; } buf += "VERTEX "; + buf += "ON " + *name_ + " "; buf += vid_->toString(); buf += " SET "; buf += updateList_->toString(); @@ -231,13 +236,11 @@ std::string UpdateEdgeSentence::toString() const { buf += "UPDATE "; } buf += "EDGE "; - buf += srcid_->toString(); + buf += srcId_->toString(); buf += "->"; - buf += dstid_->toString(); - if (hasRank_) { - buf += " AT" + std::to_string(rank_); - } - buf += " OF " + *edgeType_; + buf += dstId_->toString(); + buf += " AT" + std::to_string(rank_); + buf += " OF " + *name_; buf += " SET "; buf += updateList_->toString(); if (whenClause_ != nullptr) { diff --git a/src/parser/MutateSentences.h b/src/parser/MutateSentences.h index c9eb9db56cb8ea85f8f3c0ddaf85f10e1d6d9aab..949412f12772224f564f27f79fbc353cd5bc2fe3 100644 --- a/src/parser/MutateSentences.h +++ b/src/parser/MutateSentences.h @@ -303,22 +303,24 @@ private: class UpdateItem final { public: UpdateItem(std::string *field, Expression *value) { - field_.reset(field); + fieldStr_.reset(field); value_.reset(value); } UpdateItem(Expression *field, Expression *value) { - // TODO - UNUSED(field); - field_ = std::make_unique<std::string>(""); + fieldExpr_.reset(field); value_.reset(value); } - std::string* field() const { - return field_.get(); + std::string* getFieldName() const { + return fieldStr_.get(); } - Expression* value() const { + const Expression* getFieldExpr() const { + return fieldExpr_.get(); + } + + const Expression* value() const { return value_.get(); } @@ -327,13 +329,17 @@ public: StatusOr<std::string> toEvaledString() const; private: - std::unique_ptr<std::string> field_; + std::unique_ptr<std::string> fieldStr_; + std::unique_ptr<Expression> fieldExpr_; std::unique_ptr<Expression> value_; }; class UpdateList final { public: + UpdateList() = default; + ~UpdateList() = default; + void addItem(UpdateItem *item) { items_.emplace_back(item); } @@ -355,147 +361,137 @@ private: std::vector<std::unique_ptr<UpdateItem>> items_; }; - -class UpdateVertexSentence final : public Sentence { +class UpdateBaseSentence : public Sentence { public: - UpdateVertexSentence() { - kind_ = Kind::kUpdateVertex; + UpdateBaseSentence(UpdateList *updateList, + WhenClause *whenClause, + YieldClause *yieldClause, + std::string* name, + bool isInsertable = false) { + updateList_.reset(updateList); + whenClause_.reset(whenClause); + yieldClause_.reset(yieldClause); + name_.reset(name); + insertable_ = isInsertable; } - void setInsertable(bool insertable) { - insertable_ = insertable; - } + virtual ~UpdateBaseSentence() = default; bool getInsertable() const { return insertable_; } - void setVid(Expression *vid) { - vid_.reset(vid); - } - - Expression* getVid() const { - return vid_.get(); - } - - void setUpdateList(UpdateList *updateList) { - updateList_.reset(updateList); - } - const UpdateList* updateList() const { return updateList_.get(); } - void setWhenClause(WhenClause *clause) { - whenClause_.reset(clause); - } - const WhenClause* whenClause() const { return whenClause_.get(); } - void setYieldClause(YieldClause *clause) { - yieldClause_.reset(clause); - } - const YieldClause* yieldClause() const { return yieldClause_.get(); } - std::string toString() const override; + const std::string* getName() const { + return name_.get(); + } -private: +protected: bool insertable_{false}; - std::unique_ptr<Expression> vid_; std::unique_ptr<UpdateList> updateList_; std::unique_ptr<WhenClause> whenClause_; std::unique_ptr<YieldClause> yieldClause_; + std::unique_ptr<std::string> name_; }; - -class UpdateEdgeSentence final : public Sentence { +class UpdateVertexSentence final : public UpdateBaseSentence { public: - UpdateEdgeSentence() { - kind_ = Kind::kUpdateEdge; + UpdateVertexSentence(Expression *vid, + std::string *tagName, + UpdateList *updateList, + WhenClause *whenClause, + YieldClause *yieldClause, + bool isInsertable = false) + : UpdateBaseSentence(updateList, whenClause, yieldClause, tagName, isInsertable) { + kind_ = Kind::kUpdateVertex; + vid_.reset(vid); } - void setInsertable(bool insertable) { - insertable_ = insertable; + UpdateVertexSentence(Expression *vid, + UpdateList *updateList, + WhenClause *whenClause, + YieldClause *yieldClause, + bool isInsertable = false) + : UpdateBaseSentence(updateList, whenClause, yieldClause, nullptr, isInsertable) { + kind_ = Kind::kUpdateVertex; + vid_.reset(vid); } + ~UpdateVertexSentence() {} + bool getInsertable() const { return insertable_; } - void setSrcId(Expression* srcid) { - srcid_.reset(srcid); - } - - Expression* getSrcId() const { - return srcid_.get(); - } - - void setDstId(Expression* dstid) { - dstid_.reset(dstid); - } - - Expression* getDstId() const { - return dstid_.get(); + Expression* getVid() const { + return vid_.get(); } - void setRank(int64_t rank) { - rank_ = rank; - hasRank_ = true; + const UpdateList* updateList() const { + return updateList_.get(); } - int64_t getRank() const { - return rank_; + const WhenClause* whenClause() const { + return whenClause_.get(); } - void setEdgeType(std::string* edgeType) { - edgeType_.reset(edgeType); + const YieldClause* yieldClause() const { + return yieldClause_.get(); } - const std::string* getEdgeType() const { - return edgeType_.get(); - } + std::string toString() const override; - void setUpdateList(UpdateList *updateList) { - updateList_.reset(updateList); - } +private: + std::unique_ptr<Expression> vid_; +}; - const UpdateList* updateList() const { - return updateList_.get(); - } - void setWhenClause(WhenClause *clause) { - whenClause_.reset(clause); +class UpdateEdgeSentence final : public UpdateBaseSentence { +public: + UpdateEdgeSentence(Expression *srcId, + Expression *dstId, + int64_t rank, + std::string *edgeName, + UpdateList *updateList, + WhenClause *whenClause, + YieldClause *yieldClause, + bool isInsertable = false) + : UpdateBaseSentence(updateList, whenClause, yieldClause, edgeName, isInsertable) { + kind_ = Kind::kUpdateEdge; + srcId_.reset(srcId); + dstId_.reset(dstId); + rank_ = rank; } - const WhenClause* whenClause() const { - return whenClause_.get(); + Expression* getSrcId() const { + return srcId_.get(); } - void setYieldClause(YieldClause *clause) { - yieldClause_.reset(clause); + Expression* getDstId() const { + return dstId_.get(); } - const YieldClause* yieldClause() const { - return yieldClause_.get(); + int64_t getRank() const { + return rank_; } std::string toString() const override; private: - bool insertable_{false}; - bool hasRank_{false}; - std::unique_ptr<Expression> srcid_; - std::unique_ptr<Expression> dstid_; + std::unique_ptr<Expression> srcId_; + std::unique_ptr<Expression> dstId_; int64_t rank_{0L}; - std::unique_ptr<std::string> edgeType_; - std::unique_ptr<UpdateList> updateList_; - std::unique_ptr<WhenClause> whenClause_; - std::unique_ptr<YieldClause> yieldClause_; }; diff --git a/src/parser/parser.yy b/src/parser/parser.yy index 99f8ca2d09f96e5796b47a1e23182860852d4156..156b494868a4faed4b2a7ba93158c37407231b26 100644 --- a/src/parser/parser.yy +++ b/src/parser/parser.yy @@ -390,6 +390,10 @@ base_expression | function_call_expression { $$ = $1; } + | name_label { + // need to rewrite the expression + $$ = new SymbolPropertyExpression(Expression::Kind::kSymProperty, new std::string(""), new std::string(""), $1); + } ; input_ref_expression @@ -1492,26 +1496,6 @@ edge_row_item rank: unary_integer { $$ = $1; }; -update_vertex_sentence - : KW_UPDATE KW_VERTEX vid KW_SET update_list when_clause yield_clause { - auto sentence = new UpdateVertexSentence(); - sentence->setVid($3); - sentence->setUpdateList($5); - sentence->setWhenClause($6); - sentence->setYieldClause($7); - $$ = sentence; - } - | KW_UPSERT KW_VERTEX vid KW_SET update_list when_clause yield_clause { - auto sentence = new UpdateVertexSentence(); - sentence->setInsertable(true); - sentence->setVid($3); - sentence->setUpdateList($5); - sentence->setWhenClause($6); - sentence->setYieldClause($7); - $$ = sentence; - } - ; - update_list : update_item { $$ = new UpdateList(); @@ -1527,59 +1511,74 @@ update_item : name_label ASSIGN expression { $$ = new UpdateItem($1, $3); } - | alias_ref_expression ASSIGN expression { - $$ = new UpdateItem($1, $3); - delete $1; + | name_label DOT name_label ASSIGN expression { + auto symExpr = new SymbolPropertyExpression(Expression::Kind::kSymProperty, new std::string(""), $1, $3); + $$ = new UpdateItem(symExpr, $5); + } + ; + +update_vertex_sentence + // ======== Begin: Compatible 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; + } + | KW_UPSERT KW_VERTEX vid KW_SET update_list when_clause yield_clause { + auto sentence = new UpdateVertexSentence($3, $5, $6, $7,true); + $$ = sentence; + } + // ======== End: Compatible with 1.0 ========= + | KW_UPDATE KW_VERTEX KW_ON name_label vid 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 KW_SET update_list when_clause yield_clause { + auto sentence = new UpdateVertexSentence($5, $4, $7, $8, $9, true); + $$ = sentence; } ; update_edge_sentence + // ======== Begin: Compatible with 1.0 ========= : KW_UPDATE KW_EDGE vid R_ARROW vid KW_OF name_label KW_SET update_list when_clause yield_clause { - auto sentence = new UpdateEdgeSentence(); - sentence->setSrcId($3); - sentence->setDstId($5); - sentence->setEdgeType($7); - sentence->setUpdateList($9); - sentence->setWhenClause($10); - sentence->setYieldClause($11); + auto sentence = new UpdateEdgeSentence($3, $5, 0, $7, $9, $10, $11); $$ = sentence; } | KW_UPSERT KW_EDGE vid R_ARROW vid KW_OF name_label KW_SET update_list when_clause yield_clause { - auto sentence = new UpdateEdgeSentence(); - sentence->setInsertable(true); - sentence->setSrcId($3); - sentence->setDstId($5); - sentence->setEdgeType($7); - sentence->setUpdateList($9); - sentence->setWhenClause($10); - sentence->setYieldClause($11); + auto sentence = new UpdateEdgeSentence($3, $5, 0, $7, $9, $10, $11, true); $$ = sentence; } | KW_UPDATE KW_EDGE vid R_ARROW vid AT rank KW_OF name_label KW_SET update_list when_clause yield_clause { - auto sentence = new UpdateEdgeSentence(); - sentence->setSrcId($3); - sentence->setDstId($5); - sentence->setRank($7); - sentence->setEdgeType($9); - sentence->setUpdateList($11); - sentence->setWhenClause($12); - sentence->setYieldClause($13); + auto sentence = new UpdateEdgeSentence($3, $5, $7, $9, $11, $12, $13); $$ = sentence; } | KW_UPSERT KW_EDGE vid R_ARROW vid AT rank KW_OF name_label KW_SET update_list when_clause yield_clause { - auto sentence = new UpdateEdgeSentence(); - sentence->setInsertable(true); - sentence->setSrcId($3); - sentence->setDstId($5); - sentence->setRank($7); - sentence->setEdgeType($9); - sentence->setUpdateList($11); - sentence->setWhenClause($12); - sentence->setYieldClause($13); + auto sentence = new UpdateEdgeSentence($3, $5, $7, $9, $11, $12, $13, true); + $$ = sentence; + } + // ======== End: Compatible with 1.0 ========= + | KW_UPDATE KW_EDGE KW_ON name_label vid R_ARROW vid + KW_SET update_list when_clause yield_clause { + auto sentence = new UpdateEdgeSentence($5, $7, 0, $4, $9, $10, $11); + $$ = sentence; + } + | KW_UPSERT KW_EDGE KW_ON name_label vid R_ARROW vid + KW_SET update_list when_clause yield_clause { + auto sentence = new UpdateEdgeSentence($5, $7, 0, $4, $9, $10, $11, true); + $$ = sentence; + } + | KW_UPDATE KW_EDGE KW_ON name_label vid R_ARROW vid AT rank + KW_SET update_list when_clause yield_clause { + auto sentence = new UpdateEdgeSentence($5, $7, $9, $4, $11, $12, $13); + $$ = sentence; + } + | KW_UPSERT KW_EDGE KW_ON name_label vid R_ARROW vid AT rank + KW_SET update_list when_clause yield_clause { + auto sentence = new UpdateEdgeSentence($5, $7, $9, $4, $11, $12, $13, true); $$ = sentence; } ; diff --git a/src/parser/test/ParserTest.cpp b/src/parser/test/ParserTest.cpp index 41e62be55b94907da16350b10f5a1e305fb2ff1a..4b71a58d495a8a45ea23879d08a262fef1e3750a 100644 --- a/src/parser/test/ParserTest.cpp +++ b/src/parser/test/ParserTest.cpp @@ -145,7 +145,7 @@ TEST(Parser, Go) { } { GQLParser parser; - std::string query = "GO FROM \"1\",\"2\",\"3\" OVER friend WHERE person.name == \"dutor\""; + std::string query = "GO FROM \"1\",\"2\",\"3\" OVER friend WHERE person.name == \"Tom\""; auto result = parser.parse(query); ASSERT_TRUE(result.ok()) << result.status(); } @@ -643,7 +643,7 @@ TEST(Parser, InsertVertex) { { GQLParser parser; std::string query = "INSERT VERTEX person(name,age,married,salary,create_time) " - "VALUES \"dutor\":(\"dutor\", 30, true, 3.14, 1551331900)"; + "VALUES \"Tom\":(\"Tom\", 30, true, 3.14, 1551331900)"; auto result = parser.parse(query); ASSERT_TRUE(result.ok()) << result.status(); } @@ -679,21 +679,21 @@ TEST(Parser, InsertVertex) { { GQLParser parser; std::string query = "INSERT VERTEX person(name,age,married,salary,create_time) " - "VALUES \"dutor\":(\"dutor\", 30, true, 3.14, 1551331900)"; + "VALUES \"Tom\":(\"Tom\", 30, true, 3.14, 1551331900)"; auto result = parser.parse(query); ASSERT_TRUE(result.ok()) << result.status(); } { GQLParser parser; std::string query = "INSERT VERTEX person(name,age,married,salary,create_time) " - "VALUES \"dutor\":(\"dutor\", 30, true, 3.14, 1551331900)"; + "VALUES \"Tom\":(\"Tom\", 30, true, 3.14, 1551331900)"; auto result = parser.parse(query); ASSERT_TRUE(result.ok()) << result.status(); } { GQLParser parser; std::string query = "INSERT VERTEX person(name,age,married,salary,create_time) " - "VALUES \"dutor\":(\"dutor\", 30, true, 3.14, 1551331900)"; + "VALUES \"Tom\":(\"Tom\", 30, true, 3.14, 1551331900)"; auto result = parser.parse(query); ASSERT_TRUE(result.ok()) << result.status(); } @@ -709,7 +709,7 @@ TEST(Parser, InsertVertex) { { GQLParser parser; std::string query = "INSERT VERTEX person(name, age) " - "VALUES \"dutor\":(\"dutor, 30)"; + "VALUES \"Tom\":(\"Tom, 30)"; auto result = parser.parse(query); ASSERT_TRUE(result.status().isSyntaxError()); } @@ -717,21 +717,21 @@ TEST(Parser, InsertVertex) { { GQLParser parser; std::string query = "INSERT VERTEX person(name, age) " - "VALUES \"dutor\":(\'dutor, 30)"; + "VALUES \"Tom\":(\'Tom, 30)"; auto result = parser.parse(query); ASSERT_TRUE(result.status().isSyntaxError()); } { GQLParser parser; std::string query = "INSERT VERTEX person(name, age) " - "VALUES \"dutor\":(\'dutor, 30)"; + "VALUES \"Tom\":(\'Tom, 30)"; auto result = parser.parse(query); ASSERT_TRUE(result.status().isSyntaxError()); } { GQLParser parser; std::string query = "INSERT VERTEX person(name, age) " - "VALUES \"dutor\":(\'dutor, 30)"; + "VALUES \"Tom\":(\'Tom, 30)"; auto result = parser.parse(query); ASSERT_TRUE(result.status().isSyntaxError()); } @@ -740,49 +740,91 @@ TEST(Parser, InsertVertex) { TEST(Parser, UpdateVertex) { { GQLParser parser; - std::string query = "UPDATE VERTEX \"dutor\" " - "SET person.name=\"dutor\", person.age=30, " - "job.salary=10000, person.create_time=1551331999"; + std::string query = "UPDATE VERTEX \"12345\" " + "SET person.name=\"Tome\", person.age=30, " + "job.salary=10000, person.create_time=1551331999"; auto result = parser.parse(query); ASSERT_TRUE(result.ok()) << result.status(); } { GQLParser parser; - std::string query = "UPDATE VERTEX \"dutor\" " - "SET person.name=\"dutor\", person.age=$^.person.age + 1, " - "person.married=true " + std::string query = "UPDATE VERTEX ON person \"12345\" " + "SET name=\"Tome\", age=30"; + auto result = parser.parse(query); + ASSERT_TRUE(result.ok()) << result.status(); + } + { + GQLParser parser; + std::string query = "UPDATE VERTEX \"12345\" " + "SET person.name=\"Tom\", person.age=$^.person.age + 1, " + "person.married=true " "WHEN $^.job.salary > 10000 && $^.person.age > 30"; auto result = parser.parse(query); ASSERT_TRUE(result.ok()) << result.status(); } { GQLParser parser; - std::string query = "UPDATE VERTEX \"dutor\" " - "SET person.name=\"dutor\", person.age=31, person.married=true, " - "job.salary=1.1 * $^.person.create_time / 31536000 " + std::string query = "UPDATE VERTEX \"12345\" " + "SET name=\"Tom\", age=age + 1, " + "married=true " + "WHEN salary > 10000 && age > 30"; + auto result = parser.parse(query); + ASSERT_TRUE(result.ok()) << result.status(); + } + { + GQLParser parser; + std::string query = "UPDATE VERTEX \"12345\" " + "SET person.name=\"Tom\", person.age=31, person.married=true, " + "job.salary=1.1 * $^.person.create_time / 31536000 " "YIELD $^.person.name AS Name, job.name AS Title, " - "$^.job.salary AS Salary"; + "$^.job.salary AS Salary"; + auto result = parser.parse(query); + ASSERT_TRUE(result.ok()) << result.status(); + } + { + GQLParser parser; + std::string query = "UPDATE VERTEX ON person \"12345\" " + "SET name=\"Tom\", age=31, married=true " + "YIELD name AS Name"; auto result = parser.parse(query); ASSERT_TRUE(result.ok()) << result.status(); } { GQLParser parser; - std::string query = "UPDATE VERTEX \"dutor\" " - "SET person.name=\"dutor\", person.age=30, person.married=true " + std::string query = "UPDATE VERTEX \"12345\" " + "SET person.name=\"Tom\", person.age=30, person.married=true " "WHEN $^.job.salary > 10000 && $^.job.name == \"CTO\" || " - "$^.person.age < 30" + "$^.person.age < 30" "YIELD $^.person.name AS Name, $^.job.salary AS Salary, " - "$^.person.create_time AS Time"; + "$^.person.create_time AS Time"; auto result = parser.parse(query); ASSERT_TRUE(result.ok()) << result.status(); } { GQLParser parser; - std::string query = "UPSERT VERTEX \"dutor\" " - "SET person.name=\"dutor\", person.age = 30, job.name =\"CTO\" " + std::string query = "UPDATE VERTEX ON person \"12345\" " + "SET name=\"Tom\", age=30, married=true " + "WHEN salary > 10000 && name == \"CTO\" || age < 30" + "YIELD name AS Name, salary AS Salary, create_time AS Time"; + auto result = parser.parse(query); + ASSERT_TRUE(result.ok()) << result.status(); + } + { + GQLParser parser; + std::string query = "UPSERT VERTEX \"12345\" " + "SET person.name=\"Tom\", person.age = 30, job.name =\"CTO\" " "WHEN $^.job.salary > 10000 " "YIELD $^.person.name AS Name, $^.job.salary AS Salary, " - "$^.person.create_time AS Time"; + "$^.person.create_time AS Time"; + auto result = parser.parse(query); + ASSERT_TRUE(result.ok()) << result.status(); + } + { + GQLParser parser; + std::string query = "UPSERT VERTEX ON person \"12345\" " + "SET name=\"Tom\", age = 30, name =\"CTO\" " + "WHEN salary > 10000 " + "YIELD name AS Name, salary AS Salary, create_time AS Time"; auto result = parser.parse(query); ASSERT_TRUE(result.ok()) << result.status(); } @@ -866,11 +908,26 @@ TEST(Parser, UpdateEdge) { auto result = parser.parse(query); ASSERT_TRUE(result.ok()) << result.status(); } + { + GQLParser parser; + std::string query = "UPDATE EDGE ON transfer \"12345\" -> \"54321\" " + "SET amount=3.14, time=1537408527"; + auto result = parser.parse(query); + ASSERT_TRUE(result.ok()) << result.status(); + } { GQLParser parser; std::string query = "UPDATE EDGE \"12345\" -> \"54321\"@789 OF transfer " "SET amount=3.14,time=1537408527 " - "WHEN transfer.amount > 3.14 && $^.person.name == \"dutor\""; + "WHEN transfer.amount > 3.14 && $^.person.name == \"Tom\""; + auto result = parser.parse(query); + ASSERT_TRUE(result.ok()) << result.status(); + } + { + GQLParser parser; + std::string query = "UPDATE EDGE ON transfer \"12345\" -> \"54321\"@789 " + "SET amount=3.14,time=1537408527 " + "WHEN amount > 3.14"; auto result = parser.parse(query); ASSERT_TRUE(result.ok()) << result.status(); } @@ -880,7 +937,16 @@ TEST(Parser, UpdateEdge) { "SET amount = 3.14 + $^.job.salary, time = 1537408527 " "WHEN transfer.amount > 3.14 || $^.job.salary >= 10000 " "YIELD transfer.amount, transfer.time AS Time, " - "$^.person.name AS PayFrom"; + "$^.person.name AS PayFrom"; + auto result = parser.parse(query); + ASSERT_TRUE(result.ok()) << result.status(); + } + { + GQLParser parser; + std::string query = "UPDATE EDGE ON transfer \"12345\" -> \"54321\" " + "SET amount = 3.14 + amount, time = 1537408527 " + "WHEN amount > 3.14 " + "YIELD amount, time AS Time"; auto result = parser.parse(query); ASSERT_TRUE(result.ok()) << result.status(); } @@ -893,6 +959,15 @@ TEST(Parser, UpdateEdge) { auto result = parser.parse(query); ASSERT_TRUE(result.ok()) << result.status(); } + { + GQLParser parser; + std::string query = "UPSERT EDGE ON transfer \"12345\" -> \"54321\" @789 " + "SET amount=$^.job.salary + 3.14, time=1537408527 " + "WHEN amount > 3.14 && salary >= 10000 " + "YIELD amount, time, name AS Name"; + auto result = parser.parse(query); + ASSERT_TRUE(result.ok()) << result.status(); + } } TEST(Parser, DeleteVertex) { @@ -988,26 +1063,26 @@ TEST(Parser, FetchVertex) { } { GQLParser parser; - std::string query = "FETCH PROP ON person \"dutor\""; + std::string query = "FETCH PROP ON person \"Tom\""; auto result = parser.parse(query); ASSERT_TRUE(result.ok()) << result.status(); } { GQLParser parser; - std::string query = "FETCH PROP ON person \"dutor\", \"darion\""; + std::string query = "FETCH PROP ON person \"Tom\", \"darion\""; auto result = parser.parse(query); ASSERT_TRUE(result.ok()) << result.status(); } { GQLParser parser; - std::string query = "FETCH PROP ON person \"dutor\" " + std::string query = "FETCH PROP ON person \"Tom\" " "YIELD person.name, person.age"; auto result = parser.parse(query); ASSERT_TRUE(result.ok()) << result.status(); } { GQLParser parser; - std::string query = "FETCH PROP ON person \"dutor\", \"darion\" " + std::string query = "FETCH PROP ON person \"Tom\", \"darion\" " "YIELD person.name, person.age"; auto result = parser.parse(query); ASSERT_TRUE(result.ok()) << result.status(); @@ -1035,26 +1110,26 @@ TEST(Parser, FetchVertex) { } { GQLParser parser; - std::string query = "FETCH PROP ON person uuid(\"dutor\")"; + std::string query = "FETCH PROP ON person uuid(\"Tom\")"; auto result = parser.parse(query); ASSERT_TRUE(result.ok()) << result.status(); } { GQLParser parser; - std::string query = "FETCH PROP ON person \"dutor\", \"darion\""; + std::string query = "FETCH PROP ON person \"Tom\", \"darion\""; auto result = parser.parse(query); ASSERT_TRUE(result.ok()) << result.status(); } { GQLParser parser; - std::string query = "FETCH PROP ON person \"dutor\" " + std::string query = "FETCH PROP ON person \"Tom\" " "YIELD person.name, person.age"; auto result = parser.parse(query); ASSERT_TRUE(result.ok()) << result.status(); } { GQLParser parser; - std::string query = "FETCH PROP ON person \"dutor\", \"darion\" " + std::string query = "FETCH PROP ON person \"Tom\", \"darion\" " "YIELD person.name, person.age"; auto result = parser.parse(query); ASSERT_TRUE(result.ok()) << result.status(); @@ -2037,22 +2112,6 @@ TEST(Parser, UseReservedKeyword) { } } -TEST(Parser, IssueLabelAsExpression) { - // name label is not a valid expression, it's not value - { - GQLParser parser; - std::string query = "INSERT VERTEX person(name) VALUES \"1\":(name_label)"; - auto result = parser.parse(query); - ASSERT_FALSE(result.ok()); - } - { - GQLParser parser; - std::string query = "INSERT VERTEX person(name) VALUES \"1\":(`name_label`)"; - auto result = parser.parse(query); - ASSERT_FALSE(result.ok()); - } -} - TEST(Parser, TypeCast) { { GQLParser parser; diff --git a/src/planner/Mutate.h b/src/planner/Mutate.h index f88dc76125c19ad9a30f81c2cb049b420a0598f0..2de152ea1a3ae58becea9941aff67bf483545858 100644 --- a/src/planner/Mutate.h +++ b/src/planner/Mutate.h @@ -132,18 +132,223 @@ private: bool overwritable_; }; -class UpdateVertex final : public SingleInputNode { +class Update : public SingleInputNode { public: + bool getInsertable() const { + return insertable_; + } + + const std::vector<std::string>& getReturnProps() const { + return returnProps_; + } + + const std::string getCondition() const { + return condition_; + } + + const std::vector<std::string>& getYieldNames() const { + return yieldNames_; + } + + GraphSpaceID getSpaceId() const { + return spaceId_; + } + + const std::vector<storage::cpp2::UpdatedProp>& getUpdatedProps() const { + return updatedProps_; + } + + const std::string& getName() const { + return schemaName_; + } + +protected: + Update(Kind kind, + ExecutionPlan* plan, + PlanNode* input, + GraphSpaceID spaceId, + std::string name, + bool insertable, + std::vector<storage::cpp2::UpdatedProp> updatedProps, + std::vector<std::string> returnProps, + std::string condition, + std::vector<std::string> yieldNames) + : SingleInputNode(plan, kind, input) + , spaceId_(spaceId) + , schemaName_(std::move(name)) + , insertable_(insertable) + , updatedProps_(std::move(updatedProps)) + , returnProps_(std::move(returnProps)) + , condition_(std::move(condition)) + , yieldNames_(std::move(yieldNames)) {} + +protected: + GraphSpaceID spaceId_{-1}; + std::string schemaName_; + bool insertable_; + std::vector<storage::cpp2::UpdatedProp> updatedProps_; + std::vector<std::string> returnProps_; + std::string condition_; + std::vector<std::string> yieldNames_; +}; + +class UpdateVertex final : public Update { +public: + static UpdateVertex* make(ExecutionPlan* plan, + PlanNode* input, + GraphSpaceID spaceId, + std::string name, + std::string vId, + TagID tagId, + bool insertable, + std::vector<storage::cpp2::UpdatedProp> updatedProps, + std::vector<std::string> returnProps, + std::string condition, + std::vector<std::string> yieldNames) { + return new UpdateVertex(plan, + input, + spaceId, + std::move(name), + std::move(vId), + tagId, + insertable, + std::move(updatedProps), + std::move(returnProps), + std::move(condition), + std::move(yieldNames)); + } + std::string explain() const override { return "UpdateVertex"; } + + const std::string& getVId() const { + return vId_; + } + + TagID getTagId() const { + return tagId_; + } + +private: + UpdateVertex(ExecutionPlan* plan, + PlanNode* input, + GraphSpaceID spaceId, + std::string name, + std::string vId, + TagID tagId, + bool insertable, + std::vector<storage::cpp2::UpdatedProp> updatedProps, + std::vector<std::string> returnProps, + std::string condition, + std::vector<std::string> yieldNames) + : Update(Kind::kUpdateVertex, + plan, + input, + spaceId, + std::move(name), + insertable, + std::move(updatedProps), + std::move(returnProps), + std::move(condition), + std::move(yieldNames)) + , vId_(std::move(vId)) + , tagId_(tagId) {} + +private: + std::string vId_; + TagID tagId_{-1}; }; -class UpdateEdge final : public SingleInputNode { +class UpdateEdge final : public Update { public: + static UpdateEdge* make(ExecutionPlan* plan, + PlanNode* input, + GraphSpaceID spaceId, + std::string name, + std::string srcId, + std::string dstId, + EdgeType edgeType, + int64_t rank, + bool insertable, + std::vector<storage::cpp2::UpdatedProp> updatedProps, + std::vector<std::string> returnProps, + std::string condition, + std::vector<std::string> yieldNames) { + return new UpdateEdge(plan, + input, + spaceId, + std::move(name), + std::move(srcId), + std::move(dstId), + edgeType, + rank, + insertable, + std::move(updatedProps), + std::move(returnProps), + std::move(condition), + std::move(yieldNames)); + } + std::string explain() const override { return "UpdateEdge"; } + + const std::string& getSrcId() const { + return srcId_; + } + + const std::string& getDstId() const { + return dstId_; + } + + int64_t getRank() const { + return rank_; + } + + int64_t getEdgeType() const { + return edgeType_; + } + + const std::vector<storage::cpp2::UpdatedProp>& getUpdatedProps() const { + return updatedProps_; + } + +private: + UpdateEdge(ExecutionPlan* plan, + PlanNode* input, + GraphSpaceID spaceId, + std::string name, + std::string srcId, + std::string dstId, + EdgeType edgeType, + int64_t rank, + bool insertable, + std::vector<storage::cpp2::UpdatedProp> updatedProps, + std::vector<std::string> returnProps, + std::string condition, + std::vector<std::string> yieldNames) + : Update(Kind::kUpdateEdge, + plan, + input, + spaceId, + std::move(name), + insertable, + std::move(updatedProps), + std::move(returnProps), + std::move(condition), + std::move(yieldNames)) + + , srcId_(std::move(srcId)) + , dstId_(std::move(dstId)) + , rank_(rank) + , edgeType_(edgeType) {} + +private: + std::string srcId_; + std::string dstId_; + int64_t rank_{0}; + EdgeType edgeType_{-1}; }; class DeleteVertices final : public SingleInputNode { diff --git a/src/planner/PlanNode.cpp b/src/planner/PlanNode.cpp index 6c05e13cf1210627c9b28098193e25447e05d015..f7319b26ddf602b24107064c7dc9c8db8093bb37 100644 --- a/src/planner/PlanNode.cpp +++ b/src/planner/PlanNode.cpp @@ -108,6 +108,10 @@ const char* PlanNode::toString(PlanNode::Kind kind) { return "DeleteVertices"; case Kind::kDeleteEdges: return "DeleteEdges"; + case Kind::kUpdateVertex: + return "UpdateVertex"; + case Kind::kUpdateEdge: + return "UpdateEdge"; } LOG(FATAL) << "Impossible kind plan node " << static_cast<int>(kind); } diff --git a/src/planner/PlanNode.h b/src/planner/PlanNode.h index ea70cf3a357d375eacf97c9c2b2144f753d74150..b4e6241e5f2624062f4110045c06bb40fa2e58c9 100644 --- a/src/planner/PlanNode.h +++ b/src/planner/PlanNode.h @@ -68,6 +68,8 @@ public: kDataJoin, kDeleteVertices, kDeleteEdges, + kUpdateVertex, + kUpdateEdge, }; PlanNode(ExecutionPlan* plan, Kind kind); diff --git a/src/validator/MutateValidator.cpp b/src/validator/MutateValidator.cpp index 559e68580a50d725da9eb6b93e0c7d056a6daec1..236442200183b6c667745297835be4d40289da4e 100644 --- a/src/validator/MutateValidator.cpp +++ b/src/validator/MutateValidator.cpp @@ -88,13 +88,25 @@ Status InsertVerticesValidator::prepareVertices() { for (auto i = 0u; i < rows_.size(); i++) { auto *row = rows_[i]; if (propSize_ != row->values().size()) { - return Status::Error("Wrong number of value"); + return Status::Error("Column count doesn't match value count."); + } + if (!evaluableExpr(row->id())) { + LOG(ERROR) << "Wrong vid expression `" << row->id()->toString() << "\""; + return Status::Error("Wrong vid expression `%s'", row->id()->toString().c_str()); } auto idStatus = SchemaUtil::toVertexID(row->id()); if (!idStatus.ok()) { return idStatus.status(); } auto vertexId = std::move(idStatus).value(); + + // check value expr + for (auto &value : row->values()) { + if (!evaluableExpr(value)) { + LOG(ERROR) << "Insert wrong value: `" << value->toString() << "'."; + return Status::Error("Insert wrong value: `%s'.", value->toString().c_str()); + } + } auto valsRet = SchemaUtil::toValueVec(row->values()); if (!valsRet.ok()) { return valsRet.status(); @@ -195,8 +207,18 @@ Status InsertEdgesValidator::prepareEdges() {; for (auto i = 0u; i < rows_.size(); i++) { auto *row = rows_[i]; if (propNames_.size() != row->values().size()) { - return Status::Error("Wrong number of value"); + return Status::Error("Column count doesn't match value count."); + } + if (!evaluableExpr(row->srcid())) { + LOG(ERROR) << "Wrong src vid expression `" << row->srcid()->toString() << "\""; + return Status::Error("Wrong src vid expression `%s'", row->srcid()->toString().c_str()); + } + + if (!evaluableExpr(row->dstid())) { + LOG(ERROR) << "Wrong dst vid expression `" << row->dstid()->toString() << "\""; + return Status::Error("Wrong dst vid expression `%s'", row->dstid()->toString().c_str()); } + auto idStatus = SchemaUtil::toVertexID(row->srcid()); if (!idStatus.ok()) { return idStatus.status(); @@ -210,6 +232,14 @@ Status InsertEdgesValidator::prepareEdges() {; int64_t rank = row->rank(); + // check value expr + for (auto &value : row->values()) { + if (!evaluableExpr(value)) { + LOG(ERROR) << "Insert wrong value: `" << value->toString() << "'."; + return Status::Error("Insert wrong value: `%s'.", value->toString().c_str()); + } + } + auto valsRet = SchemaUtil::toValueVec(row->values()); if (!valsRet.ok()) { return valsRet.status(); @@ -484,5 +514,322 @@ Status DeleteEdgesValidator::toPlan() { return Status::OK(); } +Status UpdateValidator::initProps() { + spaceId_ = vctx_->whichSpace().id; + insertable_ = sentence_->getInsertable(); + if (sentence_->getName() != nullptr) { + name_ = *sentence_->getName(); + } + NG_RETURN_IF_ERROR(getUpdateProps()); + NG_RETURN_IF_ERROR(getCondition()); + return getReturnProps(); +} + +Status UpdateValidator::getCondition() { + auto *clause = sentence_->whenClause(); + if (clause != nullptr) { + auto filter = clause->filter(); + if (filter != nullptr) { + auto encodeStr = filter->encode(); + auto copyFilterExpr = Expression::decode(encodeStr); + NG_LOG_AND_RETURN_IF_ERROR( + checkAndResetSymExpr(copyFilterExpr.get(), name_, encodeStr)); + condition_ = std::move(encodeStr); + } + } + return Status::OK(); +} + +Status UpdateValidator::getReturnProps() { + auto *clause = sentence_->yieldClause(); + if (clause != nullptr) { + auto yields = clause->columns(); + for (auto *col : yields) { + if (col->alias() == nullptr) { + yieldColNames_.emplace_back(col->expr()->toString()); + } else { + yieldColNames_.emplace_back(*col->alias()); + } + auto encodeStr = col->expr()->encode(); + auto copyColExpr = Expression::decode(encodeStr); + + NG_LOG_AND_RETURN_IF_ERROR(checkAndResetSymExpr(copyColExpr.get(), name_, encodeStr)); + returnProps_.emplace_back(std::move(encodeStr)); + } + } + return Status::OK(); +} + +Status UpdateValidator::getUpdateProps() { + auto status = Status::OK(); + auto items = sentence_->updateList()->items(); + std::unordered_set<std::string> symNames; + std::string fieldName; + const std::string *symName = nullptr; + for (auto& item : items) { + storage::cpp2::UpdatedProp updatedProp; + // The syntax has guaranteed it is name or expression + if (item->getFieldName() != nullptr) { + symName = &name_; + fieldName = *item->getFieldName(); + symNames.emplace(name_); + } + if (item->getFieldExpr() != nullptr) { + DCHECK(item->getFieldExpr()->kind() == Expression::Kind::kSymProperty); + auto symExpr = static_cast<const SymbolPropertyExpression*>(item->getFieldExpr()); + symNames.emplace(*symExpr->sym()); + symName = symExpr->sym(); + fieldName = *symExpr->prop(); + } + auto valueExpr = item->value(); + if (valueExpr == nullptr) { + LOG(ERROR) << "valueExpr is nullptr"; + return Status::SyntaxError("Empty update item field value"); + } + auto encodeStr = valueExpr->encode(); + auto copyValueExpr = Expression::decode(encodeStr); + NG_LOG_AND_RETURN_IF_ERROR(checkAndResetSymExpr(copyValueExpr.get(), *symName, encodeStr)); + updatedProp.set_value(std::move(encodeStr)); + updatedProp.set_name(fieldName); + updatedProps_.emplace_back(std::move(updatedProp)); + } + + if (symNames.size() != 1) { + auto errorMsg = "Multi schema name: " + folly::join(",", symNames); + LOG(ERROR) << errorMsg; + return Status::Error(std::move(errorMsg)); + } + if (symName != nullptr) { + name_ = *symName; + } + return status; +} + + +Status UpdateValidator::checkAndResetSymExpr(Expression* inExpr, + const std::string& symName, + std::string &encodeStr) { + bool hasWrongType = false; + auto symExpr = rewriteSymExpr(inExpr, symName, hasWrongType, isEdge_); + if (hasWrongType) { + return Status::Error("Has wrong expr in `%s'", + inExpr->toString().c_str()); + } + if (symExpr != nullptr) { + encodeStr = symExpr->encode(); + return Status::OK(); + } + encodeStr = inExpr->encode(); + return Status::OK(); +} + +// rewrite the expr which has kSymProperty expr to toExpr +std::unique_ptr<Expression> UpdateValidator::rewriteSymExpr(Expression* expr, + const std::string &sym, + bool &hasWrongType, + bool isEdge) { + switch (expr->kind()) { + case Expression::Kind::kConstant: { + break; + } + case Expression::Kind::kAdd: + case Expression::Kind::kMinus: + case Expression::Kind::kMultiply: + case Expression::Kind::kDivision: + case Expression::Kind::kMod: + case Expression::Kind::kRelEQ: + case Expression::Kind::kRelNE: + case Expression::Kind::kRelLT: + case Expression::Kind::kRelLE: + case Expression::Kind::kRelGT: + case Expression::Kind::kRelGE: + case Expression::Kind::kRelIn: + case Expression::Kind::kLogicalAnd: + case Expression::Kind::kLogicalOr: + case Expression::Kind::kLogicalXor: { + auto biExpr = static_cast<BinaryExpression*>(expr); + auto left = rewriteSymExpr(biExpr->left(), sym, hasWrongType, isEdge); + if (left != nullptr) { + biExpr->setLeft(left.release()); + } + auto right = rewriteSymExpr(biExpr->right(), sym, hasWrongType, isEdge); + if (right != nullptr) { + biExpr->setRight(right.release()); + } + break; + } + case Expression::Kind::kUnaryPlus: + case Expression::Kind::kUnaryNegate: + case Expression::Kind::kUnaryNot: { + auto unaryExpr = static_cast<UnaryExpression*>(expr); + auto rewrite = rewriteSymExpr(unaryExpr->operand(), sym, hasWrongType, isEdge); + if (rewrite != nullptr) { + unaryExpr->setOperand(rewrite.release()); + } + break; + } + case Expression::Kind::kFunctionCall: { + auto funcExpr = static_cast<FunctionCallExpression*>(expr); + auto* argList = const_cast<ArgumentList*>(funcExpr->args()); + auto args = argList->moveArgs(); + for (auto iter = args.begin(); iter < args.end(); ++iter) { + auto rewrite = rewriteSymExpr(iter->get(), sym, hasWrongType, isEdge); + if (rewrite != nullptr) { + *iter = std::move(rewrite); + } + } + argList->setArgs(std::move(args)); + break; + } + case Expression::Kind::kTypeCasting: { + auto castExpr = static_cast<TypeCastingExpression*>(expr); + auto operand = rewriteSymExpr(castExpr->operand(), sym, hasWrongType, isEdge); + if (operand != nullptr) { + castExpr->setOperand(operand.release()); + } + break; + } + case Expression::Kind::kSymProperty: { + auto symExpr = static_cast<SymbolPropertyExpression*>(expr); + if (isEdge) { + return std::make_unique<EdgePropertyExpression>( + new std::string(sym), new std::string(*symExpr->prop())); + } else { + if (symExpr->sym() == nullptr || !symExpr->sym()->empty()) { + hasWrongType = true; + return nullptr; + } + return std::make_unique<SourcePropertyExpression>( + new std::string(sym), new std::string(*symExpr->prop())); + } + } + case Expression::Kind::kSrcProperty: { + if (isEdge) { + hasWrongType = true; + } + break; + } + case Expression::Kind::kEdgeProperty: { + if (!isEdge) { + hasWrongType = true; + } + break; + } + case Expression::Kind::kDstProperty: + case Expression::Kind::kTagProperty: + case Expression::Kind::kEdgeSrc: + case Expression::Kind::kEdgeRank: + case Expression::Kind::kEdgeDst: + case Expression::Kind::kEdgeType: + case Expression::Kind::kUUID: + case Expression::Kind::kVar: + case Expression::Kind::kVersionedVar: + case Expression::Kind::kVarProperty: + case Expression::Kind::kInputProperty: + case Expression::Kind::kUnaryIncr: + case Expression::Kind::kUnaryDecr: { + hasWrongType = true; + break; + } + } + return nullptr; +} + + +Status UpdateVertexValidator::validateImpl() { + auto sentence = static_cast<UpdateVertexSentence*>(sentence_); + auto idRet = SchemaUtil::toVertexID(sentence->getVid()); + if (!idRet.ok()) { + LOG(ERROR) << idRet.status(); + return idRet.status(); + } + vId_ = std::move(idRet).value(); + NG_RETURN_IF_ERROR(initProps()); + auto ret = qctx_->schemaMng()->toTagID(spaceId_, name_); + if (!ret.ok()) { + LOG(ERROR) << "No schema found for " << name_; + return Status::Error("No schema found for `%s'", name_.c_str()); + } + tagId_ = ret.value(); + return Status::OK(); +} + +Status UpdateVertexValidator::toPlan() { + auto* plan = qctx_->plan(); + auto *update = UpdateVertex::make(plan, + nullptr, + spaceId_, + std::move(name_), + vId_, + tagId_, + insertable_, + std::move(updatedProps_), + std::move(returnProps_), + std::move(condition_), + std::move(yieldColNames_)); + root_ = update; + tail_ = root_; + return Status::OK(); +} + +Status UpdateEdgeValidator::validateImpl() { + auto sentence = static_cast<UpdateEdgeSentence*>(sentence_); + auto srcIdRet = SchemaUtil::toVertexID(sentence->getSrcId()); + if (!srcIdRet.ok()) { + LOG(ERROR) << srcIdRet.status(); + return srcIdRet.status(); + } + srcId_ = std::move(srcIdRet).value(); + auto dstIdRet = SchemaUtil::toVertexID(sentence->getDstId()); + 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_; + return Status::Error("No schema found for `%s'", name_.c_str()); + } + edgeType_ = ret.value(); + return Status::OK(); +} + +Status UpdateEdgeValidator::toPlan() { + auto* plan = qctx_->plan(); + auto *outNode = UpdateEdge::make(plan, + nullptr, + spaceId_, + name_, + srcId_, + dstId_, + edgeType_, + rank_, + insertable_, + updatedProps_, + {}, + condition_, + {}); + + auto *inNode = UpdateEdge::make(plan, + 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; + return Status::OK(); +} + } // namespace graph } // namespace nebula diff --git a/src/validator/MutateValidator.h b/src/validator/MutateValidator.h index 2045b29c19564930acf43915c32aaa5b1931c62c..e06cefa7257a4cee4cb93f295a66364e900edb10 100644 --- a/src/validator/MutateValidator.h +++ b/src/validator/MutateValidator.h @@ -110,6 +110,84 @@ private: std::vector<EdgeKeyRef*> edgeKeyRefs_; std::string edgeKeyVar_; }; + +class UpdateValidator : public Validator { +public: + explicit UpdateValidator(Sentence* sentence, + QueryContext* context, + bool isEdge = false) + : Validator(sentence, context) { + sentence_ = static_cast<UpdateBaseSentence*>(sentence); + isEdge_ = isEdge; + } + + virtual ~UpdateValidator() {} + +protected: + Status initProps(); + + Status getCondition(); + + Status getReturnProps(); + + Status getUpdateProps(); + +private: + Status checkAndResetSymExpr(Expression* inExpr, + const std::string& symName, + std::string &encodeStr); + + std::unique_ptr<Expression> rewriteSymExpr(Expression* expr, + const std::string &sym, + bool &hasWrongType, + bool isEdge = false); + +protected: + UpdateBaseSentence *sentence_; + bool insertable_{false}; + GraphSpaceID spaceId_{-1}; + std::vector<std::string> returnProps_; + std::vector<std::string> yieldColNames_; + std::string condition_; + std::vector<storage::cpp2::UpdatedProp> updatedProps_; + std::string name_; + bool isEdge_{false}; +}; + +class UpdateVertexValidator final : public UpdateValidator { +public: + UpdateVertexValidator(Sentence* sentence, QueryContext* context) + : UpdateValidator(sentence, context) { + } + +private: + Status validateImpl() override; + + Status toPlan() override; + +private: + std::string vId_; + TagID tagId_{-1}; +}; + +class UpdateEdgeValidator final : public UpdateValidator { +public: + UpdateEdgeValidator(Sentence* sentence, QueryContext* context) + : UpdateValidator(sentence, context, true) { + } + +private: + Status validateImpl() override; + + Status toPlan() override; + +private: + std::string srcId_; + std::string dstId_; + EdgeRanking rank_{0}; + EdgeType edgeType_{-1}; +}; } // namespace graph } // namespace nebula #endif // VALIDATOR_MUTATEVALIDATOR_H + diff --git a/src/validator/Validator.cpp b/src/validator/Validator.cpp index 17b7d22c2ec6d77ca1637eaaa81d484620c20f7d..cea013c2c08f658a9aee3b3473c040cf273cfafb 100644 --- a/src/validator/Validator.cpp +++ b/src/validator/Validator.cpp @@ -114,6 +114,10 @@ std::unique_ptr<Validator> Validator::makeValidator(Sentence* sentence, QueryCon return std::make_unique<DeleteVerticesValidator>(sentence, context); case Sentence::Kind::kDeleteEdges: return std::make_unique<DeleteEdgesValidator>(sentence, context); + case Sentence::Kind::kUpdateVertex: + return std::make_unique<UpdateVertexValidator>(sentence, context); + case Sentence::Kind::kUpdateEdge: + return std::make_unique<UpdateEdgeValidator>(sentence, context); default: return std::make_unique<ReportError>(sentence, context); } @@ -156,7 +160,9 @@ Status Validator::appendPlan(PlanNode* node, PlanNode* appended) { case PlanNode::Kind::kDropSnapshot: case PlanNode::Kind::kShowSnapshots: case PlanNode::Kind::kDeleteVertices: - case PlanNode::Kind::kDeleteEdges: { + case PlanNode::Kind::kDeleteEdges: + case PlanNode::Kind::kUpdateVertex: + case PlanNode::Kind::kUpdateEdge: { static_cast<SingleDependencyNode*>(node)->dependsOn(appended); break; } diff --git a/src/validator/test/MaintainValidatorTest.cpp b/src/validator/test/MaintainValidatorTest.cpp index 8f0b68a553b63c1ae7ae62d186737216b6680b22..169e1dd56e3cb670d0c9147e10e5679a2a8ec716 100644 --- a/src/validator/test/MaintainValidatorTest.cpp +++ b/src/validator/test/MaintainValidatorTest.cpp @@ -28,7 +28,7 @@ TEST_F(MaintainValidatorTest, TagTest) { } // the same name schema { - ASSERT_FALSE(checkResult("CREATE TAG TEST(); CREATE TAG TEST;", {})); + ASSERT_FALSE(checkResult("CREATE TAG TEST();CREATE TAG TEST()", {})); } } @@ -41,7 +41,7 @@ TEST_F(MaintainValidatorTest, EdgeTest) { } // the same name schema { - ASSERT_FALSE(checkResult("CREATE EDGE TEST(); CREATE EDGE TEST;", {})); + ASSERT_FALSE(checkResult("CREATE EDGE TEST();CREATE EDGE TEST()", {})); } } } // namespace graph diff --git a/src/validator/test/MutateValidatorTest.cpp b/src/validator/test/MutateValidatorTest.cpp index b7b084916cf5d2991eb2cd61b9a023ae9a2896f2..357373874f4133eb16c3bbd0b92180638a9ecc46 100644 --- a/src/validator/test/MutateValidatorTest.cpp +++ b/src/validator/test/MutateValidatorTest.cpp @@ -19,6 +19,16 @@ TEST_F(MutateValidatorTest, InsertVertexTest) { auto cmd = "INSERT VERTEX person(name, age2) VALUES \"A\":(\"a\", 19);"; ASSERT_FALSE(checkResult(cmd, {})); } + // wrong vid expression + { + auto cmd = "INSERT VERTEX person(name, age2) VALUES hash($-,name):(\"a\", 19);"; + ASSERT_FALSE(checkResult(cmd, {})); + } + // vid use function call + { + auto cmd = "INSERT VERTEX person(name, age) VALUES lower(\"TOM\"):(\"a\", 19);"; + ASSERT_TRUE(checkResult(cmd, { PK::kInsertVertices, PK::kStart })); + } } TEST_F(MutateValidatorTest, InsertEdgeTest) { @@ -27,6 +37,16 @@ TEST_F(MutateValidatorTest, InsertEdgeTest) { auto cmd = "INSERT EDGE like(start, end2) VALUES \"A\"->\"B\":(11, 11);"; ASSERT_FALSE(checkResult(cmd, {})); } + // wrong vid expression + { + auto cmd = "INSERT EDGE like(start, end) VALUES hash($-,name)->\"Tom\":(2010, 2020);"; + ASSERT_FALSE(checkResult(cmd, {})); + } + // vid use function call + { + auto cmd = "INSERT EDGE like(start, end) VALUES lower(\"Lily\")->\"Tom\":(2010, 2020);"; + ASSERT_TRUE(checkResult(cmd, { PK::kInsertEdges, PK::kStart })); + } } TEST_F(MutateValidatorTest, DeleteVertexTest) { @@ -110,5 +130,76 @@ TEST_F(MutateValidatorTest, DeleteEdgeTest) { ASSERT_FALSE(checkResult(cmd)); } } + +TEST_F(MutateValidatorTest, UpdateVertexTest) { + // not exist tag + { + auto cmd = "UPDATE VERTEX ON student \"Tom\" SET count = 1"; + ASSERT_FALSE(checkResult(cmd, {})); + } + // multi tags + { + auto cmd = "UPDATE VERTEX \"Tom\" SET person.count = 1, student.age = $^.student.age + 1"; + ASSERT_FALSE(checkResult(cmd, {})); + } + // wrong expr + { + auto cmd = "UPDATE VERTEX \"Tom\" SET person.age = person.age + 1"; + ASSERT_FALSE(checkResult(cmd, {})); + } + // with function + { + auto cmd = "UPDATE VERTEX ON person \"Tom\" SET age = abs(age + 1)"; + ASSERT_TRUE(checkResult(cmd, {})); + } + // 1.0 syntax succeed + { + auto cmd = "UPDATE VERTEX \"Tom\"" + "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})); + } + // 2.0 syntax succeed + { + auto cmd = "UPDATE VERTEX ON person \"Tom\"" + "SET age = age + 1 " + "WHEN page == 18 " + "YIELD name AS name, age AS age"; + ASSERT_TRUE(checkResult(cmd, {PK::kUpdateVertex, PK::kStart})); + } +} + +TEST_F(MutateValidatorTest, UpdateEdgeTest) { + // not exist edge + { + auto cmd = "UPDATE EDGE ON study \"Tom\"->\"Lily\" SET count = 1"; + ASSERT_FALSE(checkResult(cmd, {})); + } + // Wrong expr "$^.peson.age" + { + auto cmd = "UPDATE EDGE \"Tom\"->\"Lily\" OF like " + "SET end = like.end + 1 " + "WHEN $^.peson.age >= 18 " + "YIELD $^.peson.age AS age, like.end AS end"; + ASSERT_FALSE(checkResult(cmd, {})); + } + // 1.0 syntax succeed + { + auto cmd = "UPDATE EDGE \"Tom\"->\"Lily\" OF like " + "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})); + } + // 2.0 syntax succeed + { + auto cmd = "UPDATE EDGE ON like \"Tom\"->\"Lily\"" + "SET end = end + 1 " + "WHEN start >= 2010 " + "YIELD start AS start, end AS end"; + ASSERT_TRUE(checkResult(cmd, {PK::kUpdateEdge, PK::kUpdateEdge, PK::kStart})); + } +} } // namespace graph } // namespace nebula diff --git a/src/validator/test/ValidatorTestBase.cpp b/src/validator/test/ValidatorTestBase.cpp index 606fed112cc7a199e9154ffc2692dbd254002ab8..5330ae5169e3208fdd3677f5aeae20b60e8c2ee7 100644 --- a/src/validator/test/ValidatorTestBase.cpp +++ b/src/validator/test/ValidatorTestBase.cpp @@ -81,6 +81,8 @@ namespace graph { case PlanNode::Kind::kDropSnapshot: case PlanNode::Kind::kShowSnapshots: case PlanNode::Kind::kDataJoin: + case PlanNode::Kind::kUpdateVertex: + case PlanNode::Kind::kUpdateEdge: LOG(FATAL) << "Unimplemented"; case PlanNode::Kind::kDataCollect: { const auto *lDC = static_cast<const DataCollect*>(l); diff --git a/src/validator/test/ValidatorTestBase.h b/src/validator/test/ValidatorTestBase.h index fa09d1c54f112a8b59e9c32d171520f8fce3ac41..86095a250ce95391cee4cbdc7d180d70f5b1cb97 100644 --- a/src/validator/test/ValidatorTestBase.h +++ b/src/validator/test/ValidatorTestBase.h @@ -188,7 +188,9 @@ protected: case PlanNode::Kind::kShowSnapshots: case PlanNode::Kind::kDataJoin: case PlanNode::Kind::kDeleteVertices: - case PlanNode::Kind::kDeleteEdges: { + case PlanNode::Kind::kDeleteEdges: + case PlanNode::Kind::kUpdateVertex: + case PlanNode::Kind::kUpdateEdge: { auto* current = static_cast<const SingleInputNode*>(node); queue.emplace(current->dep()); break; diff --git a/src/validator/test/YieldValidatorTest.cpp b/src/validator/test/YieldValidatorTest.cpp index 478cf0e918db38285dfc3b04c9587ceaf0cdeac4..9a272b49c8df4464b303d906f06ecaa743e3f3f7 100644 --- a/src/validator/test/YieldValidatorTest.cpp +++ b/src/validator/test/YieldValidatorTest.cpp @@ -153,10 +153,10 @@ TEST_F(YieldValidatorTest, TypeCastTest) { EXPECT_TRUE(checkResult(query, expected_)); } { - std::string query = "YIELD (int30)\"-123\""; + std::string query = "YIELD (int30)(\"-123\")"; auto result = checkResult(query); EXPECT_EQ(std::string(result.message()), - "SyntaxError: syntax error near `)\"-123\"'"); + "SyntaxError: syntax error near `(\"-123\")'"); } { std::string query = "YIELD (int)\"123abc\""; @@ -209,10 +209,10 @@ TEST_F(YieldValidatorTest, TypeCastTest) { EXPECT_TRUE(checkResult(query, expected_)); } { - std::string query = "YIELD (MAP)\"12\""; + std::string query = "YIELD (MAP)(\"12\")"; auto result = checkResult(query); EXPECT_EQ(std::string(result.message()), - "SyntaxError: syntax error near `)\"12\"'"); + "SyntaxError: syntax error near `(\"12\")'"); } { std::string query = "YIELD (SET)12"; @@ -224,13 +224,13 @@ TEST_F(YieldValidatorTest, TypeCastTest) { std::string query = "YIELD (PATH)true"; auto result = checkResult(query); EXPECT_EQ(std::string(result.message()), - "SyntaxError: syntax error near `)true'"); + "SyntaxError: syntax error near `true'"); } { std::string query = "YIELD (NOEXIST)true"; auto result = checkResult(query); EXPECT_EQ(std::string(result.message()), - "SyntaxError: syntax error near `)true'"); + "SyntaxError: syntax error near `true'"); } } diff --git a/tests/mutate/test_insert_2.py b/tests/mutate/test_insert_2.py index 5340e907ce38f7230818b98be44bfe3bbcbabf0c..8cb188e66222cdf16e7cb75b12339652720f379a 100644 --- a/tests/mutate/test_insert_2.py +++ b/tests/mutate/test_insert_2.py @@ -46,6 +46,10 @@ class TestInsert2(NebulaTestSuite): resp = self.execute('INSERT VERTEX course(name) VALUES "English":("English")') self.check_resp_succeeded(resp) + def test_insert_with_name_label(self): + resp = self.execute('INSERT VERTEX course(name) VALUES "English":(English)') + self.check_resp_failed(resp) + @pytest.mark.skip(reason="does not support fetch") def test_insert_with_fix_string(self): resp = self.execute('FETCH PROP ON course "English"') diff --git a/tests/mutate/test_update_upsert.py b/tests/mutate/test_update_upsert.py new file mode 100644 index 0000000000000000000000000000000000000000..164ebaf6eb88e285b1449b17ff1705bfe9c71e34 --- /dev/null +++ b/tests/mutate/test_update_upsert.py @@ -0,0 +1,659 @@ +# --coding:utf-8-- +# +# Copyright (c) 2020 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. + +import time +import pytest + +from tests.common.nebula_test_suite import NebulaTestSuite + +class TestUpdateVertex(NebulaTestSuite): + @classmethod + def prepare(self): + resp = self.execute('CREATE SPACE myspace_test2(partition_num=1, replica_factor=1, vid_size=20);' + 'USE myspace_test2;' + 'CREATE TAG course(name string, credits int);' + 'CREATE TAG building(name string);' + 'CREATE TAG student(name string, age int, gender string);' + 'CREATE TAG student_default(name string NOT NULL, age int NOT NULL, ' + 'gender string DEFAULT "one", birthday int DEFAULT 2010);' + 'CREATE EDGE like(likeness double);' + 'CREATE EDGE select(grade int, year int);' + 'CREATE EDGE select_default(grade int NOT NULL, ' + 'year TIMESTAMP DEFAULT 1546308000)') + self.check_resp_succeeded(resp) + time.sleep(self.delay) + + resp = self.execute('INSERT VERTEX student(name, age, gender) VALUES ' + '"200":("Monica", 16, "female"),' + '"201":("Mike", 18, "male"),' + '"202":("Jane", 17, "female");') + self.check_resp_succeeded(resp) + + resp = self.execute('INSERT VERTEX course(name, credits),building(name) VALUES ' + '"101":("Math", 3, "No5"), ' + '"102":("English", 6, "No11");') + self.check_resp_succeeded(resp) + + resp = self.execute('INSERT VERTEX course(name, credits) VALUES "103":("CS", 5);') + self.check_resp_succeeded(resp) + + # resp = self.execute('INSERT VERTEX student(name, age, gender) VALUES ' + # 'uuid("Monica"):("Monica", 16, "female"), ' + # 'uuid("Mike"):("Mike", 18, "male"), ' + # 'uuid("Jane"):("Jane", 17, "female");') + # self.check_resp_succeeded(resp) + + # resp = self.execute('INSERT VERTEX course(name, credits),building(name) VALUES ' + # 'uuid("Math"):("Math", 3, "No5"), ' + # 'uuid("English"):("English", 6, "No11");') + # self.check_resp_succeeded(resp) + + # resp = self.execute('INSERT VERTEX course(name, credits) VALUES uuid("CS"):("CS", 5);') + # self.check_resp_succeeded(resp) + + resp = self.execute('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);') + self.check_resp_succeeded(resp) + + resp = self.execute('INSERT EDGE like(likeness) VALUES ' + '"200" -> "201"@0:(92.5), ' + '"201" -> "200"@0:(85.6), ' + '"201" -> "202"@0:(93.2);') + self.check_resp_succeeded(resp) + + # resp = self.execute('INSERT EDGE select(grade, year) VALUES ' + # 'uuid("Monica") -> uuid("Math")@0:(5, 2018), ' + # 'uuid("Monica") -> uuid("English")@0:(3, 2018), ' + # 'uuid("Mike") -> uuid("English")@0:(3, 2019), ' + # 'uuid("Jane") -> uuid("English")@0:(3, 2019);') + self.check_resp_succeeded(resp) + + # resp = self.execute('INSERT EDGE like(likeness) VALUES ' + # 'uuid("Monica") -> uuid("Mike")@0:(92.5), ' + # 'uuid("Monica") -> uuid("English")@0:(3, 2018), ' + # 'uuid("Mike") -> uuid("Monica")@0:(85.6), ' + # 'uuid("Mike") -> uuid("Jane")@0:(93.2)') + # self.check_resp_succeeded(resp) + + @classmethod + def cleanup(self): + resp = self.execute('DROP SPACE myspace_test2;') + self.check_resp_succeeded(resp) + + def test_update_vertex(self): + resp = self.execute('UPDATE VERTEX "101" ' + 'SET course.credits = $^.course.credits + 1;') + self.check_resp_succeeded(resp) + + # resp = self.execute('UPDATE VERTEX ON course uuid("Math") ' + # 'SET credits = $^.course.credits + 1;') + # self.check_resp_succeeded(resp) + + # filter out + resp = self.execute('UPDATE VERTEX "101" ' + 'SET course.credits = $^.course.credits + 1 ' + 'WHEN $^.course.name == "English" && $^.course.credits > 2') + self.check_resp_succeeded(resp) + + # set filter + resp = self.execute('UPDATE VERTEX "101" ' + 'SET course.credits = $^.course.credits + 1 ' + 'WHEN $^.course.name == "Math" && $^.course.credits > 2') + self.check_resp_succeeded(resp) + + # update with uuid + # resp = self.execute('UPDATE VERTEX ON course uuid("Math") ' + # 'SET credits = $^.course.credits + 1 ' + # 'WHEN $^.course.name == "Math" && $^.course.credits > 2') + # self.check_resp_succeeded(resp) + + # set yield + resp = self.execute_query('UPDATE VERTEX "101" ' + 'SET course.credits = $^.course.credits + 1 ' + 'YIELD $^.course.name AS Name, $^.course.credits AS Credits') + self.check_resp_succeeded(resp) + expected_result = [["Math", 6]] + self.check_result(resp, expected_result) + + # set yield with uuid + # resp = self.execute_query('UPDATE VERTEX ON course uuid("Math") ' + # 'SET credits = $^.course.credits + 1 ' + # 'YIELD $^.course.name AS Name, $^.course.credits AS Credits') + # self.check_resp_succeeded(resp) + # expected_result = [["Math", 6]] + # self.check_result(resp, expected_result) + + # set filter and yield + resp = self.execute_query('UPDATE VERTEX "101" ' + 'SET course.credits = $^.course.credits + 1 ' + 'WHEN $^.course.name == "Math" && $^.course.credits > 2 ' + 'YIELD $^.course.name AS Name, $^.course.credits AS Credits') + self.check_resp_succeeded(resp) + expected_result = [["Math", 7]] + self.check_result(resp, expected_result) + + # set filter and yield with uuid + # resp = self.execute_query('UPDATE VERTEX ON course uuid("Math") ' + # 'SET credits = $^.course.credits + 1 ' + # 'WHEN $^.course.name == "Math" && $^.course.credits > 2 ' + # 'YIELD $^.course.name AS Name, $^.course.credits AS Credits') + # self.check_resp_succeeded(resp) + # expected_result = [["Math", 7]] + # self.check_result(resp, expected_result) + + # set filter out and yield + resp = self.execute_query('UPDATE VERTEX "101" ' + 'SET course.credits = $^.course.credits + 1 ' + 'WHEN $^.course.name == "notexist" && $^.course.credits > 2' + 'YIELD $^.course.name AS Name, $^.course.credits AS Credits') + self.check_resp_succeeded(resp) + expected_result = [["Math", 7]] + self.check_result(resp, expected_result) + + # set filter out and yield with uuid + # resp = self.execute_query('UPDATE VERTEX ON course uuid("Math") ' + # 'SET credits = $^.course.credits + 1 ' + # 'WHEN $^.course.name == "notexist" && $^.course.credits > 2' + # 'YIELD $^.course.name AS Name, $^.course.credits AS Credits') + # self.check_resp_succeeded(resp) + # expected_result = [["Math", 7]] + # self.check_result(resp, expected_result) + + def test_update_edge(self): + # resp = self.execute_query('FETCH PROP "200"->"101"@0 OF select ' + # 'YIELD select.grade, select.year') + # self.check_resp_succeeded(resp) + # expected_result = [[200, 101, 0, 5, 2018]] + # self.check_result(resp, expected_result) + + # update edge + resp = self.execute('UPDATE EDGE "200" -> "101"@0 OF select ' + 'SET grade = select.grade + 1, year = 2000;') + self.check_resp_succeeded(resp) + + # check + # resp = self.execute_query('FETCH PROP ON select "200"->"101"@0 ' + # 'YIELD select.grade, select.year') + # self.check_resp_succeeded(resp) + # expected_result = [[200, 101, 0, 6, 2000]] + # self.check_result(resp, expected_result) + + resp = self.execute_query('GO FROM "101" OVER select REVERSELY ' + 'YIELD select.grade, select.year') + self.check_resp_succeeded(resp) + expected_result = [[6, 2000]] + self.check_result(resp, expected_result) + + # update edge with uuid + # resp = self.execute('UPDATE EDGE ON select uuid("Monica") -> uuid("Math")@0 ' + # 'SET grade = select.grade + 1, year = 2000;') + # self.check_resp_succeeded(resp) + + # filter out, 2.0 storage not support update edge can use vertex + resp = self.execute('UPDATE EDGE "200" -> "101"@0 OF select ' + 'SET grade = select.grade + 1, year = 2000 ' + 'WHEN select.grade > 1024 && $^.student.age > 15;') + self.check_resp_failed(resp) + + # 2.0 test, filter out + resp = self.execute('UPDATE EDGE "200" -> "101"@0 OF select ' + 'SET grade = select.grade + 1, year = 2000 ' + 'WHEN select.grade > 1024') + self.check_resp_succeeded(resp) + + # set filter, 2.0 storage not support update edge can use vertex + resp = self.execute('UPDATE EDGE "200" -> "101"@0 OF select ' + 'SET grade = select.grade + 1, year = 2000 ' + 'WHEN select.grade > 4 && $^.student.age > 15;') + self.check_resp_failed(resp) + + # set filter + resp = self.execute('UPDATE EDGE "200" -> "101"@0 OF select ' + 'SET grade = select.grade + 1, year = 2000 ' + 'WHEN select.grade > 4') + self.check_resp_succeeded(resp) + + + # set filter + # resp = self.execute('UPDATE EDGE ON select uuid("Monica") -> uuid("Math")@0 ' + # 'SET grade = select.grade + 1, year = 2000' + # 'WHEN select.grade > 4 && $^.student.age > 15;') + self.check_resp_succeeded(resp) + + # set yield, 2.0 storage not support update edge can use vertex + resp = self.execute_query('UPDATE EDGE "200" -> "101"@0 OF select ' + 'SET grade = select.grade + 1, year = 2018 ' + 'YIELD $^.student.name AS Name, select.grade AS Grade, select.year AS Year') + self.check_resp_failed(resp) + + + # set yield + resp = self.execute_query('UPDATE EDGE "200" -> "101"@0 OF select ' + 'SET grade = select.grade + 1, year = 2018 ' + 'YIELD select.grade AS Grade, select.year AS Year') + self.check_resp_succeeded(resp) + expected_result = [[8, 2018]] + self.check_result(resp, expected_result) + + # set yield + # resp = self.execute('UPDATE EDGE ON select uuid("Monica") -> uuid("Math"@0 ' + # 'SET grade = select.grade + 1, year = 2018 ' + # 'YIELD $^.student.name AS Name, select.grade AS Grade, select.year AS Year') + # self.check_resp_succeeded(resp) + # expected_result = [["Monica", 8, 2018]] + # self.check_result(resp, expected_result) + + # set filter and yield, 2.0 storage not support update edge can use vertex + resp = self.execute_query('UPDATE EDGE "200" -> "101"@0 OF select ' + 'SET grade = select.grade + 1, year = 2019 ' + 'WHEN select.grade > 4 && $^.student.age > 15 ' + 'YIELD $^.student.name AS Name, select.grade AS Grade, select.year AS Year') + self.check_resp_failed(resp) + + resp = self.execute_query('UPDATE EDGE "200" -> "101"@0 OF select ' + 'SET grade = select.grade + 1, year = 2019 ' + 'WHEN select.grade > 4 ' + 'YIELD select.grade AS Grade, select.year AS Year') + self.check_resp_succeeded(resp) + expected_result = [[9, 2019]] + self.check_result(resp, expected_result) + + # set filter and yield with uuid + # resp = self.execute('UPDATE EDGE ON select uuid("Monica") -> uuid("Math"@0 ' + # 'SET grade = select.grade + 1, year = 2018 ' + # 'WHEN select.grade > 4 && $^.student.age > 15 ' + # 'YIELD $^.student.name AS Name, select.grade AS Grade, select.year AS Year') + # self.check_resp_succeeded(resp) + # expected_result = [["Monica", 9, 2019]] + # self.check_result(resp, expected_result) + + + # set filter out and yield, 2.0 storage not support update edge can use vertex + resp = self.execute_query('UPDATE EDGE "200" -> "101"@0 OF select ' + 'SET grade = select.grade + 1, year = 2019 ' + 'WHEN select.grade > 233333333333 && $^.student.age > 15 ' + 'YIELD $^.student.name AS Name, select.grade AS Grade, select.year AS Year') + self.check_resp_failed(resp) + + # set filter out and yield + resp = self.execute_query('UPDATE EDGE "200" -> "101"@0 OF select ' + 'SET grade = select.grade + 1, year = 2019 ' + 'WHEN select.grade > 233333333333' + 'YIELD select.grade AS Grade, select.year AS Year') + self.check_resp_succeeded(resp) + expected_result = [[9, 2019]] + self.check_result(resp, expected_result) + + # set filter out and yield + # resp = self.execute('UPDATE EDGE ON select uuid("Monica") -> uuid("Math"@0 ' + # 'SET grade = select.grade + 1, year = 2018 ' + # 'WHEN select.grade > 233333333333 && $^.student.age > 15 ' + # 'YIELD $^.student.name AS Name, select.grade AS Grade, select.year AS Year') + # self.check_resp_succeeded(resp) + # expected_result = [["Monica", 9, 2019]] + # self.check_result(resp, expected_result) + + def test_update_vertex_failed(self): + # update vertex: the item is TagName.PropName = Expression in SET clause + resp = self.execute_query('UPDATE VERTEX "101" ' + 'SET course.credits = $^.course.credits + 1, course.name = "No9"') + self.check_resp_succeeded(resp) + + # the $$.TagName.PropName expressions are not allowed in any update sentence + resp = self.execute_query('UPDATE VERTEX "101" ' + 'SET course.credits = $$.course.credits + 1 ' + 'WHEN $$.course.name == "Math" && $^.course.credits > $$.course.credits + 1 ' + 'YIELD $^.course.name AS Name, $^.course.credits AS Credits, $$.building.name') + self.check_resp_failed(resp) + + # make sure TagName and PropertyName must exist in all clauses + resp = self.execute_query('UPDATE VERTEX "101" ' + 'SET nonexistentTag.credits = $^.nonexistentTag.credits + 1') + self.check_resp_failed(resp) + + def test_update_edge_failed(self): + # update edge: the item is PropName = Expression in SET clause + resp = self.execute_query('UPDATE EDGE "200" -> "101"@0 OF select ' + 'SET select = select.grade + 1, select.year = 2019') + self.check_resp_failed(resp) + + # make sure EdgeName and PropertyName must exist in all clauses + resp = self.execute_query('UPDATE EDGE "200" -> "101"@0 OF select ' + 'SET nonexistentProperty = select.grade + 1, year = 2019 ' + 'WHEN nonexistentEdgeName.grade > 4 && $^.student.nonexistentProperty > 15 ' + 'YIELD $^.nonexistentTag.name AS Name, select.nonexistentProperty AS Grade') + self.check_resp_failed(resp) + + # make sure the edge_type must not exist + resp = self.execute_query('UPDATE EDGE "200" -> "101"@0 OF nonexistentEdgeTypeName ' + 'SET grade = nonexistentEdgeTypeName.grade + 1, year = 2019') + self.check_resp_failed(resp) + + + def test_upsert_vertex(self): + # vertex not exist + # resp = self.execute_query('FETCH PROP ON course "103" YIELD course.name, course.credits') + # self.check_resp_succeeded(resp) + # expected_result = [["103", "CS", 5]] + # self.check_result(resp, expected_result) + + # not allow to handle multi tagid when update + resp = self.execute('UPDATE VERTEX "103" ' + 'SET course.credits = $^.course.credits + 1, name = $^.building.name') + self.check_resp_failed(resp) + + # update: vertex 103 ("CS", 5) --> ("CS", 6), TODO: storage not ready + # resp = self.execute_query('UPDATE VERTEX "103" ' + # 'SET course.credits = $^.course.credits + 1 ' + # 'WHEN $^.course.name == "CS" && $^.course.credits > 2 ' + # "YIELD $^.course.name AS Name, $^.course.credits AS Credits") + # self.check_resp_succeeded(resp) + # expected_result = [["CS", 6]] + # self.check_result(resp, expected_result) + + # when tag on vertex not exists, update failed + resp = self.execute_query('UPDATE VERTEX "104" ' + 'SET course.credits = $^.course.credits + 1 ' + 'WHEN $^.course.name == "CS" && $^.course.credits > 2 ' + "YIELD $^.course.name AS Name, $^.course.credits AS Credits") + self.check_resp_failed(resp) + + # has default value test, + # Insertable: vertex 110 ("Ann") --> ("Ann", "one"), + # 110 is nonexistent, gender with default value, name and age without default value + # TODO: storage not ready + # resp = self.execute_query('UPSERT VERTEX "110" ' + # 'SET student_default.name = "Ann", student_default.age = 10 ' + # "YIELD $^.student_default.name AS Name, $^.student_default.gender AS Gender") + # self.check_resp_succeeded(resp) + # expected_result = [["Ann", "one"]] + # self.check_result(resp, expected_result) + + # Insertable failed, 111 is nonexistent, name and age without default value + resp = self.execute_query('UPSERT VERTEX "111" ' + 'SET student_default.name = "Tom", ' + 'age = $^.student_default.age + 8 ' + "YIELD $^.student_default.name AS Name, $^.student_default.age AS Age") + self.check_resp_failed(resp) + + # Insertable failed, 111 is nonexistent, name without default value + resp = self.execute_query('UPSERT VERTEX ON student_default "111" ' + 'SET student_default.gender = "two", age = 10 ' + "YIELD $^.student_default.name AS Name, $^.student_default.gender AS Gender") + self.check_resp_failed(resp) + + # Insertable: vertex 112 ("Lily") --> ("Lily", "one") + # 112 is nonexistent, gender with default value + # update student_default.age with string value + resp = self.execute_query('UPSERT VERTEX "112" ' + 'SET student_default.name = "Lily", age = "10"' + "YIELD $^.student_default.name AS Name, $^.student_default.gender AS Gender") + self.check_resp_failed(resp) + + # Insertable: vertex 113 ("Jack") --> ("Jack", "Three") + # 113 is nonexistent, gender with default value, + # update student_default.age with string value + resp = self.execute_query('UPSERT VERTEX "113" ' + 'SET student_default.name = "Ann", age = "10"' + "YIELD $^.student_default.name AS Name, $^.student_default.gender AS Gender") + self.check_resp_failed(resp) + + # when tag on vertex not exists, update failed + resp = self.execute_query('UPDATE VERTEX "104" ' + 'SET course.credits = $^.course.credits + 1 ' + 'WHEN $^.course.name == \"CS\" && $^.course.credits > 2 ' + 'YIELD $^.course.name AS Name, $^.course.credits AS Credits') + self.check_resp_failed(resp) + + # Insertable success, 115 is nonexistent, name and age without default value, + # the filter is always true. + # TODO: storage not ready + # resp = self.execute_query('UPSERT VERTEX "115" ' + # 'SET student_default.name = "Kate", student_default.age = 12 ' + # 'WHEN $^.student_default.gender == "two"' + # "YIELD $^.student_default.name AS Name, $^.student_default.age AS Age, " + # "$^.student_default.gender AS gender") + # self.check_resp_succeeded(resp) + # expected_result = [["Kate", 12, "one"]] + # self.check_result(resp, expected_result) + + # Order problem + # Insertable success, 116 is nonexistent, name and age without default value, + # the filter is always true. + # TODO: storage not ready + # resp = self.execute_query('UPSERT VERTEX "116" ' + # 'SET student_default.name = "Kate", student_default.age = $^.student_default.birthday + 1,' + # 'student_default.birthday = $^.student_default.birthday + 1 ' + # 'WHEN $^.student_default.gender == "two"' + # 'YIELD $^.student_default.name AS Name, $^.student_default.age AS Age, ' + # '$^.student_default.gender AS gender, $^.student_default.birthday AS birthday') + # self.check_resp_succeeded(resp) + # expected_result = [["Kate", 2011, "one", 2011]] + # self.check_result(resp, expected_result) + + # Order problem + # Insertable success, 117 is nonexistent, name and age without default value, + # the filter is always true. + # TODO: storage not ready + # resp = self.execute_query('UPSERT VERTEX "117" ' + # 'SET student_default.birthday = $^.student_default.birthday + 1,' + # 'student_default.name = "Kate", ' + # 'student_default.age = $^.student_default.birthday + 1 ' + # 'YIELD $^.student_default.name AS Name, $^.student_default.age AS Age, ' + # '$^.student_default.gender AS gender, $^.student_default.birthday AS birthday') + # self.check_resp_succeeded(resp) + # expected_result = [["Kate", 2012, "one", 2011]] + # self.check_result(resp, expected_result) + + def test_upsert_edge(self): + # resp = self.execute_query('FETCH PROP ON select "200"->"101"@0 YIELD select.grade, select.year') + # self.check_resp_succeeded(resp) + # expected_result = [["200", "101", 0, 5, 2018]] + # self.check_result(resp, expected_result) + + # Insertable, upsert when edge exists, 2.0 storage not support update edge with vertex prop + resp = self.execute_query('UPSERT EDGE ON select "201" -> "101"@0' + 'SET grade = 3, year = 2019 ' + 'WHEN $^.student.age > 15 && $^.student.gender == "male" ' + 'YIELD select.grade AS Grade, select.year AS Year') + self.check_resp_failed(resp) + + # Insertable, upsert when edge not exist + resp = self.execute_query('UPSERT EDGE ON select "201" -> "101"@0' + 'SET grade = 3, year = 2019 ' + 'WHEN select.grade > 3 ' + 'YIELD select.grade AS Grade, select.year AS Year') + self.check_resp_succeeded(resp) + expected_result = [[3, 2019]] + self.check_result(resp, expected_result) + + # update when edge not exists, failed + resp = self.execute_query('UPDATE EDGE ON select "601" -> "101"@0' + 'SET year = 2019 ' + 'WHEN select.grade >10 ' + 'YIELD select.grade AS Grade, select.year AS Year') + self.check_resp_failed(resp) + + # upsert when edge not exists,success + # filter condition is always true, insert default value or update value + resp = self.execute_query('UPSERT EDGE ON select "601" -> "101"@0' + 'SET year = 2019, grade = 3 ' + 'WHEN select.grade >10 ' + 'YIELD select.grade AS Grade, select.year AS Year') + self.check_resp_succeeded(resp) + expected_result = [[3, 2019]] + self.check_result(resp, expected_result) + + # resp = self.execute_query('FETCH PROP ON select 601->101@0 YIELD select.grade, select.year') + # self.check_resp_succeeded(resp) + # expected_result = [["601", "101", 0, 3, 2019]] + # self.check_result(resp, expected_result) + + # select_default's year with default value, timestamp not support + # resp = self.execute_query('UPSERT EDGE ON select_default "111" -> "222"@0 ' + # 'SET grade = 3 ' + # 'YIELD select_default.grade AS Grade, select_default.year AS Year') + # self.check_resp_succeeded(resp) + # expected_result = [[3, 1546308000]] + # self.check_result(resp, expected_result) + + # select_default's year is timestamp type, set str type, , timestamp not support + # resp = self.execute_query('UPSERT EDGE ON select_default "222" -> "333"@0 ' + # 'SET grade = 3, year = "2020-01-10 10:00:00" ' + # 'YIELD select_default.grade AS Grade, select_default.year AS Year') + # self.check_resp_succeeded(resp) + # expected_result = [[3, 1578621600]] + # self.check_result(resp, expected_result) + + # select_default's grade without default value + resp = self.execute_query('UPSERT EDGE ON select_default "444" -> "555"@0 ' + 'SET year = 1546279201 ' + 'YIELD select.grade AS Grade, select.year AS Year') + self.check_resp_failed(resp) + + # select_default's grade without default value + resp = self.execute_query('UPSERT EDGE ON select_default "333" -> "444"@0 ' + 'SET grade = 3 + select_default.grade ' + 'YIELD select_default.grade AS Grade, select_default.year AS Year') + self.check_resp_failed(resp) + + # update select_default's year with edge prop value, grade is not null value and without default value + # TODO: storage not ready + # resp = self.execute_query('UPSERT EDGE ON select_default "222" -> "444"@0 ' + # 'SET grade = 3, year = select_default.year + 10 ' + # 'YIELD select_default.grade AS Grade, select_default.year AS Year') + # self.check_resp_succeeded(resp) + + # TODO: timestamp has not supported + # expected_result = [[3, 1546308010]] + # self.check_result(resp, expected_result) + + def test_update_not_exist(self): + # make sure the vertex must not exist + resp = self.execute_query('UPDATE VERTEX "1010000" ' + 'SET course.credits = $^.course.credits + 1, name = "No9" ' + 'WHEN $^.course.name == "Math" && $^.course.credits > 2 ' + 'YIELD select_default.grade AS Grade, select_default.year AS Year') + self.check_resp_failed(resp) + + # make sure the edge(src, dst) must not exist + resp = self.execute_query('UPDATE EDGE ON select "200" -> "101000000000000"@0 ' + 'SET grade = select.grade + 1, year = 2019 ' + 'WHEN select.grade > 4 && $^.student.age > 15 ' + 'YIELD $^.student.name AS Name, select.grade AS Grade, select.year AS Year') + self.check_resp_failed(resp) + + # make sure the edge(src, ranking, dst) must not exist + resp = self.execute_query('UPDATE EDGE ON select "200" -> "101"@123456789 ' + 'SET grade = select.grade + 1, year = 2019 ' + 'WHEN select.grade > 4 && $^.student.age > 15 ' + 'YIELD $^.student.name AS Name, select.grade AS Grade, select.year AS Year') + self.check_resp_failed(resp) + self.check_resp_failed(resp) + + def test_upsert_then_insert(self): + resp = self.execute_query('UPSERT VERTEX "100" SET building.name = "No1"') + self.check_resp_succeeded(resp) + + # resp = self.execute_query('FETCH PROP on building "100"') + # self.check_resp_succeeded(resp) + # expected_result = [["100", "No1"]] + # self.check_result(resp, expected_result) + + resp = self.execute('INSERT VERTEX building(name) VALUES "100": ("No2")') + self.check_resp_succeeded(resp) + + # resp = self.execute_query('FETCH PROP on building "100"') + # self.check_resp_succeeded(resp) + # expected_result = [["100", "No2"]] + # self.check_result(resp, expected_result) + + resp = self.execute_query('UPSERT VERTEX "101" SET building.name = "No1"') + self.check_resp_succeeded(resp) + + # resp = self.execute_query('FETCH PROP on building "101"') + # self.check_resp_succeeded(resp) + # expected_result = [["101", "No1"]] + # self.check_result(resp, expected_result) + + resp = self.execute('INSERT VERTEX building(name) VALUES "101": ("No2")') + self.check_resp_succeeded(resp) + + # resp = self.execute_query('FETCH PROP on building "101"') + # self.check_resp_succeeded(resp) + # expected_result = [["101", "No2"]] + # self.check_result(resp, expected_result) + + def test_upsert_after_alter_schema(self): + resp = self.execute('INSERT VERTEX building(name) VALUES "100": ("No1")') + self.check_resp_succeeded(resp) + + resp = self.execute('ALTER TAG building ADD (new_field string default "123")') + self.check_resp_succeeded(resp) + + time.sleep(self.delay) + + resp = self.execute('UPSERT VERTEX "100" SET building.name = "No2"') + self.check_resp_succeeded(resp) + + # resp = self.execute_query('FETCH PROP on building "100"') + # self.check_resp_succeeded(resp) + # expected_result = [["100", "No2", "123"]] + # self.check_result(resp, expected_result) + + resp = self.execute('UPSERT VERTEX "100" SET building.name = "No3", building.new_field = "321"') + self.check_resp_succeeded(resp) + + # resp = self.execute_query('FETCH PROP on building "101"') + # self.check_resp_succeeded(resp) + # expected_result = [["100", "No3", "321"]] + # self.check_result(resp, expected_result) + + resp = self.execute('UPSERT VERTEX "101" SET building.name = "No1", building.new_field = "No2"') + self.check_resp_succeeded(resp) + + # resp = self.execute_query('FETCH PROP on building "101"') + # self.check_resp_succeeded(resp) + # expected_result = [["100", "No1", "No2"]] + # self.check_result(resp, expected_result) + + # Test upsert edge after alter schema + resp = self.execute('INSERT EDGE like(likeness) VALUES "1" -> "100":(1.0)') + self.check_resp_succeeded(resp) + + resp = self.execute('ALTER EDGE like ADD (new_field string default "123")') + self.check_resp_succeeded(resp) + + time.sleep(self.delay) + + resp = self.execute('UPSERT EDGE "1"->"100" OF like SET likeness = 2.0') + self.check_resp_succeeded(resp) + + # resp = self.execute_query('FETCH PROP ON like "1"->"100"@0') + # self.check_resp_succeeded(resp) + # expected_result = [["1", "100", 0, 2.0, "123"]] + # self.check_result(resp, expected_result) + + resp = self.execute('UPSERT EDGE "1"->"100" OF like SET likeness = 3.0, new_field = "321"') + self.check_resp_succeeded(resp) + + # resp = self.execute_query('FETCH PROP ON like "1"->"100"@0') + # self.check_resp_succeeded(resp) + # expected_result = [["1", "100", 0, 3.0, "321"]] + # self.check_result(resp, expected_result) + + resp = self.execute('UPSERT EDGE "1"->"101" OF like SET likeness = 1.0, new_field = "111"') + self.check_resp_succeeded(resp) + + # resp = self.execute_query('FETCH PROP ON like "1"->"101"@0') + # self.check_resp_succeeded(resp) + # expected_result = [["1", "101", 0, 1.0, "111"]] + # self.check_result(resp, expected_result) + diff --git a/tests/mutate/test_update_upsert_2.py b/tests/mutate/test_update_upsert_2.py new file mode 100644 index 0000000000000000000000000000000000000000..61ed472c234c586f5d6a9d9cae712822c4d9616e --- /dev/null +++ b/tests/mutate/test_update_upsert_2.py @@ -0,0 +1,552 @@ +# --coding:utf-8-- +# +# Copyright (c) 2020 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. + +import time +import pytest + +from tests.common.nebula_test_suite import NebulaTestSuite + +class TestUpdateVertex(NebulaTestSuite): + @classmethod + def prepare(self): + resp = self.execute('CREATE SPACE myspace_test_update(partition_num=1, replica_factor=1, vid_size=20);' + 'USE myspace_test_update;' + 'CREATE TAG course(name string, credits int);' + 'CREATE TAG building(name string);' + 'CREATE TAG student(name string, age int, gender string);' + 'CREATE TAG student_default(name string NOT NULL, age int NOT NULL, ' + 'gender string DEFAULT "one", birthday int DEFAULT 2010);' + 'CREATE EDGE like(likeness double);' + 'CREATE EDGE select(grade int, year int);' + 'CREATE EDGE select_default(grade int NOT NULL, ' + 'year TIMESTAMP DEFAULT 1546308000)') + self.check_resp_succeeded(resp) + time.sleep(self.delay) + + resp = self.execute('INSERT VERTEX student(name, age, gender) VALUES ' + '"200":("Monica", 16, "female"),' + '"201":("Mike", 18, "male"),' + '"202":("Jane", 17, "female");') + self.check_resp_succeeded(resp) + + resp = self.execute('INSERT VERTEX course(name, credits),building(name) VALUES ' + '"101":("Math", 3, "No5"), ' + '"102":("English", 6, "No11");') + self.check_resp_succeeded(resp) + + resp = self.execute('INSERT VERTEX course(name, credits) VALUES "103":("CS", 5);') + self.check_resp_succeeded(resp) + + resp = self.execute('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);') + self.check_resp_succeeded(resp) + + resp = self.execute('INSERT EDGE like(likeness) VALUES ' + '"200" -> "201"@0:(92.5), ' + '"201" -> "200"@0:(85.6), ' + '"201" -> "202"@0:(93.2);') + self.check_resp_succeeded(resp) + + @classmethod + def cleanup(self): + resp = self.execute('DROP SPACE myspace_test_update;') + self.check_resp_succeeded(resp) + + def test_update_vertex(self): + resp = self.execute('UPDATE VERTEX ON course "101" ' + 'SET credits = credits + 1;') + self.check_resp_succeeded(resp) + + # filter out + resp = self.execute('UPDATE VERTEX ON course "101" ' + 'SET credits = credits + 1 ' + 'WHEN name == "English" && credits > 2') + self.check_resp_succeeded(resp) + + # set filter + resp = self.execute('UPDATE VERTEX ON course "101" ' + 'SET credits = credits + 1 ' + 'WHEN name == "Math" && credits > 2') + self.check_resp_succeeded(resp) + + # set yield + resp = self.execute_query('UPDATE VERTEX ON course "101" ' + 'SET credits = credits + 1 ' + 'YIELD name AS Name, credits AS Credits') + self.check_resp_succeeded(resp) + expected_result = [["Math", 6]] + self.check_result(resp, expected_result) + + # set filter and yield + resp = self.execute_query('UPDATE VERTEX ON course "101" ' + 'SET credits = credits + 1 ' + 'WHEN name == "Math" && credits > 2 ' + 'YIELD name AS Name, credits AS Credits') + self.check_resp_succeeded(resp) + expected_result = [["Math", 7]] + self.check_result(resp, expected_result) + + # set filter out and yield + resp = self.execute_query('UPDATE VERTEX ON course "101" ' + 'SET credits = credits + 1 ' + 'WHEN name == "notexist" && credits > 2' + 'YIELD name AS Name, credits AS Credits') + self.check_resp_succeeded(resp) + expected_result = [["Math", 7]] + self.check_result(resp, expected_result) + + + def test_update_edge(self): + # resp = self.execute_query('FETCH PROP ON select "200"->"101"@0 ' + # 'YIELD select.grade, select.year') + # self.check_resp_succeeded(resp) + # expected_result = [[200, 101, 0, 5, 2018]] + # self.check_result(resp, expected_result) + + # update edge + resp = self.execute('UPDATE EDGE ON select "200" -> "101"@0 ' + 'SET grade = grade + 1, year = 2000;') + self.check_resp_succeeded(resp) + + # check + # resp = self.execute_query('FETCH PROP ON select "200"->"101"@0 ' + # 'YIELD select.grade, select.year') + # self.check_resp_succeeded(resp) + # expected_result = [[200, 101, 0, 6, 2000]] + # self.check_result(resp, expected_result) + + resp = self.execute_query('GO FROM "101" OVER select REVERSELY ' + 'YIELD select.grade, select.year') + self.check_resp_succeeded(resp) + expected_result = [[6, 2000]] + self.check_result(resp, expected_result) + + # filter out, 2.0 storage not support update edge can use vertex + resp = self.execute('UPDATE EDGE ON select "200" -> "101"@0 ' + 'SET grade = select.grade + 1, year = 2000 ' + 'WHEN select.grade > 1024 && $^.student.age > 15;') + self.check_resp_failed(resp) + + # 2.0 test, filter out + resp = self.execute('UPDATE EDGE ON select "200" -> "101"@0 ' + 'SET grade = select.grade + 1, year = 2000 ' + 'WHEN select.grade > 1024') + self.check_resp_succeeded(resp) + + # set filter, 2.0 storage not support update edge can use vertex + resp = self.execute('UPDATE EDGE ON select "200" -> "101"@0 ' + 'SET grade = select.grade + 1, year = 2000 ' + 'WHEN select.grade > 4 && $^.student.age > 15;') + self.check_resp_failed(resp) + + # set filter + resp = self.execute('UPDATE EDGE ON select "200" -> "101"@0 ' + 'SET grade = select.grade + 1, year = 2000 ' + 'WHEN select.grade > 4') + self.check_resp_succeeded(resp) + + # set yield, 2.0 storage not support update edge can use vertex + resp = self.execute_query('UPDATE EDGE ON select "200" -> "101"@0 ' + 'SET grade = select.grade + 1, year = 2018 ' + 'YIELD $^.student.name AS Name, select.grade AS Grade, select.year AS Year') + self.check_resp_failed(resp) + + + # set yield + resp = self.execute_query('UPDATE EDGE ON select "200" -> "101"@0 ' + 'SET grade = select.grade + 1, year = 2018 ' + 'YIELD select.grade AS Grade, select.year AS Year') + self.check_resp_succeeded(resp) + expected_result = [[8, 2018]] + self.check_result(resp, expected_result) + + # set filter and yield, 2.0 storage not support update edge can use vertex + resp = self.execute_query('UPDATE EDGE ON select "200" -> "101"@0 ' + 'SET grade = select.grade + 1, year = 2019 ' + 'WHEN select.grade > 4 && $^.student.age > 15 ' + 'YIELD $^.student.name AS Name, select.grade AS Grade, select.year AS Year') + self.check_resp_failed(resp) + + resp = self.execute_query('UPDATE EDGE ON select "200" -> "101"@0 ' + 'SET grade = select.grade + 1, year = 2019 ' + 'WHEN select.grade > 4 ' + 'YIELD select.grade AS Grade, select.year AS Year') + self.check_resp_succeeded(resp) + expected_result = [[9, 2019]] + self.check_result(resp, expected_result) + + # set filter out and yield, 2.0 storage not support update edge can use vertex + resp = self.execute_query('UPDATE EDGE ON select "200" -> "101"@0 ' + 'SET grade = select.grade + 1, year = 2019 ' + 'WHEN select.grade > 233333333333 && $^.student.age > 15 ' + 'YIELD $^.student.name AS Name, select.grade AS Grade, select.year AS Year') + self.check_resp_failed(resp) + + # set filter out and yield + resp = self.execute_query('UPDATE EDGE ON select "200" -> "101"@0 ' + 'SET grade = select.grade + 1, year = 2019 ' + 'WHEN select.grade > 233333333333' + 'YIELD select.grade AS Grade, select.year AS Year') + self.check_resp_succeeded(resp) + expected_result = [[9, 2019]] + self.check_result(resp, expected_result) + + def test_update_vertex_failed(self): + # update vertex: the item is TagName.PropName = Expression in SET clause, 2.0 supported + resp = self.execute_query('UPDATE VERTEX ON course "101" ' + 'SET credits = credits + 1, name = "No9"') + self.check_resp_succeeded(resp) + + # the $$.TagName.PropName expressions are not allowed in any update sentence + resp = self.execute_query('UPDATE VERTEX ON course "101" ' + 'SET credits = $$.course.credits + 1 ' + 'WHEN $$.course.name == "Math" && credits > $$.course.credits + 1 ' + 'YIELD name AS Name, credits AS Credits, $$.building.name') + self.check_resp_failed(resp) + + # make sure TagName and PropertyName must exist in all clauses + resp = self.execute_query('UPDATE VERTEX ON nonexistentTag "101" ' + 'SET credits = credits + 1, name = "No9" ' + 'WHEN name == "Math" && nonexistentProperty > 2 ' + 'YIELD name AS Name, $^.nonexistentTag.nonexistentProperty') + self.check_resp_failed(resp) + + def test_update_edge_failed(self): + # update edge: the item is PropName = Expression in SET clause + resp = self.execute_query('UPDATE EDGE ON select "200" -> "101"@0' + 'SET select = select.grade + 1, select.year = 2019') + self.check_resp_failed(resp) + + # make sure EdgeName and PropertyName must exist in all clauses + resp = self.execute_query('UPDATE EDGE ON select "200" -> "101"@0 ' + 'SET nonexistentProperty = select.grade + 1, year = 2019 ' + 'WHEN nonexistentEdgeName.grade > 4 && $^.student.nonexistentProperty > 15 ' + 'YIELD $^.nonexistentTag.name AS Name, select.nonexistentProperty AS Grade') + self.check_resp_failed(resp) + + # make sure the edge_type must not exist + resp = self.execute_query('UPDATE EDGE ON nonexistentEdgeTypeName "200" -> "101"@0 ' + 'SET grade = select.grade + 1, year = 2019') + self.check_resp_failed(resp) + + + def test_upsert_vertex(self): + # vertex not exist + # resp = self.execute_query('FETCH PROP ON course "103" YIELD course.name, course.credits') + # self.check_resp_succeeded(resp) + # expected_result = [["103", "CS", 5]] + # self.check_result(resp, expected_result) + + # not allow to handle multi tagid when update + resp = self.execute('UPDATE VERTEX ON course "103" ' + 'SET credits = credits + 1, name = $^.building.name') + self.check_resp_failed(resp) + + # update: vertex 103 ("CS", 5) --> ("CS", 6) + resp = self.execute_query('UPDATE VERTEX ON course "103" ' + 'SET credits = credits + 1 ' + 'WHEN name == "CS" && credits > 2 ' + "YIELD name AS Name, credits AS Credits") + self.check_resp_succeeded(resp) + expected_result = [["CS", 6]] + self.check_result(resp, expected_result) + + # when tag on vertex not exists, update failed + resp = self.execute_query('UPDATE VERTEX ON course "104" ' + 'SET credits = credits + 1 ' + 'WHEN name == "CS" && credits > 2 ' + "YIELD name AS Name, credits AS Credits") + self.check_resp_failed(resp) + + # has default value test, + # Insertable: vertex 110 ("Ann") --> ("Ann", "one"), + # 110 is nonexistent, gender with default value + # TODO: storage not ready + # resp = self.execute_query('UPSERT VERTEX ON student_default "110" ' + # 'SET name = "Ann", age = 10 ' + # "YIELD name AS Name, gender AS Gender") + # self.check_resp_succeeded(resp) + # expected_result = [["Ann", "one"]] + # self.check_result(resp, expected_result) + + # Insertable failed, 111 is nonexistent, name and age without default value + resp = self.execute_query('UPSERT VERTEX ON student_default "111" ' + 'SET name = "Tom", ' + 'age = age + 8 ' + "YIELD name AS Name, age AS Age") + self.check_resp_failed(resp) + + # Insertable failed, 111 is nonexistent, name without default value + resp = self.execute_query('UPSERT VERTEX ON student_default "111" ' + 'SET gender = "two", age = 10 ' + "YIELD name AS Name, gender AS Gender") + self.check_resp_failed(resp) + + # Insertable: vertex 112 ("Lily") --> ("Lily", "one") + # 112 is nonexistent, gender with default value + # update student_default.age with string value + resp = self.execute_query('UPSERT VERTEX ON student_default "112" ' + 'SET name = "Lily", age = "10"' + "YIELD name AS Name, gender AS Gender") + self.check_resp_failed(resp) + + # Insertable: vertex 113 ("Jack") --> ("Jack", "Three") + # 113 is nonexistent, gender with default value, + # update student_default.age with string value + resp = self.execute_query('UPSERT VERTEX ON student_default "113" ' + 'SET name = "Ann", age = "10"' + "YIELD name AS Name, gender AS Gender") + self.check_resp_failed(resp) + + # when tag on vertex not exists, update failed + resp = self.execute_query('UPDATE VERTEX ON course "104" ' + 'SET credits = credits + 1 ' + 'WHEN name == \"CS\" && credits > 2 ' + 'YIELD name AS Name, credits AS Credits') + self.check_resp_failed(resp) + + # Insertable success, 115 is nonexistent, name and age without default value, + # the filter is always true. + # TODO: storage not ready + # resp = self.execute_query('UPSERT VERTEX ON student_default "115" ' + # 'SET name = "Kate", age = 12 ' + # 'WHEN gender == "two" ' + # "YIELD name AS Name, age AS Age, gender AS gender") + # self.check_resp_succeeded(resp) + # expected_result = [["Kate", 12, "one"]] + # self.check_result(resp, expected_result) + + # Order problem + # Insertable success, 116 is nonexistent, name and age without default value, + # the filter is always true. + # TODO: storage not ready + # resp = self.execute_query('UPSERT VERTEX ON student_default "116" ' + # 'SET name = "Kate", age = birthday + 1,' + # 'birthday = birthday + 1 ' + # 'WHEN gender == "two" ' + # 'YIELD name AS Name, age AS Age, ' + # 'gender AS gender, birthday AS birthday') + # self.check_resp_succeeded(resp) + # expected_result = [['Kate', 2011, 'one', 2011]] + # self.check_result(resp, expected_result) + + # Order problem + # Insertable success, 117 is nonexistent, name and age without default value, + # the filter is always true. + # TODO: storage not ready + # resp = self.execute_query('UPSERT VERTEX ON student_default "117" ' + # 'SET birthday = birthday + 1, name = "Kate", age = birthday + 1 ' + # 'YIELD name AS Name, age AS Age, gender AS gender, birthday AS birthday') + # self.check_resp_succeeded(resp) + # expected_result = [["Kate", 2012, "one", 2011]] + # self.check_result(resp, expected_result) + + def test_upsert_edge(self): + # resp = self.execute_query('FETCH PROP ON select "200"->"101"@0 YIELD select.grade, select.year') + # self.check_resp_succeeded(resp) + # expected_result = [["200", "101", 0, 5, 2018]] + # self.check_result(resp, expected_result) + + # Insertable, upsert when edge exists, 2.0 storage not support + resp = self.execute_query('UPSERT EDGE ON select "201" -> "101"@0' + 'SET grade = 3, year = 2019 ' + 'WHEN $^.student.age > 15 && $^.student.gender == "male" ' + 'YIELD select.grade AS Grade, select.year AS Year') + self.check_resp_failed(resp) + + # Insertable, upsert when edge not exist + resp = self.execute_query('UPSERT EDGE ON select "201" -> "101"@0' + 'SET grade = 3, year = 2019 ' + 'WHEN select.grade > 3 ' + 'YIELD select.grade AS Grade, select.year AS Year') + self.check_resp_succeeded(resp) + expected_result = [[3, 2019]] + self.check_result(resp, expected_result) + + # update when edge not exists, failed + resp = self.execute_query('UPDATE EDGE ON select "601" -> "101"@0' + 'SET year = 2019 ' + 'WHEN select.grade >10 ' + 'YIELD select.grade AS Grade, select.year AS Year') + self.check_resp_failed(resp) + + # upsert when edge not exists,success + # filter condition is always true, insert default value or update value + resp = self.execute_query('UPSERT EDGE ON select "601" -> "101"@0' + 'SET year = 2019, grade = 3 ' + 'WHEN grade >10 ' + 'YIELD grade AS Grade, year AS Year') + self.check_resp_succeeded(resp) + expected_result = [[3, 2019]] + self.check_result(resp, expected_result) + + # resp = self.execute_query('FETCH PROP ON select 601->101@0 YIELD select.grade, select.year') + # self.check_resp_succeeded(resp) + # expected_result = [["601", "101", 0, 3, 2019]] + # self.check_result(resp, expected_result) + + # select_default's year with default value, timestamp not support + # resp = self.execute_query('UPSERT EDGE ON select_default "111" -> "222"@0 ' + # 'SET grade = 3 ' + # 'YIELD select_default.grade AS Grade, select_default.year AS Year') + # self.check_resp_succeeded(resp) + # expected_result = [[3, 1546308000]] + # self.check_result(resp, expected_result) + + # select_default's year is timestamp type, set str type, , timestamp not support + # resp = self.execute_query('UPSERT EDGE ON select_default "222" -> "333"@0 ' + # 'SET grade = 3, year = "2020-01-10 10:00:00" ' + # 'YIELD select_default.grade AS Grade, select_default.year AS Year') + # self.check_resp_succeeded(resp) + # expected_result = [[3, 1578621600]] + # self.check_result(resp, expected_result) + + # select_default's grade without default value + resp = self.execute_query('UPSERT EDGE ON select_default "444" -> "555"@0 ' + 'SET year = 1546279201 ' + 'YIELD grade AS Grade, year AS Year') + self.check_resp_failed(resp) + + # select_default's grade without default value + resp = self.execute_query('UPSERT EDGE ON select_default "333" -> "444"@0 ' + 'SET grade = 3 + grade ' + 'YIELD grade AS Grade, year AS Year') + self.check_resp_failed(resp) + + # update select_default's year with edge prop value + # TODO: storage not ready + # resp = self.execute_query('UPSERT EDGE ON select_default "222" -> "444"@0 ' + # 'SET grade = 3, year = year + 10 ' + # 'YIELD grade AS Grade, year AS Year') + # self.check_resp_succeeded(resp) + + # TODO: timestamp has not supported + # expected_result = [[3, 1546308010]] + # self.check_result(resp, expected_result) + + + def test_update_not_exist(self): + # make sure the vertex must not exist + resp = self.execute_query('UPDATE VERTEX ON course "1010000" ' + 'SET credits = credits + 1, name = "No9" ' + 'WHEN name == "Math" && credits > 2 ' + 'YIELD name as Name') + self.check_resp_failed(resp) + + # make sure the edge(src, dst) must not exist + resp = self.execute_query('UPDATE EDGE ON select "200" -> "101000000000000"@0 ' + 'SET grade = 1, year = 2019 ') + self.check_resp_failed(resp) + + # make sure the edge(src, ranking, dst) must not exist + resp = self.execute_query('UPDATE EDGE ON select "200" -> "101"@123456789 ' + 'SET grade = grade + 1, year = 2019 ') + self.check_resp_failed(resp) + + + def test_upsert_then_insert(self): + resp = self.execute_query('UPSERT VERTEX ON building "100" SET name = "No1"') + self.check_resp_succeeded(resp) + + # resp = self.execute_query('FETCH PROP on building "100"') + # self.check_resp_succeeded(resp) + # expected_result = [["100", "No1"]] + # self.check_result(resp, expected_result) + + resp = self.execute('INSERT VERTEX building(name) VALUES "100": ("No2")') + self.check_resp_succeeded(resp) + + # resp = self.execute_query('FETCH PROP on building "100"') + # self.check_resp_succeeded(resp) + # expected_result = [["100", "No2"]] + # self.check_result(resp, expected_result) + + resp = self.execute_query('UPSERT VERTEX ON building "101" SET name = "No1"') + self.check_resp_succeeded(resp) + + # resp = self.execute_query('FETCH PROP on building "101"') + # self.check_resp_succeeded(resp) + # expected_result = [["101", "No1"]] + # self.check_result(resp, expected_result) + + resp = self.execute('INSERT VERTEX building(name) VALUES "101": ("No2")') + self.check_resp_succeeded(resp) + + # resp = self.execute_query('FETCH PROP on building "101"') + # self.check_resp_succeeded(resp) + # expected_result = [["101", "No2"]] + # self.check_result(resp, expected_result) + + def test_upsert_after_alter_schema(self): + resp = self.execute('INSERT VERTEX building(name) VALUES "100": ("No1")') + self.check_resp_succeeded(resp) + + resp = self.execute('ALTER TAG building ADD (new_field string default "123")') + self.check_resp_succeeded(resp) + + time.sleep(self.delay) + + resp = self.execute('UPSERT VERTEX ON building "100" SET name = "No2"') + self.check_resp_succeeded(resp) + + # resp = self.execute_query('FETCH PROP on building "100"') + # self.check_resp_succeeded(resp) + # expected_result = [["100", "No2", "123"]] + # self.check_result(resp, expected_result) + + resp = self.execute('UPSERT VERTEX ON building "100" SET name = "No3", new_field = "321"') + self.check_resp_succeeded(resp) + + # resp = self.execute_query('FETCH PROP on building "101"') + # self.check_resp_succeeded(resp) + # expected_result = [["100", "No3", "321"]] + # self.check_result(resp, expected_result) + + resp = self.execute('UPSERT VERTEX ON building "101" SET name = "No1", new_field = "No2"') + self.check_resp_succeeded(resp) + + # resp = self.execute_query('FETCH PROP on building "101"') + # self.check_resp_succeeded(resp) + # expected_result = [["100", "No1", "No2"]] + # self.check_result(resp, expected_result) + + # Test upsert edge after alter schema + resp = self.execute('INSERT EDGE like(likeness) VALUES "1" -> "100":(1.0)') + self.check_resp_succeeded(resp) + + resp = self.execute('ALTER EDGE like ADD (new_field string default "123")') + self.check_resp_succeeded(resp) + + time.sleep(self.delay) + + resp = self.execute('UPSERT EDGE ON like "1"->"100" SET likeness = 2.0') + self.check_resp_succeeded(resp) + + # resp = self.execute_query('FETCH PROP ON like "1"->"100"@0') + # self.check_resp_succeeded(resp) + # expected_result = [["1", "100", 0, 2.0, "123"]] + # self.check_result(resp, expected_result) + + resp = self.execute('UPSERT EDGE ON like "1"->"100" SET likeness = 3.0, new_field = "321"') + self.check_resp_succeeded(resp) + + # resp = self.execute_query('FETCH PROP ON like "1"->"100"@0') + # self.check_resp_succeeded(resp) + # expected_result = [["1", "100", 0, 3.0, "321"]] + # self.check_result(resp, expected_result) + + resp = self.execute('UPSERT EDGE ON like "1"->"101" SET likeness = 1.0, new_field = "111"') + self.check_resp_succeeded(resp) + + # resp = self.execute_query('FETCH PROP ON like "1"->"101"@0') + # self.check_resp_succeeded(resp) + # expected_result = [["1", "101", 0, 1.0, "111"]] + # self.check_result(resp, expected_result) +