diff options
author | fedor-miron <fedor-miron@yandex-team.com> | 2023-07-28 17:54:51 +0300 |
---|---|---|
committer | fedor-miron <fedor-miron@yandex-team.com> | 2023-07-28 17:54:51 +0300 |
commit | 845e1b3be646163a5b0e8e1effeca264e0bfaa6a (patch) | |
tree | cd4e1648425c3cb01b73234b19d2a82494ff3bc5 | |
parent | 9de583456c536a5f21352381a38b35bd5918c9ac (diff) | |
download | ydb-845e1b3be646163a5b0e8e1effeca264e0bfaa6a.tar.gz |
YQL-15686: add support to PG Update in parser
-rw-r--r-- | ydb/library/yql/ast/yql_ast.h | 6 | ||||
-rw-r--r-- | ydb/library/yql/sql/pg/pg_sql.cpp | 83 | ||||
-rw-r--r-- | ydb/library/yql/sql/pg/pg_sql_ut.cpp | 24 |
3 files changed, 110 insertions, 3 deletions
diff --git a/ydb/library/yql/ast/yql_ast.h b/ydb/library/yql/ast/yql_ast.h index 887b53dbc5d..5f9312f5604 100644 --- a/ydb/library/yql/ast/yql_ast.h +++ b/ydb/library/yql/ast/yql_ast.h @@ -10,6 +10,7 @@ #include <util/stream/output.h> #include <util/stream/str.h> #include <util/memory/pool.h> +#include <util/generic/array_ref.h> namespace NYql { @@ -127,6 +128,11 @@ struct TAstNode { return Data.L.Children[index]; } } + + inline TArrayRef<TAstNode*> GetChildren() { + Y_VERIFY(IsList()); + return {ListCount <= SmallListCount ? Data.S.Children : Data.L.Children, ListCount}; + } static inline TAstNode* NewAtom(TPosition position, TStringBuf content, TMemoryPool& pool, ui32 flags = TNodeFlags::Default) { auto poolContent = pool.AppendString(content); diff --git a/ydb/library/yql/sql/pg/pg_sql.cpp b/ydb/library/yql/sql/pg/pg_sql.cpp index f86d7a6d164..a57a62ab43b 100644 --- a/ydb/library/yql/sql/pg/pg_sql.cpp +++ b/ydb/library/yql/sql/pg/pg_sql.cpp @@ -128,6 +128,10 @@ int StrCompare(const char* s1, const char* s2) { return strcmp(s1 ? s1 : "", s2 ? s2 : ""); } +std::shared_ptr<List> ListMake1(void* cell) { + return std::shared_ptr<List>(list_make1(cell), list_free); +} + #define CAST_NODE(nodeType, nodeptr) CastNode<nodeType>(nodeptr, T_##nodeType) #define CAST_NODE_EXT(nodeType, tag, nodeptr) CastNode<nodeType>(nodeptr, tag) #define LIST_CAST_NTH(nodeType, list, index) CAST_NODE(nodeType, list_nth(list, i)) @@ -282,6 +286,8 @@ public: return ParseSelectStmt(CAST_NODE(SelectStmt, node), false) != nullptr; case T_InsertStmt: return ParseInsertStmt(CAST_NODE(InsertStmt, node)) != nullptr; + case T_UpdateStmt: + return ParseUpdateStmt(CAST_NODE(UpdateStmt, node)) != nullptr; case T_ViewStmt: return ParseViewStmt(CAST_NODE(ViewStmt, node)) != nullptr; case T_CreateStmt: @@ -306,7 +312,7 @@ public: using TTraverseNodeStack = TStack<std::pair<const Node*, bool>>; [[nodiscard]] - TAstNode* ParseSelectStmt(const SelectStmt* value, bool inner, TVector <TAstNode*> targetColumns = {}) { + TAstNode* ParseSelectStmt(const SelectStmt* value, bool inner, TVector <TAstNode*> targetColumns = {}, bool allowEmptyResSet = false) { CTE.emplace_back(); Y_DEFER { CTE.pop_back(); @@ -600,8 +606,8 @@ public: return nullptr; } - if (!ListLength(x->valuesLists) == !ListLength(x->targetList)) { - AddError("SelectStmt: only one of values_lists and target_list should be specified"); + if (!allowEmptyResSet && (ListLength(x->valuesLists) == 0) && (ListLength(x->targetList) == 0)) { + AddError("SelectStmt: both values_list and target_list are not allowed to be empty"); return nullptr; } @@ -1013,6 +1019,63 @@ public: return Statements.back(); } + + [[nodiscard]] + TAstNode* ParseUpdateStmt(const UpdateStmt* value) { + const auto fromClause = value->fromClause ? value->fromClause : ListMake1(value->relation).get(); + SelectStmt selectStmt { + .type = T_SelectStmt, + .targetList = value->targetList, + .fromClause = fromClause, + .whereClause = value->whereClause, + .withClause = value->withClause, + }; + const auto select = ParseSelectStmt(&selectStmt, /* inner */ true, {}, /* allowEmptyResSet */ true); + if (!select) { + return nullptr; + } + + const auto [sink, key] = ParseWriteRangeVar(value->relation); + if (!sink || !key) { + return nullptr; + } + + TVector<TAstNode*> returningList; + if (value->returningList) { + auto list = ParseReturningList(value->returningList); + if (list.has_value()) { + returningList = list.value(); + } else { + return nullptr; + } + } + + TVector<TAstNode*> options; + options.push_back(QL(QA("pg_update"), A("update_select"))); + options.push_back(QL(QA("mode"), QA("update"))); + if (!returningList.empty()) { + options.push_back(QL(QA("returning"), QVL(returningList.data(), returningList.size()))); + } + const auto writeUpdate = L(A("block"), QL( + L(A("let"), A("update_select"), select), + L(A("let"), A("sink"), sink), + L(A("let"), A("key"), key), + L(A("return"), L( + A("Write!"), + A("world"), + A("sink"), + A("key"), + L(A("Void")), + QVL(options.data(), options.size()))) + )); + Statements.push_back(L( + A("let"), + A("world"), + writeUpdate + )); + + return Statements.back(); + } [[nodiscard]] TAstNode* ParseViewStmt(const ViewStmt* value) { @@ -3352,6 +3415,20 @@ public: TAstNode* QL(TNodes... nodes) { return Q(L(nodes...)); } + + template <typename... TNodes> + TAstNode* E(TAstNode* list, TNodes... nodes) { + Y_VERIFY(list->IsList()); + TVector<TAstNode*> nodes_vec; + nodes_vec.reserve(list->GetChildrenCount() + sizeof...(nodes)); + + auto children = list->GetChildren(); + if (children) { + nodes_vec.assign(children.begin(), children.end()); + } + nodes_vec.assign({nodes...}); + return VL(nodes_vec.data(), nodes_vec.size()); + } private: void AddError(const TString& value) { diff --git a/ydb/library/yql/sql/pg/pg_sql_ut.cpp b/ydb/library/yql/sql/pg/pg_sql_ut.cpp index 78ae9f4b172..ee66f25064b 100644 --- a/ydb/library/yql/sql/pg/pg_sql_ut.cpp +++ b/ydb/library/yql/sql/pg/pg_sql_ut.cpp @@ -405,4 +405,28 @@ Y_UNIT_TEST_SUITE(PgSqlParsingOnly) { const auto expectedAst = NYql::ParseAst(program); UNIT_ASSERT_STRINGS_EQUAL(res.Root->ToString(), expectedAst.Root->ToString()); } + + Y_UNIT_TEST(UpdateStmt) { + auto res = PgSqlToYql("UPDATE plato.Input SET kind = 'test' where kind = 'testtest'"); + TString updateStmtProg = R"( + ( + (let world (Configure! world (DataSource 'config) 'OrderedColumns)) + (let read0 (Read! world (DataSource '"yt" '"plato") (Key '('table (String '"input"))) (Void) '())) + (let world (Left! read0)) + (let world (block '( + (let update_select (PgSelect '('('set_items '((PgSetItem '('('result '((PgResultItem '"kind" (Void) (lambda '() (PgConst '"test" (PgType 'text)))))) '('from '('((Right! read0) '"input" '()))) '('join_ops '('())) '('where (PgWhere (Void) (lambda '() (PgOp '"=" (PgColumnRef '"kind") (PgConst '"testtest" (PgType 'text)))))))))) '('set_ops '('push))))) + (let sink (DataSink '"yt" '"plato")) + (let key (Key '('table (String '"input")))) + (return (Write! world sink key (Void) '('('pg_update update_select) '('mode 'update)))) + ))) + (let world (CommitAll! world)) + (return world) + ) + )"; + const auto expectedAst = NYql::ParseAst(updateStmtProg); + + UNIT_ASSERT_C(res.Issues.Empty(), "Failed to parse statement, issues: " + res.Issues.ToString()); + UNIT_ASSERT_C(res.Root, "Failed to parse statement, root is nullptr"); + UNIT_ASSERT_STRINGS_EQUAL(res.Root->ToString(), expectedAst.Root->ToString()); + } } |