aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorBulat <bulat@ydb.tech>2025-04-02 17:52:13 +0300
committerGitHub <noreply@github.com>2025-04-02 17:52:13 +0300
commite3295b3556127786c9f4eb6ebc71f1036660b1a7 (patch)
treeaf9cdccc93e195d1c13b3cf8269ffb6e9b2bfeca
parent889d495cab81f1a8b1f45758aec177da4a990f2d (diff)
downloadydb-e3295b3556127786c9f4eb6ebc71f1036660b1a7.tar.gz
[CLI] Supported parse parameter types on client (#16189)
Co-authored-by: Nikolay Perfilov <pnv1@yandex-team.ru>
-rw-r--r--ydb/public/lib/ydb_cli/commands/ydb_sql.cpp26
-rw-r--r--ydb/public/lib/ydb_cli/commands/ydb_sql.h3
-rw-r--r--ydb/public/lib/ydb_cli/common/parameters.cpp27
-rw-r--r--ydb/public/lib/ydb_cli/common/parameters.h4
-rw-r--r--ydb/public/lib/ydb_cli/common/ya.make5
-rw-r--r--ydb/public/lib/ydb_cli/common/yql_parser/ut/ya.make11
-rw-r--r--ydb/public/lib/ydb_cli/common/yql_parser/ya.make22
-rw-r--r--ydb/public/lib/ydb_cli/common/yql_parser/yql_parser.cpp394
-rw-r--r--ydb/public/lib/ydb_cli/common/yql_parser/yql_parser.h17
-rw-r--r--ydb/public/lib/ydb_cli/common/yql_parser/yql_parser_ut.cpp928
-rw-r--r--ydb/tests/functional/ydb_cli/canondata/result.json3
-rw-r--r--ydb/tests/functional/ydb_cli/canondata/test_ydb_sql.TestExecuteSqlWithPgSyntax.test_pg_syntax/result.output5
-rw-r--r--ydb/tests/functional/ydb_cli/test_ydb_sql.py18
13 files changed, 1442 insertions, 21 deletions
diff --git a/ydb/public/lib/ydb_cli/commands/ydb_sql.cpp b/ydb/public/lib/ydb_cli/commands/ydb_sql.cpp
index e872dd4a2f..b0081f8bbd 100644
--- a/ydb/public/lib/ydb_cli/commands/ydb_sql.cpp
+++ b/ydb/public/lib/ydb_cli/commands/ydb_sql.cpp
@@ -46,8 +46,11 @@ void TCommandSql::Config(TConfig& config) {
config.Opts->AddLongOption("diagnostics-file", "Path to file where the diagnostics will be saved.")
.RequiredArgument("[String]").StoreResult(&DiagnosticsFile);
config.Opts->AddLongOption("syntax", "Query syntax [yql, pg]")
- .RequiredArgument("[String]").DefaultValue("yql").StoreResult(&Syntax)
- .Hidden();
+ .RequiredArgument("[String]")
+ .Hidden()
+ .GetOpt().Handler1T<TString>("yql", [this](const TString& arg) {
+ SetSyntax(arg);
+ });
AddOutputFormats(config, {
EDataFormat::Pretty,
@@ -143,13 +146,8 @@ int TCommandSql::RunCommand(TConfig& config) {
auto defaultStatsMode = ExplainAnalyzeMode ? NQuery::EStatsMode::Full : NQuery::EStatsMode::None;
settings.StatsMode(ParseQueryStatsModeOrThrow(CollectStatsMode, defaultStatsMode));
}
- if (Syntax == "yql") {
- settings.Syntax(NQuery::ESyntax::YqlV1);
- } else if (Syntax == "pg") {
- settings.Syntax(NQuery::ESyntax::Pg);
- } else {
- throw TMisuseException() << "Unknow syntax option \"" << Syntax << "\"";
- }
+
+ settings.Syntax(SyntaxType);
if (!Parameters.empty() || InputParamStream) {
// Execute query with parameters
@@ -284,8 +282,14 @@ void TCommandSql::SetCollectStatsMode(TString&& collectStatsMode) {
CollectStatsMode = std::move(collectStatsMode);
}
-void TCommandSql::SetSyntax(TString&& syntax) {
- Syntax = std::move(syntax);
+void TCommandSql::SetSyntax(const TString& syntax) {
+ if (syntax == "yql") {
+ SyntaxType = NYdb::NQuery::ESyntax::YqlV1;
+ } else if (syntax == "pg") {
+ SyntaxType = NYdb::NQuery::ESyntax::Pg;
+ } else {
+ throw TMisuseException() << "Unknown syntax option \"" << syntax << "\"";
+ }
}
}
diff --git a/ydb/public/lib/ydb_cli/commands/ydb_sql.h b/ydb/public/lib/ydb_cli/commands/ydb_sql.h
index b0cfec0145..a623f8a311 100644
--- a/ydb/public/lib/ydb_cli/commands/ydb_sql.h
+++ b/ydb/public/lib/ydb_cli/commands/ydb_sql.h
@@ -19,9 +19,9 @@ public:
virtual void Config(TConfig& config) override;
virtual void Parse(TConfig& config) override;
virtual int Run(TConfig& config) override;
- void SetSyntax(TString&& syntax);
void SetCollectStatsMode(TString&& collectStatsMode);
void SetScript(TString&& script);
+ void SetSyntax(const TString& syntax);
private:
int RunCommand(TConfig& config);
@@ -31,7 +31,6 @@ private:
TString DiagnosticsFile;
TString Query;
TString QueryFile;
- TString Syntax;
bool ExplainMode = false;
bool ExplainAnalyzeMode = false;
bool ExplainAst = false;
diff --git a/ydb/public/lib/ydb_cli/common/parameters.cpp b/ydb/public/lib/ydb_cli/common/parameters.cpp
index aa14137446..89ac5155a1 100644
--- a/ydb/public/lib/ydb_cli/common/parameters.cpp
+++ b/ydb/public/lib/ydb_cli/common/parameters.cpp
@@ -3,6 +3,7 @@
#include <ydb/public/lib/json_value/ydb_json_value.h>
#include <ydb/public/lib/ydb_cli/commands/ydb_common.h>
#include <ydb/public/lib/ydb_cli/common/interactive.h>
+#include <ydb/public/lib/ydb_cli/common/yql_parser/yql_parser.h>
#include <library/cpp/json/json_reader.h>
#include <library/cpp/threading/future/async.h>
@@ -333,26 +334,38 @@ void TCommandWithParameters::SetParamsInputFromFile(TString& file) {
SetParamsInput(InputFileHolder.Get());
}
-void TCommandWithParameters::GetParamTypes(const TDriver& driver, const TString& queryText) {
- NScripting::TScriptingClient client(driver);
+void TCommandWithParameters::InitParamTypes(const TDriver& driver, const TString& queryText) {
+ if (SyntaxType == NQuery::ESyntax::Pg) {
+ ParamTypes.clear();
+ return;
+ }
- NScripting::TExplainYqlRequestSettings explainSettings;
- explainSettings.Mode(NScripting::ExplainYqlRequestMode::Validate);
+ auto types = TYqlParamParser::GetParamTypes(queryText);
+ if (types.has_value()) {
+ ParamTypes = *types;
+ return;
+ }
+
+ // Fallback to ExplainYql
+ NScripting::TScriptingClient client(driver);
+ auto explainSettings = NScripting::TExplainYqlRequestSettings()
+ .Mode(NScripting::ExplainYqlRequestMode::Validate);
auto result = client.ExplainYqlScript(
queryText,
explainSettings
).GetValueSync();
+
NStatusHelpers::ThrowOnErrorOrPrintIssues(result);
ParamTypes = result.GetParameterTypes();
}
-bool TCommandWithParameters::GetNextParams(const TDriver& driver, const TString& queryText,
- THolder<TParamsBuilder>& paramBuilder) {
+bool TCommandWithParameters::GetNextParams(const TDriver& driver, const TString& queryText, THolder<TParamsBuilder>& paramBuilder) {
paramBuilder = MakeHolder<TParamsBuilder>();
if (IsFirstEncounter) {
IsFirstEncounter = false;
- GetParamTypes(driver, queryText);
+ InitParamTypes(driver, queryText);
+
if (!InputParamStream) {
AddParams(*paramBuilder);
return true;
diff --git a/ydb/public/lib/ydb_cli/common/parameters.h b/ydb/public/lib/ydb_cli/common/parameters.h
index 6bc151688f..cd841783e0 100644
--- a/ydb/public/lib/ydb_cli/common/parameters.h
+++ b/ydb/public/lib/ydb_cli/common/parameters.h
@@ -10,6 +10,7 @@
#include <ydb/public/lib/ydb_cli/common/parameter_stream.h>
#include <ydb/public/sdk/cpp/include/ydb-cpp-sdk/client/params/params.h>
#include <ydb/public/lib/json_value/ydb_json_value.h>
+#include <ydb/public/sdk/cpp/include/ydb-cpp-sdk/client/query/query.h>
namespace NYdb {
namespace NConsoleClient {
@@ -42,7 +43,7 @@ private:
void SetParamsInput(IInputStream* input);
void SetParamsInputFromFile(TString& file);
void SetParamsInputFromStdin();
- void GetParamTypes(const TDriver& driver, const TString& queryText);
+ void InitParamTypes(const TDriver& driver, const TString& queryText);
TMaybe<TString> ReadData();
@@ -69,6 +70,7 @@ protected:
TDuration BatchMaxDelay;
THolder<NScripting::TExplainYqlResult> ValidateResult;
bool ReadingSomethingFromStdin = false;
+ NQuery::ESyntax SyntaxType = NQuery::ESyntax::YqlV1;
};
}
diff --git a/ydb/public/lib/ydb_cli/common/ya.make b/ydb/public/lib/ydb_cli/common/ya.make
index 7905d3c428..13b2ea827e 100644
--- a/ydb/public/lib/ydb_cli/common/ya.make
+++ b/ydb/public/lib/ydb_cli/common/ya.make
@@ -58,6 +58,7 @@ PEERDIR(
ydb/public/sdk/cpp/src/client/types/credentials
ydb/public/sdk/cpp/src/client/types/credentials/oauth2_token_exchange
ydb/library/arrow_parquet
+ ydb/public/lib/ydb_cli/common/yql_parser
)
GENERATE_ENUM_SERIALIZATION(formats.h)
@@ -65,6 +66,10 @@ GENERATE_ENUM_SERIALIZATION(parameters.h)
END()
+RECURSE(
+ yql_parser
+)
+
RECURSE_FOR_TESTS(
ut
)
diff --git a/ydb/public/lib/ydb_cli/common/yql_parser/ut/ya.make b/ydb/public/lib/ydb_cli/common/yql_parser/ut/ya.make
new file mode 100644
index 0000000000..35bbe38af1
--- /dev/null
+++ b/ydb/public/lib/ydb_cli/common/yql_parser/ut/ya.make
@@ -0,0 +1,11 @@
+UNITTEST_FOR(ydb/public/lib/ydb_cli/common/yql_parser)
+
+SRCS(
+ yql_parser_ut.cpp
+)
+
+DATA(
+ arcadia/yql/essentials/data/language/types.json
+)
+
+END()
diff --git a/ydb/public/lib/ydb_cli/common/yql_parser/ya.make b/ydb/public/lib/ydb_cli/common/yql_parser/ya.make
new file mode 100644
index 0000000000..c0595aaa0b
--- /dev/null
+++ b/ydb/public/lib/ydb_cli/common/yql_parser/ya.make
@@ -0,0 +1,22 @@
+LIBRARY()
+
+PEERDIR(
+ ydb/public/sdk/cpp/src/client/value
+ ydb/public/sdk/cpp/src/client/types
+ yql/essentials/parser/lexer_common
+ yql/essentials/parser/proto_ast
+ yql/essentials/sql/settings
+ yql/essentials/sql/v1/lexer
+ yql/essentials/sql/v1/lexer/antlr4
+ yql/essentials/sql/v1/lexer/antlr4_ansi
+)
+
+SRCS(
+ yql_parser.cpp
+)
+
+END()
+
+RECURSE_FOR_TESTS(
+ ut
+)
diff --git a/ydb/public/lib/ydb_cli/common/yql_parser/yql_parser.cpp b/ydb/public/lib/ydb_cli/common/yql_parser/yql_parser.cpp
new file mode 100644
index 0000000000..09454ea44e
--- /dev/null
+++ b/ydb/public/lib/ydb_cli/common/yql_parser/yql_parser.cpp
@@ -0,0 +1,394 @@
+#include "yql_parser.h"
+
+#include <yql/essentials/parser/lexer_common/lexer.h>
+#include <yql/essentials/parser/proto_ast/gen/v1_antlr4/SQLv1Antlr4Lexer.h>
+#include <yql/essentials/sql/settings/translation_settings.h>
+#include <yql/essentials/sql/v1/lexer/lexer.h>
+#include <yql/essentials/sql/v1/lexer/antlr4/lexer.h>
+#include <yql/essentials/sql/v1/lexer/antlr4_ansi/lexer.h>
+#include <yql/essentials/public/issue/yql_issue.h>
+
+#include <util/generic/scope.h>
+#include <util/string/split.h>
+#include <util/string/strip.h>
+
+namespace NYdb {
+namespace NConsoleClient {
+
+namespace {
+
+TString ToLower(const TString& s) {
+ TString result = s;
+ for (char& c : result) {
+ c = std::tolower(c);
+ }
+ return result;
+}
+
+class TYqlTypeParser {
+public:
+ TYqlTypeParser(const TVector<NSQLTranslation::TParsedToken>& tokens)
+ : Tokens(tokens)
+ {}
+
+ std::optional<TType> Build(size_t& pos) {
+ auto node = Parse(SkipWS(pos));
+ if (!node || (pos < Tokens.size() && Tokens[pos].Content != ";")) {
+ return std::nullopt;
+ }
+
+ TTypeBuilder builder;
+ if (!BuildType(*node, builder)) {
+ return std::nullopt;
+ }
+ return builder.Build();
+ }
+
+private:
+ struct TypeNode {
+ TTypeParser::ETypeKind TypeKind;
+ std::vector<TypeNode> Children;
+
+ // For primitive type
+ EPrimitiveType PrimitiveType;
+
+ // For struct type
+ TString Name;
+
+ // For decimal type
+ ui32 precision = 0;
+ ui32 scale = 0;
+ };
+
+ std::optional<TypeNode> Parse(size_t& pos) {
+ TypeNode node;
+
+ TString lowerContent = ToLower(Tokens[pos].Content);
+
+ if (lowerContent == "struct" || lowerContent == "tuple") {
+ node.TypeKind = lowerContent == "struct" ? TTypeParser::ETypeKind::Struct :
+ TTypeParser::ETypeKind::Tuple;
+
+ if (SkipCurrentTokenAndWS(pos) >= Tokens.size() || Tokens[pos].Content != "<") {
+ return std::nullopt;
+ }
+
+ while (pos < Tokens.size() && Tokens[pos].Content != ">") {
+ if (lowerContent == "struct") {
+ SkipCurrentTokenAndWS(pos);
+ auto name = Tokens[pos].Content;
+
+ if (SkipCurrentTokenAndWS(pos) >= Tokens.size() || Tokens[pos].Content != ":") {
+ return std::nullopt;
+ }
+
+ auto parseResult = Parse(SkipCurrentTokenAndWS(pos));
+ if (!parseResult) {
+ return std::nullopt;
+ }
+ node.Children.push_back(*parseResult);
+ node.Children.back().Name = name;
+ } else {
+ auto parseResult = Parse(SkipCurrentTokenAndWS(pos));
+ if (!parseResult) {
+ return std::nullopt;
+ }
+ node.Children.push_back(*parseResult);
+ }
+
+ if (pos >= Tokens.size() || (Tokens[pos].Content != "," && Tokens[pos].Content != ">")) {
+ return std::nullopt;
+ }
+ }
+ } else if (lowerContent == "list" ||
+ lowerContent == "optional" ||
+ lowerContent == "dict") {
+ node.TypeKind = lowerContent == "list" ? TTypeParser::ETypeKind::List :
+ lowerContent == "optional" ? TTypeParser::ETypeKind::Optional :
+ TTypeParser::ETypeKind::Dict;
+
+ if (SkipCurrentTokenAndWS(pos) >= Tokens.size() || Tokens[pos].Content != "<") {
+ return std::nullopt;
+ }
+
+ auto parseResult = Parse(SkipCurrentTokenAndWS(pos));
+ if (!parseResult) {
+ return std::nullopt;
+ }
+ node.Children.push_back(*parseResult);
+
+ if (lowerContent == "dict") {
+ if (pos >= Tokens.size() || Tokens[pos].Content != ",") {
+ return std::nullopt;
+ }
+
+ parseResult = Parse(SkipCurrentTokenAndWS(pos));
+ if (!parseResult) {
+ return std::nullopt;
+ }
+ node.Children.push_back(*parseResult);
+ }
+
+ if (pos >= Tokens.size() || Tokens[pos].Content != ">") {
+ return std::nullopt;
+ }
+ } else if (lowerContent == "decimal") {
+ auto parseResult = ParseDecimal(pos);
+ if (!parseResult) {
+ return std::nullopt;
+ }
+ node = *parseResult;
+ } else {
+ auto parseResult = ParsePrimitive(lowerContent);
+ if (!parseResult) {
+ return std::nullopt;
+ }
+ node = *parseResult;
+ }
+
+ if (SkipCurrentTokenAndWS(pos) < Tokens.size() && Tokens[pos].Content == "?") {
+ TypeNode optionalNode;
+ optionalNode.TypeKind = TTypeParser::ETypeKind::Optional;
+ optionalNode.Children.push_back(node);
+ SkipCurrentTokenAndWS(pos);
+
+ return optionalNode;
+ }
+
+ return node;
+ }
+
+ std::optional<TypeNode> ParsePrimitive(const TString& content) {
+ TypeNode node;
+ node.TypeKind = TTypeParser::ETypeKind::Primitive;
+
+ if (content == "bool") {
+ node.PrimitiveType = EPrimitiveType::Bool;
+ } else if (content == "int8") {
+ node.PrimitiveType = EPrimitiveType::Int8;
+ } else if (content == "uint8") {
+ node.PrimitiveType = EPrimitiveType::Uint8;
+ } else if (content == "int16") {
+ node.PrimitiveType = EPrimitiveType::Int16;
+ } else if (content == "uint16") {
+ node.PrimitiveType = EPrimitiveType::Uint16;
+ } else if (content == "int32") {
+ node.PrimitiveType = EPrimitiveType::Int32;
+ } else if (content == "uint32") {
+ node.PrimitiveType = EPrimitiveType::Uint32;
+ } else if (content == "int64") {
+ node.PrimitiveType = EPrimitiveType::Int64;
+ } else if (content == "uint64") {
+ node.PrimitiveType = EPrimitiveType::Uint64;
+ } else if (content == "float") {
+ node.PrimitiveType = EPrimitiveType::Float;
+ } else if (content == "double") {
+ node.PrimitiveType = EPrimitiveType::Double;
+ } else if (content == "string") {
+ node.PrimitiveType = EPrimitiveType::String;
+ } else if (content == "utf8") {
+ node.PrimitiveType = EPrimitiveType::Utf8;
+ } else if (content == "json") {
+ node.PrimitiveType = EPrimitiveType::Json;
+ } else if (content == "yson") {
+ node.PrimitiveType = EPrimitiveType::Yson;
+ } else if (content == "date") {
+ node.PrimitiveType = EPrimitiveType::Date;
+ } else if (content == "datetime") {
+ node.PrimitiveType = EPrimitiveType::Datetime;
+ } else if (content == "timestamp") {
+ node.PrimitiveType = EPrimitiveType::Timestamp;
+ } else if (content == "interval") {
+ node.PrimitiveType = EPrimitiveType::Interval;
+ } else if (content == "date32") {
+ node.PrimitiveType = EPrimitiveType::Date32;
+ } else if (content == "datetime64") {
+ node.PrimitiveType = EPrimitiveType::Datetime64;
+ } else if (content == "timestamp64") {
+ node.PrimitiveType = EPrimitiveType::Timestamp64;
+ } else if (content == "interval64") {
+ node.PrimitiveType = EPrimitiveType::Interval64;
+ } else if (content == "tzdate") {
+ node.PrimitiveType = EPrimitiveType::TzDate;
+ } else if (content == "tzdatetime") {
+ node.PrimitiveType = EPrimitiveType::TzDatetime;
+ } else if (content == "tztimestamp") {
+ node.PrimitiveType = EPrimitiveType::TzTimestamp;
+ } else if (content == "uuid") {
+ node.PrimitiveType = EPrimitiveType::Uuid;
+ } else if (content == "jsondocument") {
+ node.PrimitiveType = EPrimitiveType::JsonDocument;
+ } else if (content == "dynumber") {
+ node.PrimitiveType = EPrimitiveType::DyNumber;
+ } else if (content == "emptylist") {
+ node.TypeKind = TTypeParser::ETypeKind::EmptyList;
+ } else if (content == "emptydict") {
+ node.TypeKind = TTypeParser::ETypeKind::EmptyDict;
+ } else if (content == "void") {
+ node.TypeKind = TTypeParser::ETypeKind::Void;
+ } else if (content == "null") {
+ node.TypeKind = TTypeParser::ETypeKind::Null;
+ } else {
+ return std::nullopt;
+ }
+
+ return node;
+ }
+
+ std::optional<TypeNode> ParseDecimal(size_t& pos) {
+ TypeNode node;
+ node.TypeKind = TTypeParser::ETypeKind::Decimal;
+
+ if (SkipCurrentTokenAndWS(pos) >= Tokens.size() || Tokens[pos].Content != "(") {
+ return std::nullopt;
+ }
+
+ if (SkipCurrentTokenAndWS(pos) >= Tokens.size() || !TryFromString<ui32>(Tokens[pos].Content, node.precision)) {
+ return std::nullopt;
+ }
+
+ if (SkipCurrentTokenAndWS(pos) >= Tokens.size() || Tokens[pos].Content != ",") {
+ return std::nullopt;
+ }
+
+ if (SkipCurrentTokenAndWS(pos) >= Tokens.size() || !TryFromString<ui32>(Tokens[pos].Content, node.scale)) {
+ return std::nullopt;
+ }
+
+ if (SkipCurrentTokenAndWS(pos) >= Tokens.size() || Tokens[pos].Content != ")") {
+ return std::nullopt;
+ }
+
+ return node;
+ }
+
+ size_t& SkipWS(size_t& pos) {
+ while (pos < Tokens.size() && Tokens[pos].Name == "WS") {
+ pos++;
+ }
+ return pos;
+ }
+
+ size_t& SkipCurrentTokenAndWS(size_t& pos) {
+ pos++;
+ return SkipWS(pos);
+ }
+
+ bool BuildType(const TypeNode& node, TTypeBuilder& builder) {
+ if (node.TypeKind == TTypeParser::ETypeKind::Optional) {
+ builder.BeginOptional();
+ BuildType(node.Children[0], builder);
+ builder.EndOptional();
+ } else if (node.TypeKind == TTypeParser::ETypeKind::List) {
+ builder.BeginList();
+ BuildType(node.Children[0], builder);
+ builder.EndList();
+ } else if (node.TypeKind == TTypeParser::ETypeKind::Struct) {
+ builder.BeginStruct();
+ for (const auto& field : node.Children) {
+ builder.AddMember(field.Name);
+ BuildType(field, builder);
+ }
+ builder.EndStruct();
+ } else if (node.TypeKind == TTypeParser::ETypeKind::Tuple) {
+ builder.BeginTuple();
+ for (const auto& element : node.Children) {
+ builder.AddElement();
+ BuildType(element, builder);
+ }
+ builder.EndTuple();
+ } else if (node.TypeKind == TTypeParser::ETypeKind::Dict) {
+ builder.BeginDict();
+ builder.DictKey();
+ BuildType(node.Children[0], builder);
+ builder.DictPayload();
+ BuildType(node.Children[1], builder);
+ builder.EndDict();
+ } else if (node.TypeKind == TTypeParser::ETypeKind::Decimal) {
+ builder.Decimal(TDecimalType(node.precision, node.scale));
+ } else if (node.TypeKind == TTypeParser::ETypeKind::Primitive) {
+ builder.Primitive(node.PrimitiveType);
+ } else {
+ return false;
+ }
+
+ return true;
+ }
+
+ const TVector<NSQLTranslation::TParsedToken>& Tokens;
+};
+
+}
+
+
+std::optional<std::map<std::string, TType>> TYqlParamParser::GetParamTypes(const TString& queryText) {
+ enum class EParseState {
+ Start,
+ Declare,
+ ParamName,
+ As,
+ Type
+ };
+
+ std::map<std::string, TType> result;
+
+ NSQLTranslationV1::TLexers lexers;
+ lexers.Antlr4 = NSQLTranslationV1::MakeAntlr4LexerFactory();
+ lexers.Antlr4Ansi = NSQLTranslationV1::MakeAntlr4AnsiLexerFactory();
+
+ auto lexer = MakeLexer(lexers, /* ansi = */ false, /* antlr4 = */ true);
+
+ TVector<NSQLTranslation::TParsedToken> tokens;
+ NYql::TIssues issues;
+ if (!NSQLTranslation::Tokenize(*lexer, queryText, "Query", tokens, issues, NSQLTranslation::SQL_MAX_PARSER_ERRORS) ||
+ !issues.Empty()) {
+ return std::nullopt;
+ }
+
+ EParseState state = EParseState::Start;
+ TString paramName;
+
+ for (size_t i = 0; i < tokens.size(); ++i) {
+ if (tokens[i].Name == "WS") {
+ continue;
+ }
+
+ if (state == EParseState::Start) {
+ if (ToLower(tokens[i].Content) != "declare") {
+ continue;
+ }
+
+ state = EParseState::Declare;
+ } else if (state == EParseState::Declare) {
+ if (tokens[i].Content != "$") {
+ return std::nullopt;
+ }
+
+ state = EParseState::ParamName;
+ } else if (state == EParseState::ParamName) {
+ paramName = "$" + tokens[i].Content;
+ state = EParseState::As;
+ } else if (state == EParseState::As) {
+ if (ToLower(tokens[i].Content) != "as") {
+ return std::nullopt;
+ }
+
+ state = EParseState::Type;
+ } else if (state == EParseState::Type) {
+ TYqlTypeParser parser(tokens);
+ auto parsedType = parser.Build(i);
+
+ if (!parsedType) {
+ return std::nullopt;
+ }
+
+ result.emplace(paramName, *parsedType);
+ state = EParseState::Start;
+ }
+ }
+
+ return result;
+}
+
+} // namespace NConsoleClient
+} // namespace NYdb
diff --git a/ydb/public/lib/ydb_cli/common/yql_parser/yql_parser.h b/ydb/public/lib/ydb_cli/common/yql_parser/yql_parser.h
new file mode 100644
index 0000000000..e022ac4443
--- /dev/null
+++ b/ydb/public/lib/ydb_cli/common/yql_parser/yql_parser.h
@@ -0,0 +1,17 @@
+#pragma once
+
+#include <ydb/public/sdk/cpp/include/ydb-cpp-sdk/client/value/value.h>
+
+#include <map>
+#include <optional>
+
+namespace NYdb {
+namespace NConsoleClient {
+
+class TYqlParamParser {
+public:
+ static std::optional<std::map<std::string, TType>> GetParamTypes(const TString& queryText);
+};
+
+} // namespace NConsoleClient
+} // namespace NYdb
diff --git a/ydb/public/lib/ydb_cli/common/yql_parser/yql_parser_ut.cpp b/ydb/public/lib/ydb_cli/common/yql_parser/yql_parser_ut.cpp
new file mode 100644
index 0000000000..08ab9d7e18
--- /dev/null
+++ b/ydb/public/lib/ydb_cli/common/yql_parser/yql_parser_ut.cpp
@@ -0,0 +1,928 @@
+#include <ydb/public/lib/ydb_cli/common/yql_parser/yql_parser.h>
+
+#include <library/cpp/json/json_reader.h>
+#include <library/cpp/testing/common/env.h>
+#include <library/cpp/testing/unittest/registar.h>
+
+#include <util/stream/file.h>
+
+using namespace NYdb;
+using namespace NYdb::NConsoleClient;
+
+Y_UNIT_TEST_SUITE(TYqlParamParserTest) {
+ Y_UNIT_TEST(TestBasicTypes) {
+ {
+ auto types = TYqlParamParser::GetParamTypes("DECLARE $id AS Uint64;");
+ UNIT_ASSERT(types.has_value());
+ UNIT_ASSERT_VALUES_EQUAL(types->size(), 1);
+ auto it = types->find("$id");
+ UNIT_ASSERT(it != types->end());
+ TTypeParser parser(it->second);
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetKind(), TTypeParser::ETypeKind::Primitive);
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetPrimitive(), EPrimitiveType::Uint64);
+ }
+
+ {
+ auto types = TYqlParamParser::GetParamTypes("DECLARE $name AS Utf8;");
+ UNIT_ASSERT(types.has_value());
+ UNIT_ASSERT_VALUES_EQUAL(types->size(), 1);
+ auto it = types->find("$name");
+ UNIT_ASSERT(it != types->end());
+ TTypeParser parser(it->second);
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetKind(), TTypeParser::ETypeKind::Primitive);
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetPrimitive(), EPrimitiveType::Utf8);
+ }
+ }
+
+ Y_UNIT_TEST(TestListType) {
+ auto types = TYqlParamParser::GetParamTypes("DECLARE $values AS List<Uint64>;");
+ UNIT_ASSERT(types.has_value());
+ UNIT_ASSERT_VALUES_EQUAL(types->size(), 1);
+ auto it = types->find("$values");
+ UNIT_ASSERT(it != types->end());
+ TTypeParser parser(it->second);
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetKind(), TTypeParser::ETypeKind::List);
+
+ parser.OpenList();
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetKind(), TTypeParser::ETypeKind::Primitive);
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetPrimitive(), EPrimitiveType::Uint64);
+ parser.CloseList();
+ }
+
+ Y_UNIT_TEST(TestStructType) {
+ auto types = TYqlParamParser::GetParamTypes("DECLARE $user AS Struct<id:Uint64,name:Utf8>;");
+ UNIT_ASSERT(types.has_value());
+ UNIT_ASSERT_VALUES_EQUAL(types->size(), 1);
+ auto it = types->find("$user");
+ UNIT_ASSERT(it != types->end());
+ TTypeParser parser(it->second);
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetKind(), TTypeParser::ETypeKind::Struct);
+
+ parser.OpenStruct();
+
+ UNIT_ASSERT(parser.TryNextMember());
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetMemberName(), "id");
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetKind(), TTypeParser::ETypeKind::Primitive);
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetPrimitive(), EPrimitiveType::Uint64);
+
+ UNIT_ASSERT(parser.TryNextMember());
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetMemberName(), "name");
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetKind(), TTypeParser::ETypeKind::Primitive);
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetPrimitive(), EPrimitiveType::Utf8);
+
+ UNIT_ASSERT(!parser.TryNextMember());
+ parser.CloseStruct();
+ }
+
+ Y_UNIT_TEST(TestMultipleParams) {
+ TString query = R"(
+ DECLARE $id AS Uint64;
+ DECLARE $name AS Utf8;
+ DECLARE $age AS Uint32;
+ )";
+ auto types = TYqlParamParser::GetParamTypes(query);
+ UNIT_ASSERT(types.has_value());
+ UNIT_ASSERT_VALUES_EQUAL(types->size(), 3);
+
+ {
+ auto it = types->find("$id");
+ UNIT_ASSERT(it != types->end());
+ TTypeParser parser(it->second);
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetKind(), TTypeParser::ETypeKind::Primitive);
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetPrimitive(), EPrimitiveType::Uint64);
+ }
+ {
+ auto it = types->find("$name");
+ UNIT_ASSERT(it != types->end());
+ TTypeParser parser(it->second);
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetKind(), TTypeParser::ETypeKind::Primitive);
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetPrimitive(), EPrimitiveType::Utf8);
+ }
+ {
+ auto it = types->find("$age");
+ UNIT_ASSERT(it != types->end());
+ TTypeParser parser(it->second);
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetKind(), TTypeParser::ETypeKind::Primitive);
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetPrimitive(), EPrimitiveType::Uint32);
+ }
+ }
+
+ Y_UNIT_TEST(TestDecimalType) {
+ auto types = TYqlParamParser::GetParamTypes("DECLARE $price AS Decimal(22,9);");
+ UNIT_ASSERT(types.has_value());
+ UNIT_ASSERT_VALUES_EQUAL(types->size(), 1);
+ auto it = types->find("$price");
+ UNIT_ASSERT(it != types->end());
+ TTypeParser parser(it->second);
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetKind(), TTypeParser::ETypeKind::Decimal);
+ auto decimal = parser.GetDecimal();
+ UNIT_ASSERT_VALUES_EQUAL(decimal.Precision, 22);
+ UNIT_ASSERT_VALUES_EQUAL(decimal.Scale, 9);
+ }
+
+ Y_UNIT_TEST(TestDictType) {
+ auto types = TYqlParamParser::GetParamTypes("DECLARE $dict AS Dict<Utf8,Uint64>;");
+ UNIT_ASSERT(types.has_value());
+ UNIT_ASSERT_VALUES_EQUAL(types->size(), 1);
+ auto it = types->find("$dict");
+ UNIT_ASSERT(it != types->end());
+ TTypeParser parser(it->second);
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetKind(), TTypeParser::ETypeKind::Dict);
+
+ parser.OpenDict();
+
+ parser.DictKey();
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetKind(), TTypeParser::ETypeKind::Primitive);
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetPrimitive(), EPrimitiveType::Utf8);
+
+ parser.DictPayload();
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetKind(), TTypeParser::ETypeKind::Primitive);
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetPrimitive(), EPrimitiveType::Uint64);
+
+ parser.CloseDict();
+ }
+
+ Y_UNIT_TEST(TestTupleType) {
+ auto types = TYqlParamParser::GetParamTypes("DECLARE $tuple AS Tuple<Uint64,Utf8,Bool>;");
+ UNIT_ASSERT(types.has_value());
+ UNIT_ASSERT_VALUES_EQUAL(types->size(), 1);
+ auto it = types->find("$tuple");
+ UNIT_ASSERT(it != types->end());
+ TTypeParser parser(it->second);
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetKind(), TTypeParser::ETypeKind::Tuple);
+
+ parser.OpenTuple();
+
+ UNIT_ASSERT(parser.TryNextElement());
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetKind(), TTypeParser::ETypeKind::Primitive);
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetPrimitive(), EPrimitiveType::Uint64);
+
+ UNIT_ASSERT(parser.TryNextElement());
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetKind(), TTypeParser::ETypeKind::Primitive);
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetPrimitive(), EPrimitiveType::Utf8);
+
+ UNIT_ASSERT(parser.TryNextElement());
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetKind(), TTypeParser::ETypeKind::Primitive);
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetPrimitive(), EPrimitiveType::Bool);
+
+ UNIT_ASSERT(!parser.TryNextElement());
+ parser.CloseTuple();
+ }
+
+ Y_UNIT_TEST(TestNestedTypes) {
+ TString query = R"(
+ DECLARE $nested AS List<Struct<
+ id: Uint64,
+ name: Utf8,
+ tags: List<Utf8>,
+ meta: Dict<Utf8, List<Uint32>>
+ >>;
+ )";
+ auto types = TYqlParamParser::GetParamTypes(query);
+ UNIT_ASSERT(types.has_value());
+ UNIT_ASSERT_VALUES_EQUAL(types->size(), 1);
+ auto it = types->find("$nested");
+ UNIT_ASSERT(it != types->end());
+
+ TTypeParser parser(it->second);
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetKind(), TTypeParser::ETypeKind::List);
+
+ parser.OpenList();
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetKind(), TTypeParser::ETypeKind::Struct);
+
+ parser.OpenStruct();
+
+ UNIT_ASSERT(parser.TryNextMember());
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetMemberName(), "id");
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetKind(), TTypeParser::ETypeKind::Primitive);
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetPrimitive(), EPrimitiveType::Uint64);
+
+ UNIT_ASSERT(parser.TryNextMember());
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetMemberName(), "name");
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetKind(), TTypeParser::ETypeKind::Primitive);
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetPrimitive(), EPrimitiveType::Utf8);
+
+ UNIT_ASSERT(parser.TryNextMember());
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetMemberName(), "tags");
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetKind(), TTypeParser::ETypeKind::List);
+ parser.OpenList();
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetKind(), TTypeParser::ETypeKind::Primitive);
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetPrimitive(), EPrimitiveType::Utf8);
+ parser.CloseList();
+
+ UNIT_ASSERT(parser.TryNextMember());
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetMemberName(), "meta");
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetKind(), TTypeParser::ETypeKind::Dict);
+
+ parser.OpenDict();
+
+ parser.DictKey();
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetKind(), TTypeParser::ETypeKind::Primitive);
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetPrimitive(), EPrimitiveType::Utf8);
+
+ parser.DictPayload();
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetKind(), TTypeParser::ETypeKind::List);
+ parser.OpenList();
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetKind(), TTypeParser::ETypeKind::Primitive);
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetPrimitive(), EPrimitiveType::Uint32);
+ parser.CloseList();
+
+ parser.CloseDict();
+
+ UNIT_ASSERT(!parser.TryNextMember());
+ parser.CloseStruct();
+ parser.CloseList();
+ }
+
+ Y_UNIT_TEST(TestCaseInsensitiveTypes) {
+ {
+ auto types = TYqlParamParser::GetParamTypes("DECLARE $id AS UINT64;");
+ UNIT_ASSERT(types.has_value());
+ UNIT_ASSERT_VALUES_EQUAL(types->size(), 1);
+ auto it = types->find("$id");
+ UNIT_ASSERT(it != types->end());
+ TTypeParser parser(it->second);
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetKind(), TTypeParser::ETypeKind::Primitive);
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetPrimitive(), EPrimitiveType::Uint64);
+ }
+
+ {
+ auto types = TYqlParamParser::GetParamTypes("DECLARE $list AS LIST<UINT32>;");
+ UNIT_ASSERT(types.has_value());
+ UNIT_ASSERT_VALUES_EQUAL(types->size(), 1);
+ auto it = types->find("$list");
+ UNIT_ASSERT(it != types->end());
+ TTypeParser parser(it->second);
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetKind(), TTypeParser::ETypeKind::List);
+ parser.OpenList();
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetKind(), TTypeParser::ETypeKind::Primitive);
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetPrimitive(), EPrimitiveType::Uint32);
+ parser.CloseList();
+ }
+
+ {
+ auto types = TYqlParamParser::GetParamTypes("DECLARE $struct AS STRUCT<ID:UINT64,NAME:UTF8>;");
+ UNIT_ASSERT(types.has_value());
+ UNIT_ASSERT_VALUES_EQUAL(types->size(), 1);
+ auto it = types->find("$struct");
+ UNIT_ASSERT(it != types->end());
+ TTypeParser parser(it->second);
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetKind(), TTypeParser::ETypeKind::Struct);
+
+ parser.OpenStruct();
+
+ UNIT_ASSERT(parser.TryNextMember());
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetMemberName(), "ID");
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetKind(), TTypeParser::ETypeKind::Primitive);
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetPrimitive(), EPrimitiveType::Uint64);
+
+ UNIT_ASSERT(parser.TryNextMember());
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetMemberName(), "NAME");
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetKind(), TTypeParser::ETypeKind::Primitive);
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetPrimitive(), EPrimitiveType::Utf8);
+
+ UNIT_ASSERT(!parser.TryNextMember());
+ parser.CloseStruct();
+ }
+
+ {
+ auto types = TYqlParamParser::GetParamTypes("DECLARE $dict AS DICT<UTF8,UINT64>;");
+ UNIT_ASSERT(types.has_value());
+ UNIT_ASSERT_VALUES_EQUAL(types->size(), 1);
+ auto it = types->find("$dict");
+ UNIT_ASSERT(it != types->end());
+ TTypeParser parser(it->second);
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetKind(), TTypeParser::ETypeKind::Dict);
+
+ parser.OpenDict();
+
+ parser.DictKey();
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetKind(), TTypeParser::ETypeKind::Primitive);
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetPrimitive(), EPrimitiveType::Utf8);
+
+ parser.DictPayload();
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetKind(), TTypeParser::ETypeKind::Primitive);
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetPrimitive(), EPrimitiveType::Uint64);
+
+ parser.CloseDict();
+ }
+
+ {
+ auto types = TYqlParamParser::GetParamTypes("DECLARE $tuple AS TUPLE<UINT64,UTF8,BOOL>;");
+ UNIT_ASSERT(types.has_value());
+ UNIT_ASSERT_VALUES_EQUAL(types->size(), 1);
+ auto it = types->find("$tuple");
+ UNIT_ASSERT(it != types->end());
+ TTypeParser parser(it->second);
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetKind(), TTypeParser::ETypeKind::Tuple);
+
+ parser.OpenTuple();
+
+ UNIT_ASSERT(parser.TryNextElement());
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetKind(), TTypeParser::ETypeKind::Primitive);
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetPrimitive(), EPrimitiveType::Uint64);
+
+ UNIT_ASSERT(parser.TryNextElement());
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetKind(), TTypeParser::ETypeKind::Primitive);
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetPrimitive(), EPrimitiveType::Utf8);
+
+ UNIT_ASSERT(parser.TryNextElement());
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetKind(), TTypeParser::ETypeKind::Primitive);
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetPrimitive(), EPrimitiveType::Bool);
+
+ UNIT_ASSERT(!parser.TryNextElement());
+ parser.CloseTuple();
+ }
+
+ {
+ auto types = TYqlParamParser::GetParamTypes("DECLARE $price AS DECIMAL(22,9);");
+ UNIT_ASSERT(types.has_value());
+ UNIT_ASSERT_VALUES_EQUAL(types->size(), 1);
+ auto it = types->find("$price");
+ UNIT_ASSERT(it != types->end());
+ TTypeParser parser(it->second);
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetKind(), TTypeParser::ETypeKind::Decimal);
+ auto decimal = parser.GetDecimal();
+ UNIT_ASSERT_VALUES_EQUAL(decimal.Precision, 22);
+ UNIT_ASSERT_VALUES_EQUAL(decimal.Scale, 9);
+ }
+
+ {
+ auto types = TYqlParamParser::GetParamTypes("declare $id as UINT64;");
+ UNIT_ASSERT(types.has_value());
+ UNIT_ASSERT_VALUES_EQUAL(types->size(), 1);
+ auto it = types->find("$id");
+ UNIT_ASSERT(it != types->end());
+ TTypeParser parser(it->second);
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetKind(), TTypeParser::ETypeKind::Primitive);
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetPrimitive(), EPrimitiveType::Uint64);
+ }
+ }
+
+ Y_UNIT_TEST(TestOptionalTypes) {
+ {
+ auto types = TYqlParamParser::GetParamTypes("DECLARE $id AS Uint64?;");
+ UNIT_ASSERT(types.has_value());
+ UNIT_ASSERT_VALUES_EQUAL(types->size(), 1);
+ auto it = types->find("$id");
+ UNIT_ASSERT(it != types->end());
+ TTypeParser parser(it->second);
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetKind(), TTypeParser::ETypeKind::Optional);
+ parser.OpenOptional();
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetKind(), TTypeParser::ETypeKind::Primitive);
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetPrimitive(), EPrimitiveType::Uint64);
+ parser.CloseOptional();
+ }
+
+ {
+ auto types = TYqlParamParser::GetParamTypes("DECLARE $id AS Optional<Uint64>;");
+ UNIT_ASSERT(types.has_value());
+ UNIT_ASSERT_VALUES_EQUAL(types->size(), 1);
+ auto it = types->find("$id");
+ UNIT_ASSERT(it != types->end());
+ TTypeParser parser(it->second);
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetKind(), TTypeParser::ETypeKind::Optional);
+ parser.OpenOptional();
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetKind(), TTypeParser::ETypeKind::Primitive);
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetPrimitive(), EPrimitiveType::Uint64);
+ parser.CloseOptional();
+ }
+
+ {
+ auto types = TYqlParamParser::GetParamTypes("DECLARE $list AS Optional<List<Uint64>>;");
+ UNIT_ASSERT(types.has_value());
+ UNIT_ASSERT_VALUES_EQUAL(types->size(), 1);
+ auto it = types->find("$list");
+ UNIT_ASSERT(it != types->end());
+ TTypeParser parser(it->second);
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetKind(), TTypeParser::ETypeKind::Optional);
+ parser.OpenOptional();
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetKind(), TTypeParser::ETypeKind::List);
+ parser.OpenList();
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetKind(), TTypeParser::ETypeKind::Primitive);
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetPrimitive(), EPrimitiveType::Uint64);
+ parser.CloseList();
+ parser.CloseOptional();
+ }
+
+ {
+ auto types = TYqlParamParser::GetParamTypes("DECLARE $struct AS Struct<id:Uint64,name:Utf8>?;");
+ UNIT_ASSERT(types.has_value());
+ UNIT_ASSERT_VALUES_EQUAL(types->size(), 1);
+ auto it = types->find("$struct");
+ UNIT_ASSERT(it != types->end());
+ TTypeParser parser(it->second);
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetKind(), TTypeParser::ETypeKind::Optional);
+ parser.OpenOptional();
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetKind(), TTypeParser::ETypeKind::Struct);
+ parser.OpenStruct();
+
+ UNIT_ASSERT(parser.TryNextMember());
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetMemberName(), "id");
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetKind(), TTypeParser::ETypeKind::Primitive);
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetPrimitive(), EPrimitiveType::Uint64);
+
+ UNIT_ASSERT(parser.TryNextMember());
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetMemberName(), "name");
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetKind(), TTypeParser::ETypeKind::Primitive);
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetPrimitive(), EPrimitiveType::Utf8);
+
+ UNIT_ASSERT(!parser.TryNextMember());
+ parser.CloseStruct();
+ parser.CloseOptional();
+ }
+
+ {
+ auto types = TYqlParamParser::GetParamTypes("DECLARE $dict AS Optional<Dict<Utf8,Uint64>>;");
+ UNIT_ASSERT(types.has_value());
+ UNIT_ASSERT_VALUES_EQUAL(types->size(), 1);
+ auto it = types->find("$dict");
+ UNIT_ASSERT(it != types->end());
+ TTypeParser parser(it->second);
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetKind(), TTypeParser::ETypeKind::Optional);
+ parser.OpenOptional();
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetKind(), TTypeParser::ETypeKind::Dict);
+
+ parser.OpenDict();
+ parser.DictKey();
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetKind(), TTypeParser::ETypeKind::Primitive);
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetPrimitive(), EPrimitiveType::Utf8);
+
+ parser.DictPayload();
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetKind(), TTypeParser::ETypeKind::Primitive);
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetPrimitive(), EPrimitiveType::Uint64);
+ parser.CloseDict();
+
+ parser.CloseOptional();
+ }
+
+ {
+ auto types = TYqlParamParser::GetParamTypes("DECLARE $tuple AS Optional<Tuple<Uint64,Utf8>>;");
+ UNIT_ASSERT(types.has_value());
+ UNIT_ASSERT_VALUES_EQUAL(types->size(), 1);
+ auto it = types->find("$tuple");
+ UNIT_ASSERT(it != types->end());
+ TTypeParser parser(it->second);
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetKind(), TTypeParser::ETypeKind::Optional);
+ parser.OpenOptional();
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetKind(), TTypeParser::ETypeKind::Tuple);
+
+ parser.OpenTuple();
+ UNIT_ASSERT(parser.TryNextElement());
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetKind(), TTypeParser::ETypeKind::Primitive);
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetPrimitive(), EPrimitiveType::Uint64);
+
+ UNIT_ASSERT(parser.TryNextElement());
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetKind(), TTypeParser::ETypeKind::Primitive);
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetPrimitive(), EPrimitiveType::Utf8);
+
+ UNIT_ASSERT(!parser.TryNextElement());
+ parser.CloseTuple();
+
+ parser.CloseOptional();
+ }
+
+ {
+ auto types = TYqlParamParser::GetParamTypes("DECLARE $decimal AS Optional<Decimal(10,2)>;");
+ UNIT_ASSERT(types.has_value());
+ UNIT_ASSERT_VALUES_EQUAL(types->size(), 1);
+ auto it = types->find("$decimal");
+ UNIT_ASSERT(it != types->end());
+ TTypeParser parser(it->second);
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetKind(), TTypeParser::ETypeKind::Optional);
+ parser.OpenOptional();
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetKind(), TTypeParser::ETypeKind::Decimal);
+ auto decimal = parser.GetDecimal();
+ UNIT_ASSERT_VALUES_EQUAL(decimal.Precision, 10);
+ UNIT_ASSERT_VALUES_EQUAL(decimal.Scale, 2);
+ parser.CloseOptional();
+ }
+
+ {
+ auto types = TYqlParamParser::GetParamTypes("DECLARE $nested AS Optional<List<Struct<id:Uint64,name:Utf8>>>;");
+ UNIT_ASSERT(types.has_value());
+ UNIT_ASSERT_VALUES_EQUAL(types->size(), 1);
+ auto it = types->find("$nested");
+ UNIT_ASSERT(it != types->end());
+ TTypeParser parser(it->second);
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetKind(), TTypeParser::ETypeKind::Optional);
+ parser.OpenOptional();
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetKind(), TTypeParser::ETypeKind::List);
+
+ parser.OpenList();
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetKind(), TTypeParser::ETypeKind::Struct);
+ parser.OpenStruct();
+
+ UNIT_ASSERT(parser.TryNextMember());
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetMemberName(), "id");
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetKind(), TTypeParser::ETypeKind::Primitive);
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetPrimitive(), EPrimitiveType::Uint64);
+
+ UNIT_ASSERT(parser.TryNextMember());
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetMemberName(), "name");
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetKind(), TTypeParser::ETypeKind::Primitive);
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetPrimitive(), EPrimitiveType::Utf8);
+
+ UNIT_ASSERT(!parser.TryNextMember());
+ parser.CloseStruct();
+ parser.CloseList();
+
+ parser.CloseOptional();
+ }
+ }
+
+ Y_UNIT_TEST(TestInvalidQuery) {
+ {
+ auto types = TYqlParamParser::GetParamTypes("DECLARE $id AS @#$%^;");
+ UNIT_ASSERT(!types.has_value());
+ }
+
+ {
+ auto types = TYqlParamParser::GetParamTypes("DECLARE $id AS");
+ UNIT_ASSERT(!types.has_value());
+ }
+
+ {
+ auto types = TYqlParamParser::GetParamTypes("DECLARE AS $id Uint64;");
+ UNIT_ASSERT(!types.has_value());
+ }
+
+ {
+ auto types = TYqlParamParser::GetParamTypes("DECLARE $invalid AS Optional;");
+ UNIT_ASSERT(!types.has_value());
+ }
+
+ {
+ auto types = TYqlParamParser::GetParamTypes("DECLARE $invalid AS Optional<>;");
+ UNIT_ASSERT(!types.has_value());
+ }
+
+ {
+ auto types = TYqlParamParser::GetParamTypes("DECLARE $invalid AS Optional<Uint64,Utf8>;");
+ UNIT_ASSERT(!types.has_value());
+ }
+
+ {
+ auto types = TYqlParamParser::GetParamTypes("DECLARE $invalid AS SomeUnknownType;");
+ UNIT_ASSERT(!types.has_value());
+ }
+
+ auto types = *TYqlParamParser::GetParamTypes(R"(
+ DECLARE $id AS Uint64;
+ abacaba abacaba;
+ lol lol lol
+ DECLARE $name AS Utf8;
+ )");
+
+ UNIT_ASSERT(types.size() == 2);
+
+ {
+ auto it = types.find("$id");
+ UNIT_ASSERT(it != types.end());
+ TTypeParser parser(it->second);
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetKind(), TTypeParser::ETypeKind::Primitive);
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetPrimitive(), EPrimitiveType::Uint64);
+ }
+
+ {
+ auto it = types.find("$name");
+ UNIT_ASSERT(it != types.end());
+ TTypeParser parser(it->second);
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetKind(), TTypeParser::ETypeKind::Primitive);
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetPrimitive(), EPrimitiveType::Utf8);
+ }
+ }
+
+ Y_UNIT_TEST(TestWhitespace) {
+ {
+ auto types = *TYqlParamParser::GetParamTypes("DECLARE $id AS Uint64;");
+ UNIT_ASSERT_VALUES_EQUAL(types.size(), 1);
+ auto it = types.find("$id");
+ UNIT_ASSERT(it != types.end());
+ TTypeParser parser(it->second);
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetKind(), TTypeParser::ETypeKind::Primitive);
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetPrimitive(), EPrimitiveType::Uint64);
+ }
+
+ {
+ auto types = *TYqlParamParser::GetParamTypes("DECLARE\t$id\tAS\tUint64;");
+ UNIT_ASSERT_VALUES_EQUAL(types.size(), 1);
+ auto it = types.find("$id");
+ UNIT_ASSERT(it != types.end());
+ TTypeParser parser(it->second);
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetKind(), TTypeParser::ETypeKind::Primitive);
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetPrimitive(), EPrimitiveType::Uint64);
+ }
+
+ {
+ auto types = *TYqlParamParser::GetParamTypes("DECLARE\n$id\nAS\nUint64;");
+ UNIT_ASSERT_VALUES_EQUAL(types.size(), 1);
+ auto it = types.find("$id");
+ UNIT_ASSERT(it != types.end());
+ TTypeParser parser(it->second);
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetKind(), TTypeParser::ETypeKind::Primitive);
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetPrimitive(), EPrimitiveType::Uint64);
+ }
+
+ {
+ auto types = *TYqlParamParser::GetParamTypes("DECLARE $id AS List< Uint64 >;");
+ UNIT_ASSERT_VALUES_EQUAL(types.size(), 1);
+ auto it = types.find("$id");
+ UNIT_ASSERT(it != types.end());
+ TTypeParser parser(it->second);
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetKind(), TTypeParser::ETypeKind::List);
+ parser.OpenList();
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetKind(), TTypeParser::ETypeKind::Primitive);
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetPrimitive(), EPrimitiveType::Uint64);
+ parser.CloseList();
+ }
+
+ {
+ auto types = *TYqlParamParser::GetParamTypes("DECLARE $id AS Struct< id : Uint64, name : Utf8 >;");
+ UNIT_ASSERT_VALUES_EQUAL(types.size(), 1);
+ auto it = types.find("$id");
+ UNIT_ASSERT(it != types.end());
+ TTypeParser parser(it->second);
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetKind(), TTypeParser::ETypeKind::Struct);
+ parser.OpenStruct();
+
+ UNIT_ASSERT(parser.TryNextMember());
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetMemberName(), "id");
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetKind(), TTypeParser::ETypeKind::Primitive);
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetPrimitive(), EPrimitiveType::Uint64);
+
+ UNIT_ASSERT(parser.TryNextMember());
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetMemberName(), "name");
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetKind(), TTypeParser::ETypeKind::Primitive);
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetPrimitive(), EPrimitiveType::Utf8);
+
+ UNIT_ASSERT(!parser.TryNextMember());
+ parser.CloseStruct();
+ }
+
+ {
+ auto types = *TYqlParamParser::GetParamTypes("DECLARE $id AS Tuple< Uint64, Utf8 >;");
+ UNIT_ASSERT_VALUES_EQUAL(types.size(), 1);
+ auto it = types.find("$id");
+ UNIT_ASSERT(it != types.end());
+ TTypeParser parser(it->second);
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetKind(), TTypeParser::ETypeKind::Tuple);
+ parser.OpenTuple();
+
+ UNIT_ASSERT(parser.TryNextElement());
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetKind(), TTypeParser::ETypeKind::Primitive);
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetPrimitive(), EPrimitiveType::Uint64);
+
+ UNIT_ASSERT(parser.TryNextElement());
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetKind(), TTypeParser::ETypeKind::Primitive);
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetPrimitive(), EPrimitiveType::Utf8);
+
+ UNIT_ASSERT(!parser.TryNextElement());
+ parser.CloseTuple();
+ }
+
+ {
+ auto types = *TYqlParamParser::GetParamTypes("DECLARE $id AS Dict< Utf8, Uint64 >;");
+ UNIT_ASSERT_VALUES_EQUAL(types.size(), 1);
+ auto it = types.find("$id");
+ UNIT_ASSERT(it != types.end());
+ TTypeParser parser(it->second);
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetKind(), TTypeParser::ETypeKind::Dict);
+ parser.OpenDict();
+
+ parser.DictKey();
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetKind(), TTypeParser::ETypeKind::Primitive);
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetPrimitive(), EPrimitiveType::Utf8);
+
+ parser.DictPayload();
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetKind(), TTypeParser::ETypeKind::Primitive);
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetPrimitive(), EPrimitiveType::Uint64);
+ parser.CloseDict();
+ }
+
+ {
+ auto types = *TYqlParamParser::GetParamTypes("DECLARE $id AS Decimal( 10, 2 );");
+ UNIT_ASSERT_VALUES_EQUAL(types.size(), 1);
+ auto it = types.find("$id");
+ UNIT_ASSERT(it != types.end());
+ TTypeParser parser(it->second);
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetKind(), TTypeParser::ETypeKind::Decimal);
+ auto decimal = parser.GetDecimal();
+ UNIT_ASSERT_VALUES_EQUAL(decimal.Precision, 10);
+ UNIT_ASSERT_VALUES_EQUAL(decimal.Scale, 2);
+ }
+
+ {
+ auto types = *TYqlParamParser::GetParamTypes("DECLARE $id AS Uint64 ?;");
+ UNIT_ASSERT_VALUES_EQUAL(types.size(), 1);
+ auto it = types.find("$id");
+ UNIT_ASSERT(it != types.end());
+ TTypeParser parser(it->second);
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetKind(), TTypeParser::ETypeKind::Optional);
+ parser.OpenOptional();
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetKind(), TTypeParser::ETypeKind::Primitive);
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetPrimitive(), EPrimitiveType::Uint64);
+ parser.CloseOptional();
+ }
+
+ {
+ auto types = *TYqlParamParser::GetParamTypes("DECLARE $id AS Optional < Uint64 >;");
+ UNIT_ASSERT_VALUES_EQUAL(types.size(), 1);
+ auto it = types.find("$id");
+ UNIT_ASSERT(it != types.end());
+ TTypeParser parser(it->second);
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetKind(), TTypeParser::ETypeKind::Optional);
+ parser.OpenOptional();
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetKind(), TTypeParser::ETypeKind::Primitive);
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetPrimitive(), EPrimitiveType::Uint64);
+ parser.CloseOptional();
+ }
+
+ {
+ auto types = *TYqlParamParser::GetParamTypes(R"(
+ DECLARE $id AS Uint64;
+ DECLARE $name AS Utf8;
+ DECLARE $age AS Uint32;
+ )");
+ UNIT_ASSERT_VALUES_EQUAL(types.size(), 3);
+ auto it = types.find("$id");
+ UNIT_ASSERT(it != types.end());
+ TTypeParser parser(it->second);
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetKind(), TTypeParser::ETypeKind::Primitive);
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetPrimitive(), EPrimitiveType::Uint64);
+ }
+ }
+
+ Y_UNIT_TEST(TestComplexQuery) {
+ TString query = R"(
+ DECLARE $user_id AS Uint64;
+ DECLARE $start_date AS Date;
+
+ SELECT
+ user_id,
+ name,
+ email,
+ created_at,
+ last_login
+ FROM users
+ WHERE user_id = $user_id
+ AND created_at >= $start_date;
+
+ DECLARE $user_data AS Struct<
+ name: Utf8,
+ email: Utf8,
+ age: Uint32?,
+ preferences: Dict<Utf8, Json>
+ >;
+
+ DECLARE $user_tags AS List<Utf8>;
+
+ UPDATE users
+ SET
+ name = $user_data.name,
+ email = $user_data.email,
+ age = $user_data.age,
+ preferences = $user_data.preferences,
+ tags = $user_tags,
+ updated_at = CurrentUtcTimestamp()
+ WHERE user_id = $user_id;
+
+ DECLARE $stats AS Struct<
+ total_users: Uint64,
+ active_users: Uint64,
+ avg_age: Decimal(5,2)
+ >;
+
+ SELECT
+ COUNT(*) as total_users,
+ COUNT(CASE WHEN last_login >= $start_date THEN 1 END) as active_users,
+ AVG(CAST(age AS Decimal(5,2))) as avg_age
+ FROM users;
+ )";
+
+ auto types = TYqlParamParser::GetParamTypes(query);
+ UNIT_ASSERT(types.has_value());
+ UNIT_ASSERT_VALUES_EQUAL(types->size(), 5);
+
+ {
+ auto it = types->find("$user_id");
+ UNIT_ASSERT(it != types->end());
+ TTypeParser parser(it->second);
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetKind(), TTypeParser::ETypeKind::Primitive);
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetPrimitive(), EPrimitiveType::Uint64);
+ }
+
+ {
+ auto it = types->find("$start_date");
+ UNIT_ASSERT(it != types->end());
+ TTypeParser parser(it->second);
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetKind(), TTypeParser::ETypeKind::Primitive);
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetPrimitive(), EPrimitiveType::Date);
+ }
+
+ {
+ auto it = types->find("$user_data");
+ UNIT_ASSERT(it != types->end());
+ TTypeParser parser(it->second);
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetKind(), TTypeParser::ETypeKind::Struct);
+ parser.OpenStruct();
+
+ UNIT_ASSERT(parser.TryNextMember());
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetMemberName(), "name");
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetKind(), TTypeParser::ETypeKind::Primitive);
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetPrimitive(), EPrimitiveType::Utf8);
+
+ UNIT_ASSERT(parser.TryNextMember());
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetMemberName(), "email");
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetKind(), TTypeParser::ETypeKind::Primitive);
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetPrimitive(), EPrimitiveType::Utf8);
+
+ UNIT_ASSERT(parser.TryNextMember());
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetMemberName(), "age");
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetKind(), TTypeParser::ETypeKind::Optional);
+ parser.OpenOptional();
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetKind(), TTypeParser::ETypeKind::Primitive);
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetPrimitive(), EPrimitiveType::Uint32);
+ parser.CloseOptional();
+
+ UNIT_ASSERT(parser.TryNextMember());
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetMemberName(), "preferences");
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetKind(), TTypeParser::ETypeKind::Dict);
+ parser.OpenDict();
+ parser.DictKey();
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetKind(), TTypeParser::ETypeKind::Primitive);
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetPrimitive(), EPrimitiveType::Utf8);
+ parser.DictPayload();
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetKind(), TTypeParser::ETypeKind::Primitive);
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetPrimitive(), EPrimitiveType::Json);
+ parser.CloseDict();
+
+ UNIT_ASSERT(!parser.TryNextMember());
+ parser.CloseStruct();
+ }
+
+ {
+ auto it = types->find("$user_tags");
+ UNIT_ASSERT(it != types->end());
+ TTypeParser parser(it->second);
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetKind(), TTypeParser::ETypeKind::List);
+ parser.OpenList();
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetKind(), TTypeParser::ETypeKind::Primitive);
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetPrimitive(), EPrimitiveType::Utf8);
+ parser.CloseList();
+ }
+
+ {
+ auto it = types->find("$stats");
+ UNIT_ASSERT(it != types->end());
+ TTypeParser parser(it->second);
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetKind(), TTypeParser::ETypeKind::Struct);
+ parser.OpenStruct();
+
+ UNIT_ASSERT(parser.TryNextMember());
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetMemberName(), "total_users");
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetKind(), TTypeParser::ETypeKind::Primitive);
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetPrimitive(), EPrimitiveType::Uint64);
+
+ UNIT_ASSERT(parser.TryNextMember());
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetMemberName(), "active_users");
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetKind(), TTypeParser::ETypeKind::Primitive);
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetPrimitive(), EPrimitiveType::Uint64);
+
+ UNIT_ASSERT(parser.TryNextMember());
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetMemberName(), "avg_age");
+ UNIT_ASSERT_VALUES_EQUAL(parser.GetKind(), TTypeParser::ETypeKind::Decimal);
+ auto decimal = parser.GetDecimal();
+ UNIT_ASSERT_VALUES_EQUAL(decimal.Precision, 5);
+ UNIT_ASSERT_VALUES_EQUAL(decimal.Scale, 2);
+
+ UNIT_ASSERT(!parser.TryNextMember());
+ parser.CloseStruct();
+ }
+ }
+
+ Y_UNIT_TEST(TestAllTypes) {
+ TString jsonContent;
+ TFileInput file(TStringBuilder() << ArcadiaSourceRoot() << "/yql/essentials/data/language/types.json");
+ jsonContent = file.ReadAll();
+
+ NJson::TJsonValue jsonValue;
+ UNIT_ASSERT(NJson::ReadJsonTree(jsonContent, &jsonValue));
+
+ for (const auto& [typeName, typeInfo] : jsonValue.GetMap()) {
+ if (typeName == "Generic" || typeName == "Unit" || typeName == "Void" ||
+ typeName == "EmptyList" || typeName == "EmptyDict" || typeName == "Null" ||
+ typeName == "TzDate32" || typeName == "TzDatetime64" || typeName == "TzTimestamp64" ||
+ typeInfo.GetMap().at("kind").GetString() == "Pg") {
+ continue;
+ }
+
+ TString query = TStringBuilder() << "DECLARE $param AS " << typeName << ";";
+
+ auto types = TYqlParamParser::GetParamTypes(query);
+ UNIT_ASSERT_C(types.has_value(), "Unknown type: " << typeName);
+ UNIT_ASSERT_VALUES_EQUAL(types->size(), 1);
+ UNIT_ASSERT(types->contains("$param"));
+ }
+ }
+}
diff --git a/ydb/tests/functional/ydb_cli/canondata/result.json b/ydb/tests/functional/ydb_cli/canondata/result.json
index 8a050cd0f4..7ce7a736df 100644
--- a/ydb/tests/functional/ydb_cli/canondata/result.json
+++ b/ydb/tests/functional/ydb_cli/canondata/result.json
@@ -611,6 +611,9 @@
"test_ydb_sql.TestExecuteSqlWithParamsFromStdin.test_stdin_par_tsv[sql]": {
"uri": "file://test_ydb_sql.TestExecuteSqlWithParamsFromStdin.test_stdin_par_tsv_sql_/result.output"
},
+ "test_ydb_sql.TestExecuteSqlWithPgSyntax.test_pg_syntax": {
+ "uri": "file://test_ydb_sql.TestExecuteSqlWithPgSyntax.test_pg_syntax/result.output"
+ },
"test_ydb_table.TestExecuteQueryWithFormats.test_data_query_csv": {
"uri": "file://test_ydb_table.TestExecuteQueryWithFormats.test_data_query_csv/result.output"
},
diff --git a/ydb/tests/functional/ydb_cli/canondata/test_ydb_sql.TestExecuteSqlWithPgSyntax.test_pg_syntax/result.output b/ydb/tests/functional/ydb_cli/canondata/test_ydb_sql.TestExecuteSqlWithPgSyntax.test_pg_syntax/result.output
new file mode 100644
index 0000000000..53e755e635
--- /dev/null
+++ b/ydb/tests/functional/ydb_cli/canondata/test_ydb_sql.TestExecuteSqlWithPgSyntax.test_pg_syntax/result.output
@@ -0,0 +1,5 @@
+┌─────┬────────┬─────────────┐
+│ key │ id │ value │
+├─────┼────────┼─────────────┤
+│ "1" │ "1111" │ "\\x6f6e65" │
+└─────┴────────┴─────────────┘
diff --git a/ydb/tests/functional/ydb_cli/test_ydb_sql.py b/ydb/tests/functional/ydb_cli/test_ydb_sql.py
index 3e86a1bd86..26d8e029dc 100644
--- a/ydb/tests/functional/ydb_cli/test_ydb_sql.py
+++ b/ydb/tests/functional/ydb_cli/test_ydb_sql.py
@@ -664,3 +664,21 @@ class TestExecuteSqlFromStdinWithWideOutput(BaseTestSqlWithDatabase):
script = "SELECT * FROM `{}`;".format(self.table_path)
output = self.execute_ydb_cli_command_with_db(["sql", "-s", script])
return self.canonical_result(output, self.tmp_path)
+
+
+class TestExecuteSqlWithPgSyntax(BaseTestSqlWithDatabase):
+ @classmethod
+ def setup_class(cls):
+ BaseTestSqlWithDatabase.setup_class()
+ cls.session = cls.driver.table_client.session().create()
+
+ @pytest.fixture(autouse=True, scope='function')
+ def init_test(self, tmp_path):
+ self.tmp_path = tmp_path
+ self.table_path = self.tmp_path.name
+ create_table_with_data(self.session, self.root_dir + "/" + self.table_path)
+
+ def test_pg_syntax(self):
+ script = "SELECT * FROM \"{}\" WHERE key = 1;".format(self.table_path)
+ output = self.execute_ydb_cli_command_with_db(["sql", "-s", script, "--syntax", "pg"])
+ return self.canonical_result(output, self.tmp_path)