From 23c975e0c12b8271d44bf0d4f5e5925b17ab1468 Mon Sep 17 00:00:00 2001 From: CPWstatic <13495049+CPWstatic@users.noreply.github.com> Date: Mon, 12 Aug 2019 12:40:51 +0800 Subject: [PATCH] Implementation of set op. (#696) * Implement union. * Add distincter. * Pipe have priority over set operator. * Ajust error handling. * Add distinct and implicit type casting for union. * Inplement intersect and minus. * Fix format. * Adrress @monabobo's comment. * Add result column names. * Delete distincter and rebase. * Rebase and fix conflict. * Address @whitewum's comment. * Address @laura-ding's comment and rebase. --- src/dataman/RowSetWriter.cpp | 3 + src/dataman/RowSetWriter.h | 2 + src/graph/CMakeLists.txt | 1 + src/graph/Executor.cpp | 5 + src/graph/InterimResult.cpp | 167 +++++++++++- src/graph/InterimResult.h | 21 +- src/graph/PipeExecutor.cpp | 17 +- src/graph/PipeExecutor.h | 3 + src/graph/SetExecutor.cpp | 393 ++++++++++++++++++++++++++++ src/graph/SetExecutor.h | 80 ++++++ src/graph/TraverseExecutor.cpp | 4 + src/graph/test/CMakeLists.txt | 18 ++ src/graph/test/OrderByTest.cpp | 2 - src/graph/test/SetTest.cpp | 433 +++++++++++++++++++++++++++++++ src/parser/MaintainSentences.h | 22 +- src/parser/MutateSentences.h | 4 - src/parser/TraverseSentences.cpp | 1 - src/parser/TraverseSentences.h | 22 +- src/parser/parser.yy | 40 ++- src/parser/scanner.lex | 4 +- src/parser/test/ParserTest.cpp | 26 ++ src/parser/test/ScannerTest.cpp | 2 + 22 files changed, 1234 insertions(+), 36 deletions(-) create mode 100644 src/graph/SetExecutor.cpp create mode 100644 src/graph/SetExecutor.h create mode 100644 src/graph/test/SetTest.cpp diff --git a/src/dataman/RowSetWriter.cpp b/src/dataman/RowSetWriter.cpp index 66401b515f6..a2a63bd31db 100644 --- a/src/dataman/RowSetWriter.cpp +++ b/src/dataman/RowSetWriter.cpp @@ -38,5 +38,8 @@ void RowSetWriter::addRow(const std::string& data) { data_.append(data); } +void RowSetWriter::addAll(const std::string& data) { + data_.append(data); +} } // namespace nebula diff --git a/src/dataman/RowSetWriter.h b/src/dataman/RowSetWriter.h index cba8cb6ebaf..5d987285259 100644 --- a/src/dataman/RowSetWriter.h +++ b/src/dataman/RowSetWriter.h @@ -41,6 +41,8 @@ class RowSetWriter { void addRow(RowWriter& writer); // Append the encoded row data void addRow(const std::string& data); + // Copy existed rows + void addAll(const std::string& data); private: std::shared_ptr schema_; diff --git a/src/graph/CMakeLists.txt b/src/graph/CMakeLists.txt index 6239d5ab241..6304ef9d6da 100644 --- a/src/graph/CMakeLists.txt +++ b/src/graph/CMakeLists.txt @@ -41,6 +41,7 @@ add_library( FetchVerticesExecutor.cpp FetchEdgesExecutor.cpp FetchExecutor.cpp + SetExecutor.cpp ) add_dependencies( graph_obj diff --git a/src/graph/Executor.cpp b/src/graph/Executor.cpp index 62c4deb6d6e..a30006f24c2 100644 --- a/src/graph/Executor.cpp +++ b/src/graph/Executor.cpp @@ -37,6 +37,8 @@ #include "graph/ConfigExecutor.h" #include "graph/FetchVerticesExecutor.h" #include "graph/FetchEdgesExecutor.h" +#include "graph/ConfigExecutor.h" +#include "graph/SetExecutor.h" namespace nebula { namespace graph { @@ -126,6 +128,9 @@ std::unique_ptr Executor::makeExecutor(Sentence *sentence) { case Sentence::Kind::kFetchEdges: executor = std::make_unique(sentence, ectx()); break; + case Sentence::Kind::kSet: + executor = std::make_unique(sentence, ectx()); + break; case Sentence::Kind::kUnknown: LOG(FATAL) << "Sentence kind unknown"; break; diff --git a/src/graph/InterimResult.cpp b/src/graph/InterimResult.cpp index ac31c32e136..9fe11d80825 100644 --- a/src/graph/InterimResult.cpp +++ b/src/graph/InterimResult.cpp @@ -11,6 +11,8 @@ namespace nebula { namespace graph { +constexpr char NotSupported[] = "Type not supported yet"; + InterimResult::InterimResult(std::unique_ptr rsWriter) { rsWriter_ = std::move(rsWriter); rsReader_ = std::make_unique(rsWriter_->schema(), rsWriter_->data()); @@ -119,7 +121,6 @@ std::vector InterimResult::getRows() const { return rows; } - std::unique_ptr InterimResult::buildIndex(const std::string &vidColumn) const { using nebula::cpp2::SupportedType; @@ -211,6 +212,170 @@ VariantType InterimResult::InterimResultIndex::getColumnWithVID(VertexID id, return rows_[rowIndex][columnIndex]; } +Status InterimResult::castTo(cpp2::ColumnValue *col, + const nebula::cpp2::SupportedType &type) { + using nebula::cpp2::SupportedType; + switch (type) { + case SupportedType::VID: + return castToInt(col); + case SupportedType::DOUBLE: + return castToDouble(col); + case SupportedType::BOOL: + return castToBool(col); + case SupportedType::STRING: + return castToStr(col); + default: + // Notice: if we implement some other type, + // we should update here. + LOG(ERROR) << NotSupported << static_cast(type); + return Status::Error(NotSupported); + } +} +Status InterimResult::castToInt(cpp2::ColumnValue *col) { + switch (col->getType()) { + case cpp2::ColumnValue::Type::integer: + break; + case cpp2::ColumnValue::Type::double_precision: { + auto d2i = static_cast(col->get_double_precision()); + col->set_integer(d2i); + break; + } + case cpp2::ColumnValue::Type::bool_val: { + auto b2i = static_cast(col->get_bool_val()); + col->set_integer(b2i); + break; + } + case cpp2::ColumnValue::Type::str: { + auto r = folly::tryTo(col->get_str()); + if (r.hasValue()) { + col->set_integer(r.value()); + break; + } else { + return Status::Error( + "Casting from string %s to double failed.", col->get_str().c_str()); + } + } + default: + LOG(ERROR) << NotSupported << static_cast(col->getType()); + return Status::Error(NotSupported); + } + return Status::OK(); +} + +Status InterimResult::castToDouble(cpp2::ColumnValue *col) { + switch (col->getType()) { + case cpp2::ColumnValue::Type::integer: { + auto i2d = static_cast(col->get_integer()); + col->set_double_precision(i2d); + break; + } + case cpp2::ColumnValue::Type::double_precision: + break; + case cpp2::ColumnValue::Type::bool_val: { + auto b2d = static_cast(col->get_bool_val()); + col->set_double_precision(b2d); + break; + } + case cpp2::ColumnValue::Type::str: { + auto r = folly::tryTo(col->get_str()); + if (r.hasValue()) { + col->set_double_precision(r.value()); + break; + } else { + return Status::Error( + "Casting from string %s to double failed.", col->get_str().c_str()); + } + } + default: + LOG(ERROR) << NotSupported << static_cast(col->getType()); + return Status::Error(NotSupported); + } + return Status::OK(); +} + +Status InterimResult::castToBool(cpp2::ColumnValue *col) { + switch (col->getType()) { + case cpp2::ColumnValue::Type::integer: { + auto i2b = col->get_integer() != 0; + col->set_bool_val(i2b); + break; + } + case cpp2::ColumnValue::Type::double_precision: { + auto d2b = col->get_double_precision() != 0.0; + col->set_bool_val(d2b); + break; + } + case cpp2::ColumnValue::Type::bool_val: + break; + case cpp2::ColumnValue::Type::str: { + auto s2b = col->get_str().empty(); + col->set_bool_val(s2b); + break; + } + default: + LOG(ERROR) << NotSupported << static_cast(col->getType()); + return Status::Error(NotSupported); + } + return Status::OK(); +} + +Status InterimResult::castToStr(cpp2::ColumnValue *col) { + switch (col->getType()) { + case cpp2::ColumnValue::Type::integer: { + auto i2s = folly::to(col->get_integer()); + col->set_str(std::move(i2s)); + break; + } + case cpp2::ColumnValue::Type::double_precision: { + auto d2s = folly::to(col->get_double_precision()); + col->set_str(std::move(d2s)); + break; + } + case cpp2::ColumnValue::Type::bool_val: { + auto b2s = folly::to(col->get_bool_val()); + col->set_str(std::move(b2s)); + break; + } + case cpp2::ColumnValue::Type::str: + break; + default: + LOG(ERROR) << NotSupported << static_cast(col->getType()); + return Status::Error(NotSupported); + } + return Status::OK(); +} + +std::unique_ptr InterimResult::getInterim( + std::shared_ptr resultSchema, + std::vector &rows) { + auto rsWriter = std::make_unique(resultSchema); + for (auto &r : rows) { + RowWriter writer(resultSchema); + auto &cols = r.get_columns(); + for (auto &col : cols) { + switch (col.getType()) { + case cpp2::ColumnValue::Type::integer: + writer << col.get_integer(); + break; + case cpp2::ColumnValue::Type::double_precision: + writer << col.get_double_precision(); + break; + case cpp2::ColumnValue::Type::bool_val: + writer << col.get_bool_val(); + break; + case cpp2::ColumnValue::Type::str: + writer << col.get_str(); + break; + default: + LOG(ERROR) << NotSupported << static_cast(col.getType()); + return nullptr; + } + } + rsWriter->addRow(writer); + } + + return std::make_unique(std::move(rsWriter)); +} } // namespace graph } // namespace nebula diff --git a/src/graph/InterimResult.h b/src/graph/InterimResult.h index 7a0e065cba2..ca455c6fba8 100644 --- a/src/graph/InterimResult.h +++ b/src/graph/InterimResult.h @@ -14,14 +14,11 @@ #include "dataman/RowSetWriter.h" #include "dataman/SchemaWriter.h" +namespace nebula { +namespace graph { /** * The intermediate form of execution result, used in pipeline and variable. */ - - -namespace nebula { -namespace graph { - class InterimResult final { public: InterimResult() = default; @@ -34,10 +31,24 @@ class InterimResult final { explicit InterimResult(std::unique_ptr rsWriter); explicit InterimResult(std::vector vids); + static std::unique_ptr getInterim( + std::shared_ptr resultSchema, + std::vector &rows); + static Status castTo(cpp2::ColumnValue *col, + const nebula::cpp2::SupportedType &type); + static Status castToInt(cpp2::ColumnValue *col); + static Status castToDouble(cpp2::ColumnValue *col); + static Status castToBool(cpp2::ColumnValue *col); + static Status castToStr(cpp2::ColumnValue *col); + std::shared_ptr schema() const { return rsReader_->schema(); } + std::string& data() const { + return rsWriter_->data(); + } + StatusOr> getVIDs(const std::string &col) const; StatusOr> getDistinctVIDs(const std::string &col) const; diff --git a/src/graph/PipeExecutor.cpp b/src/graph/PipeExecutor.cpp index cf77d0d7202..f8ae004a2ab 100644 --- a/src/graph/PipeExecutor.cpp +++ b/src/graph/PipeExecutor.cpp @@ -17,6 +17,11 @@ PipeExecutor::PipeExecutor(Sentence *sentence, Status PipeExecutor::prepare() { + auto status = syntaxPreCheck(); + if (!status.ok()) { + return status; + } + left_ = makeTraverseExecutor(sentence_->left()); right_ = makeTraverseExecutor(sentence_->right()); DCHECK(left_ != nullptr); @@ -69,7 +74,7 @@ Status PipeExecutor::prepare() { right_->setOnError(onError); } - auto status = left_->prepare(); + status = left_->prepare(); if (!status.ok()) { FLOG_ERROR("Prepare executor `%s' failed: %s", left_->name(), status.toString().c_str()); @@ -87,6 +92,16 @@ Status PipeExecutor::prepare() { return Status::OK(); } +Status PipeExecutor::syntaxPreCheck() { + // Set op not support input, + // because '$-' would be ambiguous in such a situation: + // Go | (Go | Go $- UNION GO) + if (sentence_->right()->kind() == Sentence::Kind::kSet) { + return Status::SyntaxError("Set op not support input."); + } + + return Status::OK(); +} void PipeExecutor::execute() { left_->execute(); diff --git a/src/graph/PipeExecutor.h b/src/graph/PipeExecutor.h index 505ce9a5184..2053437ecbd 100644 --- a/src/graph/PipeExecutor.h +++ b/src/graph/PipeExecutor.h @@ -29,6 +29,9 @@ class PipeExecutor final : public TraverseExecutor { void setupResponse(cpp2::ExecutionResponse &resp) override; +private: + Status syntaxPreCheck(); + private: PipedSentence *sentence_{nullptr}; std::unique_ptr left_; diff --git a/src/graph/SetExecutor.cpp b/src/graph/SetExecutor.cpp new file mode 100644 index 00000000000..9c654314882 --- /dev/null +++ b/src/graph/SetExecutor.cpp @@ -0,0 +1,393 @@ +/* Copyright (c) 2019 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 "base/Base.h" +#include "graph/SetExecutor.h" + +namespace nebula { +namespace graph { +namespace cpp2 { +bool RowValue::operator<(const RowValue& rhs) const { + auto &lhs = *this; + return (lhs.columns < rhs.columns); +} +} + +SetExecutor::SetExecutor(Sentence *sentence, ExecutionContext *ectx) + : TraverseExecutor(ectx) { + sentence_ = static_cast(sentence); +} + +Status SetExecutor::prepare() { + left_ = makeTraverseExecutor(sentence_->left()); + right_ = makeTraverseExecutor(sentence_->right()); + DCHECK(left_ != nullptr); + DCHECK(right_ != nullptr); + + setLeft(); + setRight(); + + auto status = left_->prepare(); + if (!status.ok()) { + FLOG_ERROR("Prepare executor `%s' failed: %s", + left_->name(), status.toString().c_str()); + return status; + } + + status = right_->prepare(); + if (!status.ok()) { + FLOG_ERROR("Prepare executor `%s' failed: %s", + right_->name(), status.toString().c_str()); + return status; + } + + return Status::OK(); +} + +void SetExecutor::setLeft() { + auto onFinish = [] () { + return; + }; + + futures_.emplace_back(leftP_.getFuture()); + auto onResult = [this] (std::unique_ptr result) { + this->leftResult_ = std::move(result); + VLOG(3) << "Left result set."; + leftP_.setValue(); + }; + + auto onError = [this] (Status s) { + VLOG(3) << "Left error:" << s.toString(); + leftS_ = std::move(s); + leftP_.setValue(); + }; + + left_->setOnResult(onResult); + left_->setOnFinish(onFinish); + left_->setOnError(onError); +} + +void SetExecutor::setRight() { + auto onFinish = [] () { + return; + }; + + futures_.emplace_back(rightP_.getFuture()); + auto onResult = [this] (std::unique_ptr result) { + this->rightResult_ = std::move(result); + VLOG(3) << "Right result set."; + rightP_.setValue(); + }; + + auto onError = [this] (Status s) { + VLOG(3) << "Right error: " << s.toString(); + rightS_ = std::move(s); + rightP_.setValue(); + }; + + right_->setOnResult(onResult); + right_->setOnFinish(onFinish); + right_->setOnError(onError); +} + +void SetExecutor::execute() { + auto status = checkIfGraphSpaceChosen(); + if (!status.ok()) { + onError_(std::move(status)); + return; + } + + auto *runner = ectx()->rctx()->runner(); + runner->add([this] () mutable { left_->execute(); }); + runner->add([this] () mutable { right_->execute(); }); + + auto cb = [this] (auto &&result) { + UNUSED(result); + if (!leftS_.ok() || !rightS_.ok()) { + std::string msg; + msg += "left: "; + msg += leftS_.toString(); + msg += " right: "; + msg += rightS_.toString(); + onError_(Status::Error(msg)); + return; + } + + if (leftResult_ == nullptr && rightResult_ == nullptr) { + VLOG(3) << "Set op no input."; + onEmptyInputs(); + return; + } + + // Set op share the same priority, they would execute sequentially. + switch (sentence_->op()) { + case SetSentence::Operator::UNION: + doUnion(); + break; + case SetSentence::Operator::INTERSECT: + doIntersect(); + break; + case SetSentence::Operator::MINUS: + doMinus(); + break; + default: + LOG(FATAL) << "Unknown operator: " << sentence_->op(); + } + }; + folly::collectAll(futures_).via(runner).thenValue(cb); +} + +void SetExecutor::doUnion() { + VLOG(3) << "Do Union."; + if (leftResult_ == nullptr) { + VLOG(3) << "Union has right result."; + getResultCols(rightResult_); + finishExecution(std::move(rightResult_)); + return; + } + + if (rightResult_ == nullptr) { + VLOG(3) << "Union has left result."; + getResultCols(leftResult_); + finishExecution(std::move(leftResult_)); + return; + } + + Status status = checkSchema(); + if (!status.ok()) { + DCHECK(onError_); + onError_(status); + return; + } + + auto leftRows = leftResult_->getRows(); + auto rightRows = rightResult_->getRows(); + if (!castingMap_.empty()) { + auto stat = doCasting(rightRows); + if (!stat.ok()) { + DCHECK(onError_); + onError_(status); + return; + } + } + + leftRows.insert(leftRows.end(), + std::make_move_iterator(rightRows.begin()), + std::make_move_iterator(rightRows.end())); + if (sentence_->distinct()) { + doDistinct(leftRows); + } + + finishExecution(std::move(leftRows)); + return; +} + +Status SetExecutor::checkSchema() { + auto leftSchema = leftResult_->schema(); + auto rightSchema = rightResult_->schema(); + auto leftIter = leftSchema->begin(); + auto rightIter = rightSchema->begin(); + + auto index = 0u; + while (leftIter && rightIter) { + auto *colName = leftIter->getName(); + if (leftIter->getType() != rightIter->getType()) { + // Implicit type casting would happen if the type do no match. + // If type casting failed. the whole statement would fail. + castingMap_.emplace_back(index, leftIter->getType()); + } + + colNames_.emplace_back(colName); + + ++index; + ++leftIter; + ++rightIter; + } + + if (leftIter || rightIter) { + // If the column count not match, we will not do set op. + return Status::Error("Field count not match."); + } + + resultSchema_ = std::move(leftSchema); + + return Status::OK(); +} + +Status SetExecutor::doCasting(std::vector &rows) const { + for (auto &row : rows) { + auto cols = row.get_columns(); + for (auto &pair : castingMap_) { + auto stat = + InterimResult::castTo(&cols[pair.first], pair.second.get_type()); + if (!stat.ok()) { + return stat; + } + } + row.set_columns(std::move(cols)); + } + + return Status::OK(); +} + + +void SetExecutor::doDistinct(std::vector &rows) const { + std::sort(rows.begin(), rows.end()); + auto it = std::unique(rows.begin(), rows.end()); + rows.erase(it, rows.end()); +} + +void SetExecutor::doIntersect() { + VLOG(3) << "Do InterSect."; + if (leftResult_ == nullptr || rightResult_ == nullptr) { + VLOG(3) << "No intersect."; + onEmptyInputs(); + return; + } + + Status status = checkSchema(); + if (!status.ok()) { + DCHECK(onError_); + onError_(status); + return; + } + + auto leftRows = leftResult_->getRows(); + auto rightRows = rightResult_->getRows(); + if (!castingMap_.empty()) { + Status stat = doCasting(rightRows); + if (!stat.ok()) { + DCHECK(onError_); + onError_(status); + return; + } + } + + std::vector rows; + for (auto &lr : leftRows) { + for (auto &rr : rightRows) { + if (rr == lr) { + rows.emplace_back(std::move(rr)); + break; + } + } + } + + finishExecution(std::move(rows)); + return; +} + +void SetExecutor::doMinus() { + VLOG(3) << "Do Minus."; + if (leftResult_ == nullptr) { + VLOG(3) << "Minus has only right result."; + onEmptyInputs(); + return; + } + + if (rightResult_ == nullptr) { + VLOG(3) << "Minus has left result."; + getResultCols(leftResult_); + finishExecution(std::move(leftResult_)); + return; + } + + Status status = checkSchema(); + if (!status.ok()) { + DCHECK(onError_); + onError_(status); + return; + } + + auto leftRows = leftResult_->getRows(); + auto rightRows = rightResult_->getRows(); + if (!castingMap_.empty()) { + Status stat = doCasting(rightRows); + if (!stat.ok()) { + DCHECK(onError_); + onError_(status); + return; + } + } + + for (auto &rr : rightRows) { + for (auto iter = leftRows.begin(); iter < leftRows.end();) { + if (rr == *iter) { + iter = leftRows.erase(iter); + } else { + ++iter; + } + } + } + + finishExecution(std::move(leftRows)); + return; +} + +void SetExecutor::getResultCols(std::unique_ptr &result) { + auto schema = result->schema(); + auto iter = schema->begin(); + while (iter) { + auto *colName = iter->getName(); + colNames_.emplace_back(colName); + ++iter; + } +} + +void SetExecutor::onEmptyInputs() { + if (onResult_) { + onResult_(nullptr); + } else if (resp_ == nullptr) { + resp_ = std::make_unique(); + } + DCHECK(onFinish_); + onFinish_(); +} + +void SetExecutor::finishExecution(std::unique_ptr result) { + if (onResult_) { + onResult_(std::move(result)); + } else { + resp_ = std::make_unique(); + resp_->set_column_names(std::move(colNames_)); + if (result != nullptr) { + auto rows = result->getRows(); + resp_->set_rows(std::move(rows)); + } + } + + DCHECK(onFinish_); + onFinish_(); +} + +void SetExecutor::finishExecution(std::vector rows) { + if (onResult_) { + auto outputs = InterimResult::getInterim(resultSchema_, rows); + onResult_(std::move(outputs)); + } else { + resp_ = std::make_unique(); + resp_->set_column_names(std::move(colNames_)); + resp_->set_rows(std::move(rows)); + } + DCHECK(onFinish_); + onFinish_(); +} + +void SetExecutor::feedResult(std::unique_ptr result) { + // Feed input for set operator is an act of reservation. + UNUSED(result); + LOG(FATAL) << "Set operation not support input yet."; +} + +void SetExecutor::setupResponse(cpp2::ExecutionResponse &resp) { + if (resp_ == nullptr) { + return; + } + + resp = std::move(*resp_); +} +} // namespace graph +} // namespace nebula diff --git a/src/graph/SetExecutor.h b/src/graph/SetExecutor.h new file mode 100644 index 00000000000..10159fef4b2 --- /dev/null +++ b/src/graph/SetExecutor.h @@ -0,0 +1,80 @@ +/* Copyright (c) 2019 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 GRAPH_SETEXECUTOR_H_ +#define GRAPH_SETEXECUTOR_H_ + +#include "base/Base.h" +#include "graph/TraverseExecutor.h" +#include "meta/SchemaProviderIf.h" +#include + +namespace nebula { +namespace graph { + +class SetExecutor final : public TraverseExecutor { +public: + SetExecutor(Sentence *sentence, ExecutionContext *ectx); + + const char* name() const override { + return "SetExecutor"; + } + + Status MUST_USE_RESULT prepare() override; + + void execute() override; + + void feedResult(std::unique_ptr result) override; + + void setupResponse(cpp2::ExecutionResponse &resp) override; + +private: + void setLeft(); + + void setRight(); + + void finishExecution(std::unique_ptr result); + + void finishExecution(std::vector leftRows); + + void doUnion(); + + void doIntersect(); + + void doMinus(); + + Status checkSchema(); + + void getResultCols(std::unique_ptr &result); + + Status doCasting(std::vector &rows) const; + + void doDistinct(std::vector &rows) const; + + void onEmptyInputs(); + +private: + SetSentence *sentence_{nullptr}; + std::unique_ptr left_; + std::unique_ptr right_; + std::unique_ptr leftResult_; + std::unique_ptr rightResult_; + folly::Promise leftP_; + folly::Promise rightP_; + std::vector> futures_; + Status leftS_; + Status rightS_; + std::vector> castingMap_; + std::vector colNames_; + std::shared_ptr resultSchema_; + std::unique_ptr resp_; +}; + +} // namespace graph +} // namespace nebula + + +#endif // GRAPH_SETEXECUTOR_H_ diff --git a/src/graph/TraverseExecutor.cpp b/src/graph/TraverseExecutor.cpp index c882745c429..9d4680fb289 100644 --- a/src/graph/TraverseExecutor.cpp +++ b/src/graph/TraverseExecutor.cpp @@ -14,6 +14,7 @@ #include "graph/FetchEdgesExecutor.h" #include "dataman/RowReader.h" #include "dataman/RowWriter.h" +#include "graph/SetExecutor.h" namespace nebula { namespace graph { @@ -44,6 +45,9 @@ TraverseExecutor::makeTraverseExecutor(Sentence *sentence, ExecutionContext *ect case Sentence::Kind::kFetchEdges: executor = std::make_unique(sentence, ectx); break; + case Sentence::Kind::kSet: + executor = std::make_unique(sentence, ectx); + break; case Sentence::Kind::kUnknown: LOG(FATAL) << "Sentence kind unknown"; break; diff --git a/src/graph/test/CMakeLists.txt b/src/graph/test/CMakeLists.txt index dadda134d9b..d8e70e7e06b 100644 --- a/src/graph/test/CMakeLists.txt +++ b/src/graph/test/CMakeLists.txt @@ -186,3 +186,21 @@ nebula_add_test( wangle gtest ) + +nebula_add_test( + NAME + set_test + SOURCES + SetTest.cpp + OBJECTS + $ + $ + $ + $ + ${GRAPH_TEST_LIBS} + LIBRARIES + ${THRIFT_LIBRARIES} + ${ROCKSDB_LIBRARIES} + wangle + gtest +) diff --git a/src/graph/test/OrderByTest.cpp b/src/graph/test/OrderByTest.cpp index b01e9517d82..c0828bf57c9 100644 --- a/src/graph/test/OrderByTest.cpp +++ b/src/graph/test/OrderByTest.cpp @@ -10,8 +10,6 @@ #include "graph/test/TraverseTestBase.h" #include "meta/test/TestUtils.h" -DECLARE_int32(load_data_interval_secs); - namespace nebula { namespace graph { diff --git a/src/graph/test/SetTest.cpp b/src/graph/test/SetTest.cpp new file mode 100644 index 00000000000..88519907a66 --- /dev/null +++ b/src/graph/test/SetTest.cpp @@ -0,0 +1,433 @@ +/* Copyright (c) 2019 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 "base/Base.h" +#include "graph/test/TestEnv.h" +#include "graph/test/TestBase.h" +#include "graph/test/TraverseTestBase.h" +#include "meta/test/TestUtils.h" + +namespace nebula { +namespace graph { + +class SetTest : public TraverseTestBase { +protected: + void SetUp() override { + TraverseTestBase::SetUp(); + // ... + } + + void TearDown() override { + // ... + TraverseTestBase::TearDown(); + } +}; + +TEST_F(SetTest, UnionAllTest) { + { + cpp2::ExecutionResponse resp; + auto *fmt = "GO FROM %ld OVER serve YIELD $^.player.name, serve.start_year, $$.team.name" + " UNION ALL " + "GO FROM %ld OVER serve YIELD $^.player.name, serve.start_year, $$.team.name"; + auto &tim = players_["Tim Duncan"]; + auto &tony = players_["Tony Parker"]; + auto query = folly::stringPrintf(fmt, tim.vid(), tony.vid()); + auto code = client_->execute(query, resp); + ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code); + std::vector> expected; + for (auto &serve : tim.serves()) { + std::tuple record( + tim.name(), std::get<1>(serve), std::get<0>(serve)); + expected.emplace_back(std::move(record)); + } + for (auto &serve : tony.serves()) { + std::tuple record( + tony.name(), std::get<1>(serve), std::get<0>(serve)); + expected.emplace_back(std::move(record)); + } + ASSERT_TRUE(verifyResult(resp, expected)); + } + { + cpp2::ExecutionResponse resp; + auto *fmt = "GO FROM %ld OVER serve YIELD $^.player.name, serve.start_year, $$.team.name" + " UNION ALL " + "GO FROM %ld OVER serve YIELD $^.player.name, serve.start_year, $$.team.name" + " UNION ALL " + "GO FROM %ld OVER serve YIELD $^.player.name, serve.start_year, $$.team.name"; + auto &tim = players_["Tim Duncan"]; + auto &tony = players_["Tony Parker"]; + auto &manu = players_["Manu Ginobili"]; + auto query = folly::stringPrintf(fmt, tim.vid(), tony.vid(), manu.vid()); + auto code = client_->execute(query, resp); + ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code); + std::vector> expected; + for (auto &serve : tim.serves()) { + std::tuple record( + tim.name(), std::get<1>(serve), std::get<0>(serve)); + expected.emplace_back(std::move(record)); + } + for (auto &serve : tony.serves()) { + std::tuple record( + tony.name(), std::get<1>(serve), std::get<0>(serve)); + expected.emplace_back(std::move(record)); + } + for (auto &serve : manu.serves()) { + std::tuple record( + manu.name(), std::get<1>(serve), std::get<0>(serve)); + expected.emplace_back(std::move(record)); + } + ASSERT_TRUE(verifyResult(resp, expected)); + } + { + cpp2::ExecutionResponse resp; + auto *fmt = "(GO FROM %ld OVER like | " + "GO FROM $- OVER serve YIELD $^.player.name, serve.start_year, $$.team.name)" + " UNION ALL " + "GO FROM %ld OVER serve YIELD $^.player.name, serve.start_year, $$.team.name"; + auto &tim = players_["Tim Duncan"]; + auto &tony = players_["Tony Parker"]; + auto query = folly::stringPrintf(fmt, tim.vid(), tony.vid()); + auto code = client_->execute(query, resp); + ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code); + std::vector> expected; + for (auto &like : tim.likes()) { + auto &player = players_[std::get<0>(like)]; + for (auto &serve : player.serves()) { + std::tuple record( + player.name(), std::get<1>(serve), std::get<0>(serve)); + expected.emplace_back(std::move(record)); + } + } + for (auto &serve : tony.serves()) { + std::tuple record( + tony.name(), std::get<1>(serve), std::get<0>(serve)); + expected.emplace_back(std::move(record)); + } + ASSERT_TRUE(verifyResult(resp, expected)); + } + { + cpp2::ExecutionResponse resp; + auto *fmt = "GO FROM %ld OVER like | " + "GO FROM $- OVER serve YIELD $^.player.name, serve.start_year, $$.team.name" + " UNION ALL " + "GO FROM %ld OVER serve YIELD $^.player.name, serve.start_year, $$.team.name"; + auto &tim = players_["Tim Duncan"]; + auto &tony = players_["Tony Parker"]; + auto query = folly::stringPrintf(fmt, tim.vid(), tony.vid()); + auto code = client_->execute(query, resp); + ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code); + std::vector> expected; + for (auto &like : tim.likes()) { + auto &player = players_[std::get<0>(like)]; + for (auto &serve : player.serves()) { + std::tuple record( + player.name(), std::get<1>(serve), std::get<0>(serve)); + expected.emplace_back(std::move(record)); + } + } + for (auto &serve : tony.serves()) { + std::tuple record( + tony.name(), std::get<1>(serve), std::get<0>(serve)); + expected.emplace_back(std::move(record)); + } + ASSERT_TRUE(verifyResult(resp, expected)); + } + { + cpp2::ExecutionResponse resp; + auto *fmt = "GO FROM %ld OVER serve YIELD $^.player.name, serve.start_year, $$.team.name" + " UNION ALL " + "(GO FROM %ld OVER like | " + "GO FROM $- OVER serve YIELD $^.player.name, serve.start_year, $$.team.name)"; + auto &tim = players_["Tim Duncan"]; + auto &tony = players_["Tony Parker"]; + auto query = folly::stringPrintf(fmt, tony.vid(), tim.vid()); + auto code = client_->execute(query, resp); + ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code); + std::vector> expected; + for (auto &like : tim.likes()) { + auto &player = players_[std::get<0>(like)]; + for (auto &serve : player.serves()) { + std::tuple record( + player.name(), std::get<1>(serve), std::get<0>(serve)); + expected.emplace_back(std::move(record)); + } + } + for (auto &serve : tony.serves()) { + std::tuple record( + tony.name(), std::get<1>(serve), std::get<0>(serve)); + expected.emplace_back(std::move(record)); + } + ASSERT_TRUE(verifyResult(resp, expected)); + } + { + cpp2::ExecutionResponse resp; + auto *fmt = "GO FROM %ld OVER serve YIELD $^.player.name, serve.start_year, $$.team.name" + " UNION ALL " + "GO FROM %ld OVER like | " + "GO FROM $- OVER serve YIELD $^.player.name, serve.start_year, $$.team.name"; + auto &tim = players_["Tim Duncan"]; + auto &tony = players_["Tony Parker"]; + auto query = folly::stringPrintf(fmt, tony.vid(), tim.vid()); + auto code = client_->execute(query, resp); + ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code); + std::vector> expected; + for (auto &like : tim.likes()) { + auto &player = players_[std::get<0>(like)]; + for (auto &serve : player.serves()) { + std::tuple record( + player.name(), std::get<1>(serve), std::get<0>(serve)); + expected.emplace_back(std::move(record)); + } + } + for (auto &serve : tony.serves()) { + std::tuple record( + tony.name(), std::get<1>(serve), std::get<0>(serve)); + expected.emplace_back(std::move(record)); + } + ASSERT_TRUE(verifyResult(resp, expected)); + } + { + cpp2::ExecutionResponse resp; + auto *fmt = "(GO FROM %ld OVER like UNION ALL GO FROM %ld OVER like)" + " | GO FROM $- OVER serve" + " YIELD $^.player.name, serve.start_year, $$.team.name"; + auto &tim = players_["Tim Duncan"]; + auto &tony = players_["Tony Parker"]; + auto query = folly::stringPrintf(fmt, tony.vid(), tim.vid()); + auto code = client_->execute(query, resp); + ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code); + std::vector> expected; + for (auto &like : tim.likes()) { + auto &player = players_[std::get<0>(like)]; + for (auto &serve : player.serves()) { + std::tuple record( + player.name(), std::get<1>(serve), std::get<0>(serve)); + expected.emplace_back(std::move(record)); + } + } + for (auto &like : tony.likes()) { + auto &player = players_[std::get<0>(like)]; + for (auto &serve : player.serves()) { + std::tuple record( + player.name(), std::get<1>(serve), std::get<0>(serve)); + expected.emplace_back(std::move(record)); + } + } + } + { + cpp2::ExecutionResponse resp; + // Although the corresponding column name and type not match, + // we still do the union via implicit type casting for the query . + auto *fmt = "GO FROM %ld OVER serve YIELD $^.player.name as name, $$.team.name as player" + " UNION ALL " + "GO FROM %ld OVER serve " + "YIELD $^.player.name as name, serve.start_year"; + auto &tim = players_["Tim Duncan"]; + auto &tony = players_["Tony Parker"]; + auto query = folly::stringPrintf(fmt, tim.vid(), tony.vid()); + auto code = client_->execute(query, resp); + ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code); + std::vector> expected; + for (auto &serve : tim.serves()) { + std::tuple record( + tim.name(), std::get<0>(serve)); + expected.emplace_back(std::move(record)); + } + for (auto &serve : tony.serves()) { + std::tuple record( + tony.name(), + folly::to(std::get<1>(serve))); + expected.emplace_back(std::move(record)); + } + } +} + +TEST_F(SetTest, UnionDistinct) { + { + cpp2::ExecutionResponse resp; + auto *fmt = "(GO FROM %ld OVER like | " + "GO FROM $- OVER serve YIELD $^.player.name, serve.start_year, $$.team.name)" + " UNION " + "GO FROM %ld OVER serve YIELD $^.player.name, serve.start_year, $$.team.name" + " UNION " + "GO FROM %ld OVER serve YIELD $^.player.name, serve.start_year, $$.team.name"; + auto &tim = players_["Tim Duncan"]; + auto &tony = players_["Tony Parker"]; + auto &manu = players_["Manu Ginobili"]; + auto query = folly::stringPrintf(fmt, tim.vid(), tony.vid(), manu.vid()); + auto code = client_->execute(query, resp); + ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code); + std::vector> expected; + for (auto &like : tim.likes()) { + auto &player = players_[std::get<0>(like)]; + for (auto &serve : player.serves()) { + std::tuple record( + player.name(), std::get<1>(serve), std::get<0>(serve)); + expected.emplace_back(std::move(record)); + } + } + ASSERT_TRUE(verifyResult(resp, expected)); + } + { + cpp2::ExecutionResponse resp; + auto *fmt = "(GO FROM %ld OVER like | " + "GO FROM $- OVER serve YIELD $^.player.name, serve.start_year, $$.team.name)" + " UNION DISTINCT " + "GO FROM %ld OVER serve YIELD $^.player.name, serve.start_year, $$.team.name"; + auto &tim = players_["Tim Duncan"]; + auto &tony = players_["Tony Parker"]; + auto query = folly::stringPrintf(fmt, tim.vid(), tony.vid()); + auto code = client_->execute(query, resp); + ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code); + std::vector> expected; + for (auto &like : tim.likes()) { + auto &player = players_[std::get<0>(like)]; + for (auto &serve : player.serves()) { + std::tuple record( + player.name(), std::get<1>(serve), std::get<0>(serve)); + expected.emplace_back(std::move(record)); + } + } + ASSERT_TRUE(verifyResult(resp, expected)); + } +} + +TEST_F(SetTest, Minus) { + { + cpp2::ExecutionResponse resp; + auto *fmt = "(GO FROM %ld OVER like | " + "GO FROM $- OVER serve YIELD $^.player.name, serve.start_year, $$.team.name)" + " MINUS " + "GO FROM %ld OVER serve YIELD $^.player.name, serve.start_year, $$.team.name"; + auto &tim = players_["Tim Duncan"]; + auto &tony = players_["Tony Parker"]; + auto query = folly::stringPrintf(fmt, tim.vid(), tony.vid()); + auto code = client_->execute(query, resp); + ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code); + std::vector> expected; + for (auto &like : tim.likes()) { + auto &player = players_[std::get<0>(like)]; + if (player.name() == tony.name()) { + continue; + } + for (auto &serve : player.serves()) { + std::tuple record( + player.name(), std::get<1>(serve), std::get<0>(serve)); + expected.emplace_back(std::move(record)); + } + } + ASSERT_TRUE(verifyResult(resp, expected)); + } +} + +TEST_F(SetTest, Intersect) { + { + cpp2::ExecutionResponse resp; + auto *fmt = "(GO FROM %ld OVER like | " + "GO FROM $- OVER serve YIELD $^.player.name, serve.start_year, $$.team.name)" + " INTERSECT " + "GO FROM %ld OVER serve YIELD $^.player.name, serve.start_year, $$.team.name"; + auto &tim = players_["Tim Duncan"]; + auto &tony = players_["Tony Parker"]; + auto query = folly::stringPrintf(fmt, tim.vid(), tony.vid()); + auto code = client_->execute(query, resp); + ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code); + std::vector> expected; + for (auto &serve : tony.serves()) { + std::tuple record( + tony.name(), std::get<1>(serve), std::get<0>(serve)); + expected.emplace_back(std::move(record)); + } + ASSERT_TRUE(verifyResult(resp, expected)); + } +} + +TEST_F(SetTest, Mix) { + { + cpp2::ExecutionResponse resp; + auto *fmt = "(GO FROM %ld OVER like | " + "GO FROM $- OVER serve YIELD $^.player.name, serve.start_year, $$.team.name)" + " MINUS " + "GO FROM %ld OVER serve YIELD $^.player.name, serve.start_year, $$.team.name" + " UNION " + "GO FROM %ld OVER serve YIELD $^.player.name, serve.start_year, $$.team.name" + " INTERSECT " + "GO FROM %ld OVER serve YIELD $^.player.name, serve.start_year, $$.team.name"; + auto &tim = players_["Tim Duncan"]; + auto &tony = players_["Tony Parker"]; + auto &manu = players_["Manu Ginobili"]; + auto query = folly::stringPrintf(fmt, tim.vid(), tony.vid(), tim.vid(), manu.vid()); + auto code = client_->execute(query, resp); + ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code); + std::vector> expected; + for (auto &serve : manu.serves()) { + std::tuple record( + manu.name(), std::get<1>(serve), std::get<0>(serve)); + expected.emplace_back(std::move(record)); + } + ASSERT_TRUE(verifyResult(resp, expected)); + } +} + +TEST_F(SetTest, NoInput) { + { + cpp2::ExecutionResponse resp; + auto *fmt = "GO FROM %ld OVER serve YIELD serve.start_year, $$.team.name" + " UNION " + "GO FROM %ld OVER serve YIELD serve.start_year, $$.team.name" + " MINUS " + "GO FROM %ld OVER serve YIELD serve.start_year, $$.team.name" + " INTERSECT " + "GO FROM %ld OVER serve YIELD serve.start_year, $$.team.name"; + auto &nobody = players_["Nobody"]; + auto query = folly::stringPrintf( + fmt, nobody.vid(), nobody.vid(), nobody.vid(), nobody.vid()); + auto code = client_->execute(query, resp); + ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code); + ASSERT_EQ(nullptr, resp.get_rows()); + } +} + +TEST_F(SetTest, SyntaxError) { + { + cpp2::ExecutionResponse resp; + // This is an act of reservation. + // For now, we treat it as an syntax error. + auto query = "GO FROM 123 OVER like" + " YIELD like._src as src, like._dst as dst" + " | (GO FROM $-.src OVER serve" + " UNION GO FROM $-.dst OVER serve)"; + auto code = client_->execute(query, resp); + ASSERT_EQ(cpp2::ErrorCode::E_SYNTAX_ERROR, code); + } +} + +TEST_F(SetTest, ExecutionError) { + { + cpp2::ExecutionResponse resp; + auto *fmt = "GO FROM %ld OVER serve YIELD $^.player.name, serve.start_year, $$.team.name" + " UNION " + "GO FROM %ld OVER serve YIELD $^.player.name1, serve.start_year, $$.team.name"; + auto &tim = players_["Tim Duncan"]; + auto &tony = players_["Tony Parker"]; + auto query = folly::stringPrintf(fmt, tim.vid(), tony.vid()); + auto code = client_->execute(query, resp); + ASSERT_EQ(cpp2::ErrorCode::E_EXECUTION_ERROR, code); + } + { + cpp2::ExecutionResponse resp; + auto *fmt = "GO FROM %ld OVER serve YIELD $^.player.name, serve.start_year" + " UNION " + "GO FROM %ld OVER serve YIELD $^.player.name, serve.start_year, $$.team.name"; + auto &tim = players_["Tim Duncan"]; + auto &tony = players_["Tony Parker"]; + auto query = folly::stringPrintf(fmt, tim.vid(), tony.vid()); + auto code = client_->execute(query, resp); + ASSERT_EQ(cpp2::ErrorCode::E_EXECUTION_ERROR, code); + } +} +} // namespace graph +} // namespace nebula diff --git a/src/parser/MaintainSentences.h b/src/parser/MaintainSentences.h index 921a61a8478..40fc7acaf9d 100644 --- a/src/parser/MaintainSentences.h +++ b/src/parser/MaintainSentences.h @@ -448,20 +448,20 @@ class DropEdgeSentence final : public Sentence { class YieldSentence final : public Sentence { - public: - explicit YieldSentence(YieldColumns *fields) { - yieldColumns_.reset(fields); - kind_ = Kind::kYield; - } +public: + explicit YieldSentence(YieldColumns *fields) { + yieldColumns_.reset(fields); + kind_ = Kind::kYield; + } - std::vector columns() const { - return yieldColumns_->columns(); - } + std::vector columns() const { + return yieldColumns_->columns(); + } - std::string toString() const override; + std::string toString() const override; - private: - std::unique_ptr yieldColumns_; +private: + std::unique_ptr yieldColumns_; }; } // namespace nebula diff --git a/src/parser/MutateSentences.h b/src/parser/MutateSentences.h index a6f4653a812..0e75912687e 100644 --- a/src/parser/MutateSentences.h +++ b/src/parser/MutateSentences.h @@ -544,7 +544,6 @@ class DownloadSentence final : public Sentence { std::unique_ptr path_; }; - class IngestSentence final : public Sentence { public: IngestSentence() { @@ -553,8 +552,5 @@ class IngestSentence final : public Sentence { std::string toString() const override; }; - - } // namespace nebula - #endif // PARSER_MUTATESENTENCES_H_ diff --git a/src/parser/TraverseSentences.cpp b/src/parser/TraverseSentences.cpp index df105f72c23..248727ca4fc 100644 --- a/src/parser/TraverseSentences.cpp +++ b/src/parser/TraverseSentences.cpp @@ -189,7 +189,6 @@ std::string* EdgeKeyRef::dstid() { } } - std::string* EdgeKeyRef::rank() { if (rank_ == nullptr) { return nullptr; diff --git a/src/parser/TraverseSentences.h b/src/parser/TraverseSentences.h index 7611cf03a08..abf242d9282 100644 --- a/src/parser/TraverseSentences.h +++ b/src/parser/TraverseSentences.h @@ -146,10 +146,31 @@ class SetSentence final : public Sentence { std::string toString() const override; + auto left() { + return left_.get(); + } + + auto right() { + return right_.get(); + } + + auto op() { + return op_; + } + + void setDistinct() { + distinct_ = true; + } + + auto distinct() { + return distinct_; + } + private: Operator op_; std::unique_ptr left_; std::unique_ptr right_; + bool distinct_{false}; }; @@ -463,5 +484,4 @@ class FetchEdgesSentence final : public Sentence { std::unique_ptr yieldClause_; }; } // namespace nebula - #endif // PARSER_TRAVERSESENTENCES_H_ diff --git a/src/parser/parser.yy b/src/parser/parser.yy index dd86e3d944a..590f2d80d17 100644 --- a/src/parser/parser.yy +++ b/src/parser/parser.yy @@ -102,8 +102,8 @@ class GraphScanner; %token KW_VARIABLES KW_GET KW_DECLARE KW_GRAPH KW_META KW_STORAGE %token KW_TTL_DURATION KW_TTL_COL %token KW_ORDER KW_ASC -%token KW_DISTINCT %token KW_FETCH KW_PROP +%token KW_DISTINCT KW_ALL /* symbols */ %token L_PAREN R_PAREN L_BRACKET R_BRACKET L_BRACE R_BRACE COMMA %token PIPE OR AND LT LE GT GE EQ NE PLUS MINUS MUL DIV MOD NOT NEG ASSIGN @@ -886,19 +886,42 @@ traverse_sentence | find_sentence { $$ = $1; } | order_by_sentence { $$ = $1; } | fetch_sentence { $$ = $1; } + | L_PAREN piped_sentence R_PAREN { $$ = $2; } + | L_PAREN set_sentence R_PAREN { $$ = $2; } ; set_sentence - : traverse_sentence { $$ = $1; } - | set_sentence KW_UNION traverse_sentence { $$ = new SetSentence($1, SetSentence::UNION, $3); } - | set_sentence KW_INTERSECT traverse_sentence { $$ = new SetSentence($1, SetSentence::INTERSECT, $3); } - | set_sentence KW_MINUS traverse_sentence { $$ = new SetSentence($1, SetSentence::MINUS, $3); } - | L_PAREN piped_sentence R_PAREN { $$ = $2; } + : piped_sentence KW_UNION KW_ALL piped_sentence { $$ = new SetSentence($1, SetSentence::UNION, $4); } + | piped_sentence KW_UNION piped_sentence { + auto *s = new SetSentence($1, SetSentence::UNION, $3); + s->setDistinct(); + $$ = s; + } + | piped_sentence KW_UNION KW_DISTINCT piped_sentence { + auto *s = new SetSentence($1, SetSentence::UNION, $4); + s->setDistinct(); + $$ = s; + } + | piped_sentence KW_INTERSECT piped_sentence { $$ = new SetSentence($1, SetSentence::INTERSECT, $3); } + | piped_sentence KW_MINUS piped_sentence { $$ = new SetSentence($1, SetSentence::MINUS, $3); } + | set_sentence KW_UNION KW_ALL piped_sentence { $$ = new SetSentence($1, SetSentence::UNION, $4); } + | set_sentence KW_UNION piped_sentence { + auto *s = new SetSentence($1, SetSentence::UNION, $3); + s->setDistinct(); + $$ = s; + } + | set_sentence KW_UNION KW_DISTINCT piped_sentence { + auto *s = new SetSentence($1, SetSentence::UNION, $4); + s->setDistinct(); + $$ = s; + } + | set_sentence KW_INTERSECT piped_sentence { $$ = new SetSentence($1, SetSentence::INTERSECT, $3); } + | set_sentence KW_MINUS piped_sentence { $$ = new SetSentence($1, SetSentence::MINUS, $3); } ; piped_sentence - : set_sentence { $$ = $1; } - | piped_sentence PIPE set_sentence { $$ = new PipedSentence($1, $3); } + : traverse_sentence { $$ = $1; } + | piped_sentence PIPE traverse_sentence { $$ = new PipedSentence($1, $3); } ; assignment_sentence @@ -1496,6 +1519,7 @@ maintain_sentence sentence : maintain_sentence { $$ = $1; } | use_sentence { $$ = $1; } + | set_sentence { $$ = $1; } | piped_sentence { $$ = $1; } | assignment_sentence { $$ = $1; } | mutate_sentence { $$ = $1; } diff --git a/src/parser/scanner.lex b/src/parser/scanner.lex index 6b61193e9eb..372b669ae8f 100644 --- a/src/parser/scanner.lex +++ b/src/parser/scanner.lex @@ -112,6 +112,7 @@ META ([Mm][Ee][Tt][Aa]) STORAGE ([Ss][Tt][Oo][Rr][Aa][Gg][Ee]) FETCH ([Ff][Ee][Tt][Cc][Hh]) PROP ([Pp][Rr][Oo][Pp]) +ALL ([Aa][Ll][Ll]) LABEL ([a-zA-Z][_a-zA-Z0-9]*) DEC ([0-9]) @@ -213,8 +214,7 @@ IP_OCTET ([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5]) {DISTINCT} { return TokenType::KW_DISTINCT; } {FETCH} { return TokenType::KW_FETCH; } {PROP} { return TokenType::KW_PROP; } - - +{ALL} { return TokenType::KW_ALL; } "." { return TokenType::DOT; } "," { return TokenType::COMMA; } diff --git a/src/parser/test/ParserTest.cpp b/src/parser/test/ParserTest.cpp index de8f30b7807..e682cbafcce 100644 --- a/src/parser/test/ParserTest.cpp +++ b/src/parser/test/ParserTest.cpp @@ -405,10 +405,36 @@ TEST(Parser, Set) { GQLParser parser; std::string query = "GO FROM 1 OVER friend MINUS " "GO FROM 2 OVER friend UNION " + "GO FROM 2 OVER friend INTERSECT " "GO FROM 3 OVER friend"; auto result = parser.parse(query); ASSERT_TRUE(result.ok()) << result.status(); } + { + GQLParser parser; + std::string query = "(GO FROM 1 OVER friend | " + "GO FROM 2 OVER friend) UNION " + "GO FROM 3 OVER friend"; + auto result = parser.parse(query); + ASSERT_TRUE(result.ok()) << result.status(); + } + { + GQLParser parser; + // pipe have priority over set + std::string query = "GO FROM 1 OVER friend | " + "GO FROM 2 OVER friend UNION " + "GO FROM 3 OVER friend"; + auto result = parser.parse(query); + ASSERT_TRUE(result.ok()) << result.status(); + } + { + GQLParser parser; + std::string query = "(GO FROM 1 OVER friend UNION " + "GO FROM 2 OVER friend) | " + "GO FROM $- OVER friend"; + auto result = parser.parse(query); + ASSERT_TRUE(result.ok()) << result.status(); + } } TEST(Parser, Pipe) { diff --git a/src/parser/test/ScannerTest.cpp b/src/parser/test/ScannerTest.cpp index 7dbf0126df2..1110e684a7f 100644 --- a/src/parser/test/ScannerTest.cpp +++ b/src/parser/test/ScannerTest.cpp @@ -358,6 +358,8 @@ TEST(Scanner, Basic) { CHECK_SEMANTIC_TYPE("VARIABLES", TokenType::KW_VARIABLES), CHECK_SEMANTIC_TYPE("variables", TokenType::KW_VARIABLES), CHECK_SEMANTIC_TYPE("Variables", TokenType::KW_VARIABLES), + CHECK_SEMANTIC_TYPE("ALL", TokenType::KW_ALL), + CHECK_SEMANTIC_TYPE("all", TokenType::KW_ALL), CHECK_SEMANTIC_TYPE("_type", TokenType::TYPE_PROP), CHECK_SEMANTIC_TYPE("_id", TokenType::ID_PROP),