summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorazevaykin <[email protected]>2026-07-23 15:31:05 +0300
committerazevaykin <[email protected]>2026-07-23 16:17:44 +0300
commit7f1caa8883ece537eda0cc9c5c5dc03f007fd9ae (patch)
treeed600089861694221b0ae1e888fb9b49007d1c06
parent1ce4e3eb967d04326eee21cef6ecfa6319a67dd6 (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.in6
-rw-r--r--yql/essentials/sql/v1/format/sql_format_ut.h12
-rw-r--r--yql/essentials/sql/v1/translation/node.h10
-rw-r--r--yql/essentials/sql/v1/translation/query.cpp65
-rw-r--r--yql/essentials/sql/v1/translation/sql_query.cpp74
-rw-r--r--yql/essentials/sql/v1/translation/sql_ut_common.h339
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) {