diff options
| author | azevaykin <[email protected]> | 2026-07-23 15:31:05 +0300 |
|---|---|---|
| committer | azevaykin <[email protected]> | 2026-07-23 16:17:44 +0300 |
| commit | 7f1caa8883ece537eda0cc9c5c5dc03f007fd9ae (patch) | |
| tree | ed600089861694221b0ae1e888fb9b49007d1c06 | |
| parent | 1ce4e3eb967d04326eee21cef6ecfa6319a67dd6 (diff) | |
Add CREATE IF NOT EXISTS, CREATE OR REPLACE, ALTER/DROP IF EXISTS for SECRET operations
#### Add CREATE IF NOT EXISTS, CREATE OR REPLACE, ALTER/DROP IF EXISTS for SECRET operations ✎
- Introduce support for `CREATE IF NOT EXISTS` and `CREATE OR REPLACE` syntax for secret creation, allowing conditional secret creation based on existence
- Add `ALTER IF EXISTS` and `DROP IF EXISTS` syntax for secret modification and deletion, preventing errors when operating on non-existent secrets
- Extend secret operation parsing to handle optional keywords for conditional operations while maintaining backward compatibility
- Update secret parameter validation and error handling to support new conditional operation modes
- Add comprehensive test coverage for all new conditional secret operations including error cases and expression support
<a href="https://nda.ya.ru/t/qa0kX64r7DqvtN"><font size="2">Autodescription by Yandex Code Assistant</font></a>
commit_hash:5da22cd6acc7bb610627c562280556c497d57a10
| -rw-r--r-- | yql/essentials/sql/v1/SQLv1Antlr4.g.in | 6 | ||||
| -rw-r--r-- | yql/essentials/sql/v1/format/sql_format_ut.h | 12 | ||||
| -rw-r--r-- | yql/essentials/sql/v1/translation/node.h | 10 | ||||
| -rw-r--r-- | yql/essentials/sql/v1/translation/query.cpp | 65 | ||||
| -rw-r--r-- | yql/essentials/sql/v1/translation/sql_query.cpp | 74 | ||||
| -rw-r--r-- | yql/essentials/sql/v1/translation/sql_ut_common.h | 339 |
6 files changed, 464 insertions, 42 deletions
diff --git a/yql/essentials/sql/v1/SQLv1Antlr4.g.in b/yql/essentials/sql/v1/SQLv1Antlr4.g.in index 54ffbdeec54..34f98185f33 100644 --- a/yql/essentials/sql/v1/SQLv1Antlr4.g.in +++ b/yql/essentials/sql/v1/SQLv1Antlr4.g.in @@ -1262,18 +1262,18 @@ alter_sequence_action: show_create_table_stmt: SHOW CREATE (TABLE | VIEW | EXTERNAL DATA SOURCE) simple_table_ref; -create_secret_stmt: CREATE SECRET object_ref +create_secret_stmt: CREATE (OR REPLACE)? SECRET (IF NOT EXISTS)? object_ref with_secret_settings ; with_secret_settings: WITH LPAREN secret_setting_entry (COMMA secret_setting_entry)* COMMA? RPAREN; secret_setting_entry: an_id EQUALS secret_setting_value; secret_setting_value: expr; -alter_secret_stmt: ALTER SECRET object_ref +alter_secret_stmt: ALTER SECRET (IF EXISTS)? object_ref with_secret_settings ; -drop_secret_stmt: DROP SECRET object_ref; +drop_secret_stmt: DROP SECRET (IF EXISTS)? object_ref; // Special rules that allow to use certain keywords as identifiers. identifier: ID_PLAIN | ID_QUOTED; diff --git a/yql/essentials/sql/v1/format/sql_format_ut.h b/yql/essentials/sql/v1/format/sql_format_ut.h index 6406608c26b..eff542ef3c4 100644 --- a/yql/essentials/sql/v1/format/sql_format_ut.h +++ b/yql/essentials/sql/v1/format/sql_format_ut.h @@ -205,12 +205,24 @@ Y_UNIT_TEST(SecretOperations) { {// create with more than one setting "use plato; create secret `secret-name` with (value=\"secret_value\",inherit_permissions=fALSe);\n", "USE plato;\n\nCREATE SECRET `secret-name` WITH (value = 'secret_value', inherit_permissions = FALSE);\n"}, + {// create if not exists + "use plato; create secret if not exists `secret-name` with (value=\"secret_value\");\n", + "USE plato;\n\nCREATE SECRET IF NOT EXISTS `secret-name` WITH (value = 'secret_value');\n"}, + {// create or replace + "use plato; create or replace secret `secret-name` with (value=\"secret_value\");\n", + "USE plato;\n\nCREATE OR REPLACE SECRET `secret-name` WITH (value = 'secret_value');\n"}, {// alter "use plato; alter secret `secret-name` with (value=\"secret_value\");\n", "USE plato;\n\nALTER SECRET `secret-name` WITH (value = 'secret_value');\n"}, + {// alter if exists + "use plato; alter secret if exists `secret-name` with (value=\"secret_value\");\n", + "USE plato;\n\nALTER SECRET IF EXISTS `secret-name` WITH (value = 'secret_value');\n"}, {// drop "use plato; drop secret `secret-name`;\n", "USE plato;\n\nDROP SECRET `secret-name`;\n"}, + {// drop if exists + "use plato; drop secret if exists `secret-name`;\n", + "USE plato;\n\nDROP SECRET IF EXISTS `secret-name`;\n"}, }; TSetup setup; diff --git a/yql/essentials/sql/v1/translation/node.h b/yql/essentials/sql/v1/translation/node.h index b79eb7d04bc..5b19485c9a6 100644 --- a/yql/essentials/sql/v1/translation/node.h +++ b/yql/essentials/sql/v1/translation/node.h @@ -1831,18 +1831,22 @@ TNodePtr BuildCreateSecret( const TString& objectId, const TSecretParameters& secretParams, const TObjectOperatorContext& context, - TScopedStatePtr scoped); + TScopedStatePtr scoped, + bool replaceIfExists = false, + bool existingOk = false); TNodePtr BuildAlterSecret( TPosition pos, const TString& objectId, const TSecretParameters& secretParams, const TObjectOperatorContext& context, - TScopedStatePtr scoped); + TScopedStatePtr scoped, + bool missingOk = false); TNodePtr BuildDropSecret( TPosition pos, const TString& objectId, const TObjectOperatorContext& context, - TScopedStatePtr scoped); + TScopedStatePtr scoped, + bool missingOk = false); template <class TContainer> TMaybe<TString> FindMistypeIn(const TContainer& container, const TString& name) { diff --git a/yql/essentials/sql/v1/translation/query.cpp b/yql/essentials/sql/v1/translation/query.cpp index 0276df12534..46aa5348382 100644 --- a/yql/essentials/sql/v1/translation/query.cpp +++ b/yql/essentials/sql/v1/translation/query.cpp @@ -4556,13 +4556,19 @@ public: TString objectId, TSecretParameters params, const TObjectOperatorContext& context, - TScopedStatePtr scoped) + TScopedStatePtr scoped, + bool replaceIfExists = false, + bool existingOk = false, + bool missingOk = false) : TBase(pos) , Pos_(pos) , ObjectId_(std::move(objectId)) , Params_(std::move(params)) , Context_(context) , Scoped_(std::move(scoped)) + , ReplaceIfExists_(replaceIfExists) + , ExistingOk_(existingOk) + , MissingOk_(missingOk) { } @@ -4582,6 +4588,16 @@ public: protected: virtual TString GetMode() = 0; + TString GetCreateMode() const { + if (ReplaceIfExists_) { + return "create_or_replace"; + } + if (ExistingOk_) { + return "create_if_not_exists"; + } + return "create"; + } + private: TPtr BuildOptions() { auto options = Y(); @@ -4617,6 +4633,9 @@ protected: const TSecretParameters Params_; const TObjectOperatorContext Context_; TScopedStatePtr Scoped_; + const bool ReplaceIfExists_ = false; + const bool ExistingOk_ = false; + const bool MissingOk_ = false; }; class TCreateSecretNode: public TSecretNode { @@ -4628,18 +4647,20 @@ public: const TString& objectId, const TSecretParameters& params, const TObjectOperatorContext& context, - TScopedStatePtr scoped) - : TBase(pos, objectId, params, context, scoped) + TScopedStatePtr scoped, + bool replaceIfExists, + bool existingOk) + : TBase(pos, objectId, params, context, scoped, replaceIfExists, existingOk, false) { } protected: TString GetMode() override { - return "create"; + return GetCreateMode(); } TPtr DoClone() const final { - return new TCreateSecretNode(Pos_, ObjectId_, Params_, Context_, Scoped_); + return new TCreateSecretNode(Pos_, ObjectId_, Params_, Context_, Scoped_, ReplaceIfExists_, ExistingOk_); } }; @@ -4648,8 +4669,10 @@ TNodePtr BuildCreateSecret( const TString& objectId, const TSecretParameters& secretParams, const TObjectOperatorContext& context, - TScopedStatePtr scoped) { - return new TCreateSecretNode(pos, objectId, secretParams, context, scoped); + TScopedStatePtr scoped, + bool replaceIfExists, + bool existingOk) { + return new TCreateSecretNode(pos, objectId, secretParams, context, scoped, replaceIfExists, existingOk); } class TAlterSecretNode: public TSecretNode { @@ -4661,18 +4684,19 @@ public: const TString& objectId, const TSecretParameters& params, const TObjectOperatorContext& context, - TScopedStatePtr scoped) - : TBase(pos, objectId, params, context, scoped) + TScopedStatePtr scoped, + bool missingOk) + : TBase(pos, objectId, params, context, scoped, false, false, missingOk) { } protected: TString GetMode() override { - return "alter"; + return MissingOk_ ? "alter_if_exists" : "alter"; } TPtr DoClone() const final { - return new TAlterSecretNode(Pos_, ObjectId_, Params_, Context_, Scoped_); + return new TAlterSecretNode(Pos_, ObjectId_, Params_, Context_, Scoped_, MissingOk_); } }; @@ -4681,8 +4705,9 @@ TNodePtr BuildAlterSecret( const TString& objectId, const TSecretParameters& secretParams, const TObjectOperatorContext& context, - TScopedStatePtr scoped) { - return new TAlterSecretNode(pos, objectId, secretParams, context, scoped); + TScopedStatePtr scoped, + bool missingOk) { + return new TAlterSecretNode(pos, objectId, secretParams, context, scoped, missingOk); } class TDropSecretNode: public TSecretNode { @@ -4693,18 +4718,19 @@ public: TPosition pos, const TString& objectId, const TObjectOperatorContext& context, - TScopedStatePtr scoped) - : TBase(pos, objectId, TSecretParameters{}, context, scoped) + TScopedStatePtr scoped, + bool missingOk) + : TBase(pos, objectId, TSecretParameters{}, context, scoped, false, false, missingOk) { } protected: TPtr DoClone() const final { - return new TDropSecretNode(Pos_, ObjectId_, Context_, Scoped_); + return new TDropSecretNode(Pos_, ObjectId_, Context_, Scoped_, MissingOk_); } TString GetMode() override { - return "drop"; + return MissingOk_ ? "drop_if_exists" : "drop"; } }; @@ -4712,8 +4738,9 @@ TNodePtr BuildDropSecret( TPosition pos, const TString& objectId, const TObjectOperatorContext& context, - TScopedStatePtr scoped) { - return new TDropSecretNode(pos, objectId, context, scoped); + TScopedStatePtr scoped, + bool missingOk) { + return new TDropSecretNode(pos, objectId, context, scoped, missingOk); } } // namespace NSQLTranslationV1 diff --git a/yql/essentials/sql/v1/translation/sql_query.cpp b/yql/essentials/sql/v1/translation/sql_query.cpp index 42228fa5586..6726d3ddb2d 100644 --- a/yql/essentials/sql/v1/translation/sql_query.cpp +++ b/yql/essentials/sql/v1/translation/sql_query.cpp @@ -2229,29 +2229,47 @@ bool TSqlQuery::Statement(TVector<TNodePtr>& blocks, const TRule_sql_stmt_core& break; } case TRule_sql_stmt_core::kAltSqlStmtCore66: { - // create_secret_stmt: CREATE SECRET object_ref WITH (k=v,...); + // create_secret_stmt: CREATE (OR REPLACE)? SECRET (IF NOT EXISTS)? object_ref WITH (k=v,...); Ctx_.BodyPart(); auto& node = core.GetAlt_sql_stmt_core66().GetRule_create_secret_stmt1(); Ctx_.Token(node.GetToken1()); const TPosition stmBeginPos = Ctx_.Pos(); + + bool replaceIfExists = false; + if (node.HasBlock2()) { // OR REPLACE + replaceIfExists = true; + Y_DEBUG_ABORT_UNLESS( + IS_TOKEN(node.GetBlock2().GetToken1().GetId(), OR) && + IS_TOKEN(node.GetBlock2().GetToken2().GetId(), REPLACE)); + } + + bool existingOk = false; + if (node.HasBlock4()) { // IF NOT EXISTS + existingOk = true; + Y_DEBUG_ABORT_UNLESS( + IS_TOKEN(node.GetBlock4().GetToken1().GetId(), IF) && + IS_TOKEN(node.GetBlock4().GetToken2().GetId(), NOT) && + IS_TOKEN(node.GetBlock4().GetToken3().GetId(), EXISTS)); + } + TObjectOperatorContext context(Ctx_.Scoped); - if (node.GetRule_object_ref3().HasBlock1()) { + if (node.GetRule_object_ref5().HasBlock1()) { if (!ClusterExpr( - node.GetRule_object_ref3().GetBlock1().GetRule_cluster_expr1(), + node.GetRule_object_ref5().GetBlock1().GetRule_cluster_expr1(), /*allowWildcard=*/false, context.ServiceId, context.Cluster)) { return false; } } TString objectId; - if (!ParseSecretId(node.GetRule_object_ref3().GetRule_id_or_at2(), objectId)) { + if (!ParseSecretId(node.GetRule_object_ref5().GetRule_id_or_at2(), objectId)) { return false; } TSecretParameters secretParams; if (!ParseSecretSettings( stmBeginPos, - node.GetRule_with_secret_settings4(), + node.GetRule_with_secret_settings6(), secretParams, TSecretParameters::EOperationMode::Create)) { return false; @@ -2264,33 +2282,44 @@ bool TSqlQuery::Statement(TVector<TNodePtr>& blocks, const TRule_sql_stmt_core& BuildTablePath(Ctx_.GetPrefixPath(context.ServiceId, context.Cluster), objectId), secretParams, context, - Ctx_.Scoped)); + Ctx_.Scoped, + replaceIfExists, + existingOk)); break; } case TRule_sql_stmt_core::kAltSqlStmtCore67: { - // alter_secret_stmt: ALTER SECRET object_ref WITH (k=v,...); + // alter_secret_stmt: ALTER SECRET (IF EXISTS)? object_ref WITH (k=v,...); Ctx_.BodyPart(); auto& node = core.GetAlt_sql_stmt_core67().GetRule_alter_secret_stmt1(); Ctx_.Token(node.GetToken1()); const TPosition stmBeginPos = Ctx_.Pos(); + + bool missingOk = false; + if (node.HasBlock3()) { // IF EXISTS + missingOk = true; + Y_DEBUG_ABORT_UNLESS( + IS_TOKEN(node.GetBlock3().GetToken1().GetId(), IF) && + IS_TOKEN(node.GetBlock3().GetToken2().GetId(), EXISTS)); + } + TObjectOperatorContext context(Ctx_.Scoped); - if (node.GetRule_object_ref3().HasBlock1()) { + if (node.GetRule_object_ref4().HasBlock1()) { if (!ClusterExpr( - node.GetRule_object_ref3().GetBlock1().GetRule_cluster_expr1(), + node.GetRule_object_ref4().GetBlock1().GetRule_cluster_expr1(), /*allowWildcard=*/false, context.ServiceId, context.Cluster)) { return false; } } TString objectId; - if (!ParseSecretId(node.GetRule_object_ref3().GetRule_id_or_at2(), objectId)) { + if (!ParseSecretId(node.GetRule_object_ref4().GetRule_id_or_at2(), objectId)) { return false; } TSecretParameters secretParams; if (!ParseSecretSettings( stmBeginPos, - node.GetRule_with_secret_settings4(), + node.GetRule_with_secret_settings5(), secretParams, TSecretParameters::EOperationMode::Alter)) { return false; @@ -2303,26 +2332,36 @@ bool TSqlQuery::Statement(TVector<TNodePtr>& blocks, const TRule_sql_stmt_core& BuildTablePath(Ctx_.GetPrefixPath(context.ServiceId, context.Cluster), objectId), secretParams, context, - Ctx_.Scoped)); + Ctx_.Scoped, + missingOk)); break; } case TRule_sql_stmt_core::kAltSqlStmtCore68: { - // drop_secret_stmt: DROP SECRET object_ref; + // drop_secret_stmt: DROP SECRET (IF EXISTS)? object_ref; Ctx_.BodyPart(); auto& node = core.GetAlt_sql_stmt_core68().GetRule_drop_secret_stmt1(); Ctx_.Token(node.GetToken1()); const TPosition pos = Ctx_.Pos(); + + bool missingOk = false; + if (node.HasBlock3()) { // IF EXISTS + missingOk = true; + Y_DEBUG_ABORT_UNLESS( + IS_TOKEN(node.GetBlock3().GetToken1().GetId(), IF) && + IS_TOKEN(node.GetBlock3().GetToken2().GetId(), EXISTS)); + } + TObjectOperatorContext context(Ctx_.Scoped); - if (node.GetRule_object_ref3().HasBlock1()) { + if (node.GetRule_object_ref4().HasBlock1()) { if (!ClusterExpr( - node.GetRule_object_ref3().GetBlock1().GetRule_cluster_expr1(), + node.GetRule_object_ref4().GetBlock1().GetRule_cluster_expr1(), /*allowWildcard=*/false, context.ServiceId, context.Cluster)) { return false; } } TString objectId; - if (!ParseSecretId(node.GetRule_object_ref3().GetRule_id_or_at2(), objectId)) { + if (!ParseSecretId(node.GetRule_object_ref4().GetRule_id_or_at2(), objectId)) { return false; } @@ -2332,7 +2371,8 @@ bool TSqlQuery::Statement(TVector<TNodePtr>& blocks, const TRule_sql_stmt_core& Ctx_.Pos(), BuildTablePath(Ctx_.GetPrefixPath(context.ServiceId, context.Cluster), objectId), context, - Ctx_.Scoped)); + Ctx_.Scoped, + missingOk)); break; } case TRule_sql_stmt_core::kAltSqlStmtCore69: { diff --git a/yql/essentials/sql/v1/translation/sql_ut_common.h b/yql/essentials/sql/v1/translation/sql_ut_common.h index 8558598e1da..78eb97e8d60 100644 --- a/yql/essentials/sql/v1/translation/sql_ut_common.h +++ b/yql/essentials/sql/v1/translation/sql_ut_common.h @@ -6230,6 +6230,345 @@ Y_UNIT_TEST(DropSecretIncorrect) { } } +Y_UNIT_TEST(CreateSecretIfNotExists) { + UNIT_ASSERT(SqlToYql(R"sql( + USE plato; + CREATE SECRET IF NOT EXISTS `secret-name` WITH (VALUE = "secret-value"); + )sql") + .IsOk()); +} + +Y_UNIT_TEST(CreateSecretIfNotExistsCorrect) { + auto res = SqlToYql(R"sql( + USE plato; + CREATE SECRET IF NOT EXISTS `secret-name` WITH (VALUE = "secret-value"); + )sql"); + UNIT_ASSERT_C(res.IsOk(), Err2Str(res)); + + TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { + if (word == "Write") { + UNIT_ASSERT_STRING_CONTAINS(line, "Key '('secret"); + UNIT_ASSERT_STRING_CONTAINS(line, "'mode 'create_if_not_exists"); + UNIT_ASSERT_STRING_CONTAINS(line, "secret-name"); + UNIT_ASSERT_STRING_CONTAINS(line, R"("value" '"secret-value")"); + } + }; + + TWordCountHive elementStat = {{TString("Write"), 0}}; + VerifyProgram(res, elementStat, verifyLine); + + UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); +} + +Y_UNIT_TEST(CreateSecretOrReplace) { + UNIT_ASSERT(SqlToYql(R"sql( + USE plato; + CREATE OR REPLACE SECRET `secret-name` WITH (VALUE = "secret-value"); + )sql") + .IsOk()); +} + +Y_UNIT_TEST(CreateSecretOrReplaceCorrect) { + auto res = SqlToYql(R"sql( + USE plato; + CREATE OR REPLACE SECRET `secret-name` WITH (VALUE = "secret-value"); + )sql"); + UNIT_ASSERT_C(res.IsOk(), Err2Str(res)); + + TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { + if (word == "Write") { + UNIT_ASSERT_STRING_CONTAINS(line, "Key '('secret"); + UNIT_ASSERT_STRING_CONTAINS(line, "'mode 'create_or_replace"); + UNIT_ASSERT_STRING_CONTAINS(line, "secret-name"); + UNIT_ASSERT_STRING_CONTAINS(line, R"("value" '"secret-value")"); + } + }; + + TWordCountHive elementStat = {{TString("Write"), 0}}; + VerifyProgram(res, elementStat, verifyLine); + + UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); +} + +Y_UNIT_TEST(CreateSecretOrReplaceWithInheritPermissions) { + auto res = SqlToYql(R"sql( + USE plato; + CREATE OR REPLACE SECRET `secret-name` WITH (VALUE = "secret-value", INHERIT_PERMISSIONS = TRUE); + )sql"); + UNIT_ASSERT_C(res.IsOk(), Err2Str(res)); + + TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { + if (word == "Write") { + UNIT_ASSERT_STRING_CONTAINS(line, "'mode 'create_or_replace"); + UNIT_ASSERT_STRING_CONTAINS(line, R"("inherit_permissions" '"1")"); + } + }; + + TWordCountHive elementStat = {{TString("Write"), 0}}; + VerifyProgram(res, elementStat, verifyLine); + + UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); +} + +Y_UNIT_TEST(CreateSecretIfNotExistsWithExpression) { + const auto res = SqlToYql(R"sql( + USE plato; + DECLARE $foo AS String; + CREATE SECRET IF NOT EXISTS `secret-name` WITH (VALUE = $foo); + )sql"); + UNIT_ASSERT_C(res.IsOk(), Err2Str(res)); + + TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { + if (word == "Write") { + UNIT_ASSERT_STRING_CONTAINS(line, "'mode 'create_if_not_exists"); + UNIT_ASSERT_STRING_CONTAINS(line, R"('"value_expr" (EvaluateExpr "$foo"))"); + } + }; + + TWordCountHive elementStat = {{TString("Write"), 0}}; + VerifyProgram(res, elementStat, verifyLine); + + UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); +} + +Y_UNIT_TEST(CreateSecretOrReplaceWithExpression) { + const auto res = SqlToYql(R"sql( + USE plato; + DECLARE $foo AS String; + CREATE OR REPLACE SECRET `secret-name` WITH (VALUE = $foo); + )sql"); + UNIT_ASSERT_C(res.IsOk(), Err2Str(res)); + + TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { + if (word == "Write") { + UNIT_ASSERT_STRING_CONTAINS(line, "'mode 'create_or_replace"); + UNIT_ASSERT_STRING_CONTAINS(line, R"('"value_expr" (EvaluateExpr "$foo"))"); + } + }; + + TWordCountHive elementStat = {{TString("Write"), 0}}; + VerifyProgram(res, elementStat, verifyLine); + + UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); +} + +Y_UNIT_TEST(AlterSecretIfExists) { + UNIT_ASSERT(SqlToYql(R"sql( + USE ydb; + ALTER SECRET IF EXISTS `secret-name` WITH (VALUE = "secret-value"); + )sql") + .IsOk()); +} + +Y_UNIT_TEST(AlterSecretIfExistsCorrect) { + auto res = SqlToYql(R"sql( + USE ydb; + ALTER SECRET IF EXISTS `secret-name` WITH (VALUE = "secret-value"); + )sql"); + UNIT_ASSERT_C(res.IsOk(), Err2Str(res)); + + TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { + if (word == "Write") { + UNIT_ASSERT_STRING_CONTAINS(line, "Key '('secret"); + UNIT_ASSERT_STRING_CONTAINS(line, "'mode 'alter_if_exists"); + UNIT_ASSERT_STRING_CONTAINS(line, "secret-name"); + UNIT_ASSERT_STRING_CONTAINS(line, R"("value" '"secret-value")"); + } + }; + + TWordCountHive elementStat = {{TString("Write"), 0}}; + VerifyProgram(res, elementStat, verifyLine); + + UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); +} + +Y_UNIT_TEST(AlterSecretIfExistsWithExpression) { + const auto res = SqlToYql(R"sql( + USE plato; + DECLARE $foo AS String; + ALTER SECRET IF EXISTS `secret-name` WITH (VALUE = $foo); + )sql"); + UNIT_ASSERT_C(res.IsOk(), Err2Str(res)); + + TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { + if (word == "Write") { + UNIT_ASSERT_STRING_CONTAINS(line, "'mode 'alter_if_exists"); + UNIT_ASSERT_STRING_CONTAINS(line, R"('"value_expr" (EvaluateExpr "$foo"))"); + } + }; + + TWordCountHive elementStat = {{TString("Write"), 0}}; + VerifyProgram(res, elementStat, verifyLine); + + UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); +} + +Y_UNIT_TEST(DropSecretIfExists) { + UNIT_ASSERT(SqlToYql(R"sql( + USE plato; + DROP SECRET IF EXISTS `secret-name`; + )sql") + .IsOk()); +} + +Y_UNIT_TEST(DropSecretIfExistsCorrect) { + auto res = SqlToYql(R"sql( + USE plato; + DROP SECRET IF EXISTS `secret-name`; + )sql"); + UNIT_ASSERT_C(res.IsOk(), Err2Str(res)); + + TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { + if (word == "Write") { + UNIT_ASSERT_STRING_CONTAINS(line, "Key '('secret"); + UNIT_ASSERT_STRING_CONTAINS(line, "'mode 'drop_if_exists"); + UNIT_ASSERT_STRING_CONTAINS(line, "secret-name"); + } + }; + + TWordCountHive elementStat = {{TString("Write"), 0}}; + VerifyProgram(res, elementStat, verifyLine); + + UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); +} + +Y_UNIT_TEST(DropSecretIfExistsWithTablePathPrefix) { + UNIT_ASSERT(SqlToYql(R"sql( + USE plato; + PRAGMA TablePathPrefix = "/PathPrefix"; + DROP SECRET IF EXISTS `secret-name`; + )sql") + .IsOk()); +} + +Y_UNIT_TEST(CreateSecretOrReplaceAndIfNotExists) { + // Both OR REPLACE and IF NOT EXISTS are allowed together; OR REPLACE takes priority + auto res = SqlToYql(R"sql( + USE plato; + CREATE OR REPLACE SECRET IF NOT EXISTS `secret-name` WITH (VALUE = "secret-value"); + )sql"); + UNIT_ASSERT_C(res.IsOk(), Err2Str(res)); + + TVerifyLineFunc verifyLine = [](const TString& word, const TString& line) { + if (word == "Write") { + UNIT_ASSERT_STRING_CONTAINS(line, "'mode 'create_or_replace"); + } + }; + + TWordCountHive elementStat = {{TString("Write"), 0}}; + VerifyProgram(res, elementStat, verifyLine); + + UNIT_ASSERT_VALUES_EQUAL(1, elementStat["Write"]); +} + +Y_UNIT_TEST(CreateSecretIfNotExistsIncorrect) { + { // no value + NYql::TAstParseResult res = SqlToYql(R"sql( + USE plato; + CREATE SECRET IF NOT EXISTS `secret-name` WITH (INHERIT_PERMISSIONS = FALSE); + )sql"); + UNIT_ASSERT(!res.IsOk()); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:3:13: Error: Parameter VALUE must be set\n"); + } + { // temporal object in secret name + NYql::TAstParseResult res = SqlToYql(R"sql( + USE plato; + CREATE SECRET IF NOT EXISTS @tmp WITH (VALUE = "abc"); + )sql"); + UNIT_ASSERT(!res.IsOk()); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:3:42: Error: '@' is not allowed prefix for secret name\n"); + } + { // empty secret name + NYql::TAstParseResult res = SqlToYql(R"sql( + USE plato; + CREATE SECRET IF NOT EXISTS `` WITH (INHERIT_PERMISSIONS = FALSE); + )sql"); + UNIT_ASSERT(!res.IsOk()); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:3:41: Error: Empty secret name\n"); + } +} + +Y_UNIT_TEST(CreateSecretOrReplaceIncorrect) { + { // no value + NYql::TAstParseResult res = SqlToYql(R"sql( + USE plato; + CREATE OR REPLACE SECRET `secret-name` WITH (INHERIT_PERMISSIONS = FALSE); + )sql"); + UNIT_ASSERT(!res.IsOk()); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:3:13: Error: Parameter VALUE must be set\n"); + } + { // value is not a string + NYql::TAstParseResult res = SqlToYql(R"sql( + USE plato; + CREATE OR REPLACE SECRET `secret-name` WITH (VALUE = true); + )sql"); + UNIT_ASSERT(!res.IsOk()); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:3:66: Error: Unsupported type for parameter: VALUE. String was expected\n"); + } + { // temporal object in secret name + NYql::TAstParseResult res = SqlToYql(R"sql( + USE plato; + CREATE OR REPLACE SECRET @tmp WITH (VALUE = "abc"); + )sql"); + UNIT_ASSERT(!res.IsOk()); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:3:39: Error: '@' is not allowed prefix for secret name\n"); + } +} + +Y_UNIT_TEST(AlterSecretIfExistsIncorrect) { + { // no value + NYql::TAstParseResult res = SqlToYql(R"sql( + USE ydb; + ALTER SECRET IF EXISTS `secret-name`; + )sql"); + UNIT_ASSERT(!res.IsOk()); +#if ANTLR_VER == 3 + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:3:48: Error: Unexpected token ';' : syntax error...\n\n"); +#else + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:3:48: Error: mismatched input ';' expecting WITH\n"); +#endif + } + { // inherit_permissions is set + NYql::TAstParseResult res = SqlToYql(R"sql( + USE ydb; + ALTER SECRET IF EXISTS `secret-name` WITH (VALUE = "value", INHERIT_PERMISSIONS = FALSE); + )sql"); + UNIT_ASSERT(!res.IsOk()); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:3:13: Error: parameter INHERIT_PERMISSIONS is not supported for alter operation\n"); + } + { // temporal object in secret name + NYql::TAstParseResult res = SqlToYql(R"sql( + USE ydb; + ALTER SECRET IF EXISTS @tmp WITH (VALUE = "abc"); + )sql"); + UNIT_ASSERT(!res.IsOk()); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:3:37: Error: '@' is not allowed prefix for secret name\n"); + } +} + +Y_UNIT_TEST(DropSecretIfExistsIncorrect) { + { // temporal object in secret name + NYql::TAstParseResult res = SqlToYql(R"sql( + USE plato; + DROP SECRET IF EXISTS @tmp; + )sql"); + UNIT_ASSERT(!res.IsOk()); + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:3:36: Error: '@' is not allowed prefix for secret name\n"); + } + { // WITH is not allowed for drop + NYql::TAstParseResult res = SqlToYql(R"sql( + USE plato; + DROP SECRET IF EXISTS `secret-name` WITH (VALUE = "abc"); + )sql"); + UNIT_ASSERT(!res.IsOk()); +#if ANTLR_VER == 3 + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:3:48: Error: Unexpected token 'WITH' : cannot match to any predicted input...\n\n"); +#else + UNIT_ASSERT_NO_DIFF(Err2Str(res), "<main>:3:48: Error: extraneous input 'WITH' expecting {<EOF>, ';'}\n"); +#endif + } +} + } // Y_UNIT_TEST_SUITE(SqlParsingOnly) Y_UNIT_TEST_SUITE(ExternalFunction) { |
