diff options
author | Bulat <bulat@ydb.tech> | 2025-04-02 17:52:13 +0300 |
---|---|---|
committer | GitHub <noreply@github.com> | 2025-04-02 17:52:13 +0300 |
commit | e3295b3556127786c9f4eb6ebc71f1036660b1a7 (patch) | |
tree | af9cdccc93e195d1c13b3cf8269ffb6e9b2bfeca | |
parent | 889d495cab81f1a8b1f45758aec177da4a990f2d (diff) | |
download | ydb-e3295b3556127786c9f4eb6ebc71f1036660b1a7.tar.gz |
[CLI] Supported parse parameter types on client (#16189)
Co-authored-by: Nikolay Perfilov <pnv1@yandex-team.ru>
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) |