From cebc15f684b0b5173f1a6db78a1089c72a8a2c52 Mon Sep 17 00:00:00 2001
From: laura-ding <48548375+laura-ding@users.noreply.github.com>
Date: Wed, 8 Jul 2020 11:56:57 +0800
Subject: [PATCH] Add alter executor (#52)

* Add alter executor

* rebase upstream

* rebase upstream

* rebase upstream

* rebase upstream

Co-authored-by: dutor <440396+dutor@users.noreply.github.com>
---
 src/exec/CMakeLists.txt                   |   1 +
 src/exec/Executor.cpp                     |  17 ++-
 src/exec/maintain/AlterSchemaExecutor.cpp |  51 ++++++++
 src/exec/maintain/AlterSchemaExecutor.h   |  33 +++++
 src/mock/MetaCache.cpp                    | 153 ++++++++++++++++++++++
 src/mock/MetaCache.h                      |  12 ++
 src/mock/MockMetaServiceHandler.cpp       |  31 ++++-
 src/mock/test/SchemaTest.cpp              |  70 ++++++++++
 src/mock/test/TestBase.h                  |  82 ++++++++++++
 src/planner/Maintain.h                    | 103 ++++++++++++++-
 src/planner/PlanNode.cpp                  |   4 +
 src/planner/PlanNode.h                    |   2 +
 src/validator/MaintainValidator.cpp       | 104 +++++++++++++++
 src/validator/MaintainValidator.h         |  38 ++++++
 src/validator/Validator.cpp               |   8 +-
 15 files changed, 701 insertions(+), 8 deletions(-)
 create mode 100644 src/exec/maintain/AlterSchemaExecutor.cpp
 create mode 100644 src/exec/maintain/AlterSchemaExecutor.h

diff --git a/src/exec/CMakeLists.txt b/src/exec/CMakeLists.txt
index c9af0007..24bea57e 100644
--- a/src/exec/CMakeLists.txt
+++ b/src/exec/CMakeLists.txt
@@ -32,6 +32,7 @@ nebula_add_library(
     maintain/CreateEdgeExecutor.cpp
     maintain/DescTagExecutor.cpp
     maintain/DescEdgeExecutor.cpp
+    maintain/AlterSchemaExecutor.cpp
     mutate/InsertVerticesExecutor.cpp
     mutate/InsertEdgesExecutor.cpp
 )
diff --git a/src/exec/Executor.cpp b/src/exec/Executor.cpp
index 8deb6271..8a4e89f7 100644
--- a/src/exec/Executor.cpp
+++ b/src/exec/Executor.cpp
@@ -23,6 +23,7 @@
 #include "exec/maintain/CreateTagExecutor.h"
 #include "exec/maintain/DescEdgeExecutor.h"
 #include "exec/maintain/DescTagExecutor.h"
+#include "exec/maintain/AlterSchemaExecutor.h"
 #include "exec/mutate/InsertEdgesExecutor.h"
 #include "exec/mutate/InsertVerticesExecutor.h"
 #include "exec/query/AggregateExecutor.h"
@@ -226,6 +227,13 @@ Executor *Executor::makeExecutor(const PlanNode *node,
             exec->addDependent(input);
             break;
         }
+        case PlanNode::Kind::kAlterTag: {
+            auto alterTag = asNode<AlterTag>(node);
+            auto input = makeExecutor(alterTag->input(), qctx, visited);
+            exec = new AlterTagExecutor(alterTag, qctx);
+            exec->addDependent(input);
+            break;
+        }
         case PlanNode::Kind::kCreateEdge: {
             auto createEdge = asNode<CreateEdge>(node);
             auto input = makeExecutor(createEdge->input(), qctx, visited);
@@ -240,6 +248,13 @@ Executor *Executor::makeExecutor(const PlanNode *node,
             exec->addDependent(input);
             break;
         }
+        case PlanNode::Kind::kAlterEdge: {
+            auto alterEdge = asNode<AlterEdge>(node);
+            auto input = makeExecutor(alterEdge->input(), qctx, visited);
+            exec = new AlterEdgeExecutor(alterEdge, qctx);
+            exec->addDependent(input);
+            break;
+        }
         case PlanNode::Kind::kInsertVertices: {
             auto insertV = asNode<InsertVertices>(node);
             auto input = makeExecutor(insertV->input(), qctx, visited);
@@ -263,7 +278,7 @@ Executor *Executor::makeExecutor(const PlanNode *node,
         }
         case PlanNode::Kind::kUnknown:
         default:
-            LOG(FATAL) << "Unknown plan node kind.";
+            LOG(FATAL) << "Unknown plan node kind " << static_cast<int32_t>(node->kind());
             break;
     }
 
diff --git a/src/exec/maintain/AlterSchemaExecutor.cpp b/src/exec/maintain/AlterSchemaExecutor.cpp
new file mode 100644
index 00000000..000cd867
--- /dev/null
+++ b/src/exec/maintain/AlterSchemaExecutor.cpp
@@ -0,0 +1,51 @@
+/* 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 "exec/maintain/AlterSchemaExecutor.h"
+#include "planner/Maintain.h"
+#include "context/QueryContext.h"
+
+namespace nebula {
+namespace graph {
+
+folly::Future<Status> AlterTagExecutor::execute() {
+    dumpLog();
+
+    auto *aeNode = asNode<AlterTag>(node());
+    return qctx()->getMetaClient()->alterTagSchema(aeNode->space(),
+                                                   aeNode->getName(),
+                                                   aeNode->getSchemaItems(),
+                                                   aeNode->getSchemaProp())
+            .via(runner())
+            .then([](StatusOr<TagID> resp) {
+                if (!resp.ok()) {
+                    LOG(ERROR) << resp.status();
+                    return resp.status();
+                }
+                return Status::OK();
+            });
+}
+
+folly::Future<Status> AlterEdgeExecutor::execute() {
+    dumpLog();
+
+    auto *aeNode = asNode<AlterEdge>(node());
+    return qctx()->getMetaClient()->alterEdgeSchema(aeNode->space(),
+                                                    aeNode->getName(),
+                                                    aeNode->getSchemaItems(),
+                                                    aeNode->getSchemaProp())
+            .via(runner())
+            .then([](StatusOr<EdgeType> resp) {
+                if (!resp.ok()) {
+                    LOG(ERROR) << resp.status();
+                    return resp.status();
+                }
+                return Status::OK();
+            });
+}
+
+}   // namespace graph
+}   // namespace nebula
diff --git a/src/exec/maintain/AlterSchemaExecutor.h b/src/exec/maintain/AlterSchemaExecutor.h
new file mode 100644
index 00000000..f67093a5
--- /dev/null
+++ b/src/exec/maintain/AlterSchemaExecutor.h
@@ -0,0 +1,33 @@
+/* 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_MAINTAIN_ALTERSCHEMAEXECUTOR_H_
+#define EXEC_MAINTAIN_ALTERSCHEMAEXECUTOR_H_
+
+#include "exec/Executor.h"
+
+namespace nebula {
+namespace graph {
+class AlterTagExecutor final : public Executor {
+public:
+    AlterTagExecutor(const PlanNode *node, QueryContext *qctx)
+            : Executor("AlterTagExecutor", node, qctx) {}
+
+    folly::Future<Status> execute() override;
+};
+
+class AlterEdgeExecutor final : public Executor {
+public:
+    AlterEdgeExecutor(const PlanNode *node, QueryContext *qctx)
+            : Executor("AlterEdgeExecutor", node, qctx) {}
+
+    folly::Future<Status> execute() override;
+};
+
+}   // namespace graph
+}   // namespace nebula
+
+#endif   // EXEC_MAINTAIN_ALTERSCHEMAEXECUTOR_H_
diff --git a/src/mock/MetaCache.cpp b/src/mock/MetaCache.cpp
index 646428aa..f8e61ebf 100644
--- a/src/mock/MetaCache.cpp
+++ b/src/mock/MetaCache.cpp
@@ -213,6 +213,46 @@ Status MetaCache::dropEdge(const meta::cpp2::DropEdgeReq& req) {
     return Status::OK();
 }
 
+Status MetaCache::AlterTag(const meta::cpp2::AlterTagReq &req) {
+    folly::RWSpinLock::WriteHolder holder(lock_);
+    CHECK_SPACE_ID(req.get_space_id());
+    auto tagName = req.get_tag_name();
+    auto &tagSchemas = spaceIter->second.tagSchemas_;
+    auto findIter = tagSchemas.find(tagName);
+    if (findIter == tagSchemas.end()) {
+        return Status::Error("Tag `%s' not existed", req.get_tag_name().c_str());
+    }
+
+    auto &schema = findIter->second.schema;
+    auto items = req.get_tag_items();
+    auto prop = req.get_schema_prop();
+    auto status = alterColumnDefs(schema, items);
+    if (!status.ok()) {
+        return status;
+    }
+    return alterSchemaProp(schema, prop);
+}
+
+Status MetaCache::AlterEdge(const meta::cpp2::AlterEdgeReq &req) {
+    folly::RWSpinLock::WriteHolder holder(lock_);
+    CHECK_SPACE_ID(req.get_space_id());
+    auto edgeName = req.get_edge_name();
+    auto &edgeSchemas = spaceIter->second.edgeSchemas_;
+    auto findIter = edgeSchemas.find(edgeName);
+    if (findIter == edgeSchemas.end()) {
+        return Status::Error("Edge `%s' not existed", req.get_edge_name().c_str());
+    }
+
+    auto &schema = findIter->second.schema;
+    auto items = req.get_edge_items();
+    auto prop = req.get_schema_prop();
+    auto status = alterColumnDefs(schema, items);
+    if (!status.ok()) {
+        return status;
+    }
+    return alterSchemaProp(schema, prop);
+}
+
 Status MetaCache::createTagIndex(const meta::cpp2::CreateTagIndexReq&) {
     return Status::OK();
 }
@@ -279,5 +319,118 @@ std::unordered_map<PartitionID, std::vector<HostAddr>> MetaCache::getParts() {
     }
     return parts;
 }
+
+Status MetaCache::alterColumnDefs(meta::cpp2::Schema &schema,
+                                  const std::vector<meta::cpp2::AlterSchemaItem> &items) {
+    std::vector<meta::cpp2::ColumnDef> columns = schema.columns;
+    for (auto& item : items) {
+        auto& cols = item.get_schema().get_columns();
+        auto op = item.op;
+        for (auto& col : cols) {
+            switch (op) {
+                case meta::cpp2::AlterSchemaOp::ADD:
+                    for (auto it = schema.columns.begin(); it != schema.columns.end(); ++it) {
+                        if (it->get_name() == col.get_name()) {
+                            return Status::Error("Column existing: `%s'", col.get_name().c_str());
+                        }
+                    }
+                    columns.emplace_back(col);
+                    break;
+                case meta::cpp2::AlterSchemaOp::CHANGE: {
+                    bool isOk = false;
+                    for (auto it = columns.begin(); it != columns.end(); ++it) {
+                        auto colName = col.get_name();
+                        if (colName == it->get_name()) {
+                            // If this col is ttl_col, change not allowed
+                            if (schema.schema_prop.__isset.ttl_col &&
+                                (*schema.schema_prop.get_ttl_col() == colName)) {
+                                return Status::Error("Column: `%s' as ttl_col, change not allowed",
+                                                     colName.c_str());
+                            }
+                            *it = col;
+                            isOk = true;
+                            break;
+                        }
+                    }
+                    if (!isOk) {
+                        return Status::Error("Column not found: `%s'", col.get_name().c_str());
+                    }
+                    break;
+                }
+                case meta::cpp2::AlterSchemaOp::DROP: {
+                    bool isOk = false;
+                    for (auto it = columns.begin(); it != columns.end(); ++it) {
+                        auto colName = col.get_name();
+                        if (colName == it->get_name()) {
+                            if (schema.schema_prop.__isset.ttl_col &&
+                                (*schema.schema_prop.get_ttl_col() == colName)) {
+                                schema.schema_prop.set_ttl_duration(0);
+                                schema.schema_prop.set_ttl_col("");
+                            }
+                            columns.erase(it);
+                            isOk = true;
+                            break;
+                        }
+                    }
+                    if (!isOk) {
+                        return Status::Error("Column not found: `%s'", col.get_name().c_str());
+                    }
+                    break;
+                }
+                default:
+                    return Status::Error("Alter schema operator not supported");
+            }
+        }
+    }
+    schema.columns = std::move(columns);
+    return Status::OK();
+}
+
+Status MetaCache::alterSchemaProp(meta::cpp2::Schema &schema,
+                                  const meta::cpp2::SchemaProp &alterSchemaProp) {
+    meta::cpp2::SchemaProp schemaProp = schema.get_schema_prop();
+    if (alterSchemaProp.__isset.ttl_duration) {
+        // Graph check  <=0 to = 0
+        schemaProp.set_ttl_duration(*alterSchemaProp.get_ttl_duration());
+    }
+    if (alterSchemaProp.__isset.ttl_col) {
+        auto ttlCol = *alterSchemaProp.get_ttl_col();
+        // Disable ttl, ttl_col is empty, ttl_duration is 0
+        if (ttlCol.empty()) {
+            schemaProp.set_ttl_duration(0);
+            schemaProp.set_ttl_col(ttlCol);
+            return Status::OK();
+        }
+
+        auto existed = false;
+        for (auto& col : schema.columns) {
+            if (col.get_name() == ttlCol) {
+                // Only integer and timestamp columns can be used as ttl_col
+                if (col.type != meta::cpp2::PropertyType::INT32 &&
+                    col.type != meta::cpp2::PropertyType::INT64 &&
+                    col.type != meta::cpp2::PropertyType::TIMESTAMP) {
+                    return Status::Error("TTL column type illegal");
+                }
+                existed = true;
+                schemaProp.set_ttl_col(ttlCol);
+                break;
+            }
+        }
+
+        if (!existed) {
+            return Status::Error("TTL column not found: `%s'", ttlCol.c_str());
+        }
+    }
+
+    // Disable implicit TTL mode
+    if ((schemaProp.get_ttl_duration() && (*schemaProp.get_ttl_duration() != 0)) &&
+        (!schemaProp.get_ttl_col() || (schemaProp.get_ttl_col() &&
+                                       schemaProp.get_ttl_col()->empty()))) {
+        return Status::Error("Implicit ttl_col not support");
+    }
+
+    schema.set_schema_prop(std::move(schemaProp));
+    return Status::OK();
+}
 }  // namespace graph
 }  // namespace nebula
diff --git a/src/mock/MetaCache.h b/src/mock/MetaCache.h
index d183a480..e8704a68 100644
--- a/src/mock/MetaCache.h
+++ b/src/mock/MetaCache.h
@@ -45,6 +45,10 @@ public:
 
     Status dropEdge(const meta::cpp2::DropEdgeReq &req);
 
+    Status AlterTag(const meta::cpp2::AlterTagReq &req);
+
+    Status AlterEdge(const meta::cpp2::AlterEdgeReq &req);
+
     Status createTagIndex(const meta::cpp2::CreateTagIndexReq &req);
 
     Status createEdgeIndex(const meta::cpp2::CreateEdgeIndexReq &req);
@@ -68,6 +72,14 @@ public:
 private:
     MetaCache() = default;
 
+    Status alterColumnDefs(meta::cpp2::Schema &schema,
+                           const std::vector<meta::cpp2::AlterSchemaItem> &items);
+
+    Status alterSchemaProp(meta::cpp2::Schema &schema,
+                           const meta::cpp2::SchemaProp &alterSchemaProp);
+
+
+private:
     enum class EntryType : int8_t {
         SPACE       = 0x01,
         TAG         = 0x02,
diff --git a/src/mock/MockMetaServiceHandler.cpp b/src/mock/MockMetaServiceHandler.cpp
index 4ace2fa3..a51e2549 100644
--- a/src/mock/MockMetaServiceHandler.cpp
+++ b/src/mock/MockMetaServiceHandler.cpp
@@ -188,8 +188,20 @@ MockMetaServiceHandler::future_createTag(const meta::cpp2::CreateTagReq& req) {
 }
 
 folly::Future<meta::cpp2::ExecResp>
-MockMetaServiceHandler::future_alterTag(const meta::cpp2::AlterTagReq&) {
-    RETURN_SUCCESSED();
+MockMetaServiceHandler::future_alterTag(const meta::cpp2::AlterTagReq &req) {
+    folly::Promise<meta::cpp2::ExecResp> promise;
+    auto future = promise.getFuture();
+    meta::cpp2::ExecResp resp;
+    auto status = MetaCache::instance().AlterTag(req);
+    if (!status.ok()) {
+        LOG(ERROR) << status;
+        resp.set_code(meta::cpp2::ErrorCode::E_UNKNOWN);
+        promise.setValue(std::move(resp));
+        return future;
+    }
+    resp.set_code(meta::cpp2::ErrorCode::SUCCEEDED);
+    promise.setValue(std::move(resp));
+    return future;
 }
 
 folly::Future<meta::cpp2::ExecResp>
@@ -264,8 +276,19 @@ MockMetaServiceHandler::future_createEdge(const meta::cpp2::CreateEdgeReq& req)
 }
 
 folly::Future<meta::cpp2::ExecResp>
-MockMetaServiceHandler::future_alterEdge(const meta::cpp2::AlterEdgeReq&) {
-    RETURN_SUCCESSED();
+MockMetaServiceHandler::future_alterEdge(const meta::cpp2::AlterEdgeReq &req) {
+    folly::Promise<meta::cpp2::ExecResp> promise;
+    auto future = promise.getFuture();
+    meta::cpp2::ExecResp resp;
+    auto status = MetaCache::instance().AlterEdge(req);
+    if (!status.ok()) {
+        resp.set_code(meta::cpp2::ErrorCode::E_UNKNOWN);
+        promise.setValue(std::move(resp));
+        return future;
+    }
+    resp.set_code(meta::cpp2::ErrorCode::SUCCEEDED);
+    promise.setValue(std::move(resp));
+    return future;
 }
 
 folly::Future<meta::cpp2::ExecResp>
diff --git a/src/mock/test/SchemaTest.cpp b/src/mock/test/SchemaTest.cpp
index 9afda2df..ae65abb2 100644
--- a/src/mock/test/SchemaTest.cpp
+++ b/src/mock/test/SchemaTest.cpp
@@ -156,6 +156,76 @@ TEST_F(SchemaTest, TestEdge) {
     }
 }
 
+TEST_F(SchemaTest, TestAlterTag) {
+    {
+        cpp2::ExecutionResponse resp;
+        std::string query = "CREATE TAG alterTag(col1 STRING, col2 INT8, "
+                            "col3 DOUBLE, col4 FIXED_STRING(10));";
+        auto code = client_->execute(query, resp);
+        ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code);
+    }
+    {
+        cpp2::ExecutionResponse resp;
+        std::string query = "ALTER TAG alterTag "
+                            "ADD (col5 TIMESTAMP, col6 DATE NOT NULL), "
+                            "CHANGE (col2 INT8 DEFAULT 10), "
+                            "DROP (col4)";
+        auto code = client_->execute(query, resp);
+        ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code);
+    }
+    {
+        cpp2::ExecutionResponse resp;
+        std::string query = "DESC TAG alterTag;";
+        auto code = client_->execute(query, resp);
+        ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code);
+        ASSERT_TRUE(resp.__isset.data);
+        std::vector<std::string> colNames = {"Field", "Type", "Null", "Default"};
+        ASSERT_TRUE(verifyColNames(resp, colNames));
+        std::vector<std::vector<Value>> values = {
+                {Value("col1"), Value("string"), Value("YES"), Value()},
+                {Value("col2"), Value("int8"), Value("YES"), Value(10)},
+                {Value("col3"), Value("double"), Value("YES"), Value()},
+                {Value("col5"), Value("timestamp"), Value("YES"), Value()},
+                {Value("col6"), Value("date"), Value("NO"), Value()}};
+        ASSERT_TRUE(verifyValues(resp, values));
+    }
+}
+
+TEST_F(SchemaTest, TestAlterEdge) {
+    {
+        cpp2::ExecutionResponse resp;
+        std::string query = "CREATE EDGE alterEdge(col1 STRING, col2 INT8, "
+                            "col3 DOUBLE, col4 FIXED_STRING(10));";
+        auto code = client_->execute(query, resp);
+        ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code);
+        }
+        {
+        cpp2::ExecutionResponse resp;
+        std::string query = "ALTER EDGE alterEdge "
+                            "ADD (col5 TIMESTAMP, col6 DATE NOT NULL), "
+                            "CHANGE (col2 INT8 DEFAULT 10), "
+                            "DROP (col4)";
+        auto code = client_->execute(query, resp);
+        ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code);
+    }
+    {
+        cpp2::ExecutionResponse resp;
+        std::string query = "DESC EDGE alterEdge;";
+        auto code = client_->execute(query, resp);
+        ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code);
+        ASSERT_TRUE(resp.__isset.data);
+        std::vector<std::string> colNames = {"Field", "Type", "Null", "Default"};
+        ASSERT_TRUE(verifyColNames(resp, colNames));
+        std::vector<std::vector<Value>> values = {
+                {Value("col1"), Value("string"), Value("YES"), Value()},
+                {Value("col2"), Value("int8"), Value("YES"), Value(10)},
+                {Value("col3"), Value("double"), Value("YES"), Value()},
+                {Value("col5"), Value("timestamp"), Value("YES"), Value()},
+                {Value("col6"), Value("date"), Value("NO"), Value()}};
+        ASSERT_TRUE(verifyValues(resp, values));
+    }
+}
+
 TEST_F(SchemaTest, TestInsert) {
     sleep(FLAGS_heartbeat_interval_secs + 1);
     {
diff --git a/src/mock/test/TestBase.h b/src/mock/test/TestBase.h
index 21203192..c90825af 100644
--- a/src/mock/test/TestBase.h
+++ b/src/mock/test/TestBase.h
@@ -19,6 +19,88 @@ protected:
     void SetUp() override;
 
     void TearDown() override;
+
+    static ::testing::AssertionResult TestOK() {
+        return ::testing::AssertionSuccess();
+    }
+
+    static ::testing::AssertionResult TestError() {
+        return ::testing::AssertionFailure();
+    }
+
+    ::testing::AssertionResult verifyColNames(const cpp2::ExecutionResponse &resp,
+                                              const std::vector<std::string> &expected) {
+        if (resp.get_error_code() != cpp2::ErrorCode::SUCCEEDED) {
+            return TestError() << "query failed: "
+                               << cpp2::_ErrorCode_VALUES_TO_NAMES.at(resp.get_error_code());
+        }
+        bool hasData = resp.__isset.data;
+        if (!hasData && expected.empty()) {
+            return TestOK();
+        }
+
+        if (!hasData) {
+            return TestError() << "data is empty";
+        }
+
+        auto colNames = resp.get_data()->colNames;
+
+        if (colNames.size() != expected.size()) {
+            return TestError() << "ColNames' count not match: "
+                               << colNames.size() << " vs. " << expected.size();
+        }
+        for (auto i = 0u; i < colNames.size(); i++) {
+            if (colNames[i] != expected[i]) {
+                return TestError() << "wrong size, result size: " << colNames.size()
+                                   << ", expect size: " << expected.size();
+            }
+        }
+        return TestOK();
+    }
+
+    ::testing::AssertionResult verifyValues(const cpp2::ExecutionResponse &resp,
+                                            const std::vector<Value> &expected) {
+        std::vector<std::vector<Value>> temp;
+        temp.emplace_back(expected);
+        return verifyValues(resp, temp);
+    }
+
+    ::testing::AssertionResult verifyValues(const cpp2::ExecutionResponse &resp,
+                                            const std::vector<std::vector<Value>> &expected) {
+        if (resp.get_error_code() != cpp2::ErrorCode::SUCCEEDED) {
+            return TestError() << "query failed: "
+                               << cpp2::_ErrorCode_VALUES_TO_NAMES.at(resp.get_error_code());
+        }
+
+        bool hasData = resp.__isset.data;
+        if (!hasData && expected.empty()) {
+            return TestOK();
+        }
+
+        if (!hasData) {
+            return TestError() << "data is empty";
+        }
+
+        auto rows = resp.get_data()->rows;
+
+        if (rows.size() != expected.size()) {
+            return TestError() << "rows' count not match: "
+                               << rows.size() << " vs. " << expected.size();
+        }
+
+        for (auto i = 0u; i < rows.size(); i++) {
+            if (rows[i].values.size() != expected[i].size()) {
+                return TestError() << "The row[" << i << "]' size not match "
+                                   << rows[i].values.size() << " vs. " << expected[i].size();
+            }
+            for (auto j = 0u; j < rows[i].values.size(); j++) {
+                if (rows[i].values[j] != expected[i][j]) {
+                    return TestError() << rows[i].values[j] << " vs. " << expected[i][j];
+                }
+            }
+        }
+        return TestOK();
+    }
 };
 
 }   // namespace graph
diff --git a/src/planner/Maintain.h b/src/planner/Maintain.h
index 7f59655a..1ba43ed5 100644
--- a/src/planner/Maintain.h
+++ b/src/planner/Maintain.h
@@ -112,18 +112,117 @@ private:
         }
 };
 
-class AlterTag final : public SingleInputNode {
+class AlterSchemaNode : public SingleInputNode {
+protected:
+    AlterSchemaNode(ExecutionPlan* plan,
+                    Kind kind,
+                    PlanNode* input,
+                    GraphSpaceID space,
+                    std::string name,
+                    std::vector<meta::cpp2::AlterSchemaItem> items,
+                    meta::cpp2::SchemaProp schemaProp)
+        : SingleInputNode(plan, kind, input)
+        , space_(space)
+        , name_(std::move(name))
+        , schemaItems_(std::move(items))
+        , schemaProp_(std::move(schemaProp)) {}
+
+public:
+    const std::string& getName() const {
+        return name_;
+    }
+
+    const std::vector<meta::cpp2::AlterSchemaItem>& getSchemaItems() const {
+        return schemaItems_;
+    }
+
+    const meta::cpp2::SchemaProp& getSchemaProp() const {
+        return schemaProp_;
+    }
+
+    GraphSpaceID space() const {
+        return space_;
+    }
+
+protected:
+    GraphSpaceID                               space_;
+    std::string                                name_;
+    std::vector<meta::cpp2::AlterSchemaItem>   schemaItems_;
+    meta::cpp2::SchemaProp                     schemaProp_;
+};
+
+class AlterTag final : public AlterSchemaNode {
 public:
+    static AlterTag* make(ExecutionPlan* plan,
+                          PlanNode* input,
+                          GraphSpaceID space,
+                          std::string name,
+                          std::vector<meta::cpp2::AlterSchemaItem> items,
+                          meta::cpp2::SchemaProp schemaProp) {
+        return new AlterTag(plan,
+                            input,
+                            space,
+                            std::move(name),
+                            std::move(items),
+                            std::move(schemaProp));
+    }
+
     std::string explain() const override {
         return "AlterTag";
     }
+
+private:
+    AlterTag(ExecutionPlan* plan,
+             PlanNode* input,
+             GraphSpaceID space,
+             std::string name,
+             std::vector<meta::cpp2::AlterSchemaItem> items,
+             meta::cpp2::SchemaProp schemaProp)
+        : AlterSchemaNode(plan,
+                            Kind::kAlterTag,
+                            input,
+                            space,
+                            std::move(name),
+                            std::move(items),
+                            std::move(schemaProp)) {
+    }
 };
 
-class AlterEdge final : public SingleInputNode {
+class AlterEdge final : public AlterSchemaNode {
 public:
+    static AlterEdge* make(ExecutionPlan* plan,
+                           PlanNode* input,
+                           GraphSpaceID space,
+                           std::string name,
+                           std::vector<meta::cpp2::AlterSchemaItem> items,
+                           meta::cpp2::SchemaProp schemaProp) {
+        return new AlterEdge(plan,
+                             input,
+                             space,
+                             std::move(name),
+                             std::move(items),
+                             std::move(schemaProp));
+    }
+
     std::string explain() const override {
         return "AlterEdge";
     }
+
+private:
+    AlterEdge(ExecutionPlan* plan,
+              PlanNode* input,
+              GraphSpaceID space,
+              std::string name,
+              std::vector<meta::cpp2::AlterSchemaItem> items,
+              meta::cpp2::SchemaProp schemaProp)
+        : AlterSchemaNode(plan,
+                            Kind::kAlterEdge,
+                            input,
+                            space,
+                            std::move(name),
+                            std::move(items),
+                            std::move(schemaProp)) {
+    }
 };
 
 class DescSchema : public SingleInputNode {
diff --git a/src/planner/PlanNode.cpp b/src/planner/PlanNode.cpp
index 12af9956..a81ec7c6 100644
--- a/src/planner/PlanNode.cpp
+++ b/src/planner/PlanNode.cpp
@@ -68,6 +68,10 @@ const char* PlanNode::toString(Kind kind) {
             return "DescTag";
         case PlanNode::Kind::kDescEdge:
             return "DescEdge";
+        case PlanNode::Kind::kAlterTag:
+            return "AlterTag";
+        case PlanNode::Kind::kAlterEdge:
+            return "AlterEdge";
         case PlanNode::Kind::kInsertVertices:
             return "InsertVertices";
         case PlanNode::Kind::kInsertEdges:
diff --git a/src/planner/PlanNode.h b/src/planner/PlanNode.h
index 1cf3d117..459db15a 100644
--- a/src/planner/PlanNode.h
+++ b/src/planner/PlanNode.h
@@ -48,6 +48,8 @@ public:
         kDescSpace,
         kDescTag,
         kDescEdge,
+        kAlterTag,
+        kAlterEdge,
         kInsertVertices,
         kInsertEdges,
         kDataCollect,
diff --git a/src/validator/MaintainValidator.cpp b/src/validator/MaintainValidator.cpp
index 78bd8066..b57901f7 100644
--- a/src/validator/MaintainValidator.cpp
+++ b/src/validator/MaintainValidator.cpp
@@ -126,5 +126,109 @@ Status DescEdgeValidator::toPlan() {
     tail_ = root_;
     return Status::OK();
 }
+
+Status AlterValidator::alterSchema(const std::vector<AlterSchemaOptItem*>& schemaOpts,
+                                   const std::vector<SchemaPropItem*>& schemaProps) {
+        for (auto& schemaOpt : schemaOpts) {
+            meta::cpp2::AlterSchemaItem schemaItem;
+            auto opType = schemaOpt->toType();
+            schemaItem.set_op(opType);
+            meta::cpp2::Schema schema;
+            if (opType == meta::cpp2::AlterSchemaOp::DROP) {
+                const auto& colNames = schemaOpt->columnNames();
+                for (auto& colName : colNames) {
+                    meta::cpp2::ColumnDef column;
+                    column.name = *colName;
+                    schema.columns.emplace_back(std::move(column));
+                }
+            } else {
+                const auto& specs = schemaOpt->columnSpecs();
+                for (auto& spec : specs) {
+                    meta::cpp2::ColumnDef column;
+                    column.name = *spec->name();
+                    column.type = spec->type();
+                    if (spec->hasDefaultValue()) {
+                        column.set_default_value(spec->getDefaultValue());
+                    }
+                    if (spec->type() == meta::cpp2::PropertyType::FIXED_STRING) {
+                        column.set_type_length(spec->typeLen());
+                    }
+                    if (spec->isNull()) {
+                        column.set_nullable(true);
+                    }
+                    schema.columns.emplace_back(std::move(column));
+                }
+            }
+
+            schemaItem.set_schema(std::move(schema));
+            schemaItems_.emplace_back(std::move(schemaItem));
+        }
+
+        for (auto& schemaProp : schemaProps) {
+            auto propType = schemaProp->getPropType();
+            StatusOr<int64_t> retInt;
+            StatusOr<std::string> retStr;
+            int ttlDuration;
+            switch (propType) {
+                case SchemaPropItem::TTL_DURATION:
+                    retInt = schemaProp->getTtlDuration();
+                    if (!retInt.ok()) {
+                        return retInt.status();
+                    }
+                    ttlDuration = retInt.value();
+                    schemaProp_.set_ttl_duration(ttlDuration);
+                    break;
+                case SchemaPropItem::TTL_COL:
+                    // Check the legality of the column in meta
+                    retStr = schemaProp->getTtlCol();
+                    if (!retStr.ok()) {
+                        return retStr.status();
+                    }
+                    schemaProp_.set_ttl_col(retStr.value());
+                    break;
+                default:
+                    return Status::Error("Property type not support");
+            }
+        }
+        return Status::OK();
+}
+
+Status AlterTagValidator::validateImpl() {
+    auto sentence = static_cast<AlterTagSentence*>(sentence_);
+    name_ = *sentence->name();
+    return alterSchema(sentence->getSchemaOpts(), sentence->getSchemaProps());
+}
+
+Status AlterTagValidator::toPlan() {
+    auto* plan = qctx_->plan();
+    auto *doNode = AlterTag::make(plan,
+                                  nullptr,
+                                  vctx_->whichSpace().id,
+                                  std::move(name_),
+                                  std::move(schemaItems_),
+                                  std::move(schemaProp_));
+    root_ = doNode;
+    tail_ = root_;
+    return Status::OK();
+}
+
+Status AlterEdgeValidator::validateImpl() {
+    auto sentence = static_cast<AlterEdgeSentence*>(sentence_);
+    name_ = *sentence->name();
+    return alterSchema(sentence->getSchemaOpts(), sentence->getSchemaProps());
+}
+
+Status AlterEdgeValidator::toPlan() {
+    auto* plan = qctx_->plan();
+    auto *doNode = AlterEdge::make(plan,
+                                   nullptr,
+                                   vctx_->whichSpace().id,
+                                   std::move(name_),
+                                   std::move(schemaItems_),
+                                   std::move(schemaProp_));
+    root_ = doNode;
+    tail_ = root_;
+    return Status::OK();
+}
 }  // namespace graph
 }  // namespace nebula
diff --git a/src/validator/MaintainValidator.h b/src/validator/MaintainValidator.h
index f03a3503..d4382423 100644
--- a/src/validator/MaintainValidator.h
+++ b/src/validator/MaintainValidator.h
@@ -77,6 +77,44 @@ private:
     Status toPlan() override;
 };
 
+class AlterValidator : public Validator {
+public:
+    AlterValidator(Sentence* sentence, QueryContext* context)
+            : Validator(sentence, context) {}
+protected:
+    Status alterSchema(const std::vector<AlterSchemaOptItem*>& schemaOpts,
+                       const std::vector<SchemaPropItem*>& schemaProps);
+
+protected:
+    std::vector<meta::cpp2::AlterSchemaItem>          schemaItems_;
+    meta::cpp2::SchemaProp                            schemaProp_;
+    std::string                                       name_;
+};
+
+class AlterTagValidator final : public AlterValidator {
+public:
+    AlterTagValidator(Sentence* sentence, QueryContext* context)
+            : AlterValidator(sentence, context) {
+    }
+
+private:
+    Status validateImpl() override;
+
+    Status toPlan() override;
+};
+
+class AlterEdgeValidator final : public AlterValidator {
+public:
+    AlterEdgeValidator(Sentence* sentence, QueryContext* context)
+            : AlterValidator(sentence, context) {
+    }
+
+private:
+    Status validateImpl() override;
+
+    Status toPlan() override;
+};
+
 }  // namespace graph
 }  // namespace nebula
 #endif  // VALIDATOR_MAINTAINVALIDATOR_H_
diff --git a/src/validator/Validator.cpp b/src/validator/Validator.cpp
index 31edaa79..7b449369 100644
--- a/src/validator/Validator.cpp
+++ b/src/validator/Validator.cpp
@@ -54,6 +54,10 @@ std::unique_ptr<Validator> Validator::makeValidator(Sentence* sentence, QueryCon
             return std::make_unique<DescTagValidator>(sentence, context);
         case Sentence::Kind::kDescribeEdge:
             return std::make_unique<DescEdgeValidator>(sentence, context);
+        case Sentence::Kind::kAlterTag:
+            return std::make_unique<AlterTagValidator>(sentence, context);
+        case Sentence::Kind::kAlterEdge:
+            return std::make_unique<AlterEdgeValidator>(sentence, context);
         case Sentence::Kind::kInsertVertices:
             return std::make_unique<InsertVerticesValidator>(sentence, context);
         case Sentence::Kind::kInsertEdges:
@@ -81,7 +85,9 @@ Status Validator::appendPlan(PlanNode* node, PlanNode* appended) {
         case PlanNode::Kind::kDescEdge:
         case PlanNode::Kind::kInsertVertices:
         case PlanNode::Kind::kInsertEdges:
-        case PlanNode::Kind::kGetNeighbors: {
+        case PlanNode::Kind::kGetNeighbors:
+        case PlanNode::Kind::kAlterTag:
+        case PlanNode::Kind::kAlterEdge: {
             static_cast<SingleInputNode*>(node)->setInput(appended);
             break;
         }
-- 
GitLab