diff options
author | vitya-smirnov <vitya-smirnov@yandex-team.com> | 2025-07-10 16:18:41 +0300 |
---|---|---|
committer | vitya-smirnov <vitya-smirnov@yandex-team.com> | 2025-07-10 16:49:06 +0300 |
commit | ab4a6f4beadc1b478f8c208d07226687821b5fc2 (patch) | |
tree | e19e74f48361d1aa73d139c23ae514ea8ab4453f /yql/essentials/sql/v1/complete | |
parent | ea2073d5c0897338da46444473d7649ceac2289b (diff) | |
download | ydb-ab4a6f4beadc1b478f8c208d07226687821b5fc2.tar.gz |
YQL-19747: Complete table functions
- Added table functions completion.
- Also fixed a bug when USEd cluster
was not added to table context at table
function argument.
- Complete folder names at `prefix`
of `LIKE`, `RANGE`, etc.
commit_hash:26be383be728796e8431f906e2815acd77645ad4
Diffstat (limited to 'yql/essentials/sql/v1/complete')
14 files changed, 200 insertions, 45 deletions
diff --git a/yql/essentials/sql/v1/complete/analysis/global/function.cpp b/yql/essentials/sql/v1/complete/analysis/global/function.cpp index 0bd9740796a..30072f4a69c 100644 --- a/yql/essentials/sql/v1/complete/analysis/global/function.cpp +++ b/yql/essentials/sql/v1/complete/analysis/global/function.cpp @@ -2,6 +2,8 @@ #include "narrowing_visitor.h" +#include <library/cpp/iterator/enumerate.h> + namespace NSQLComplete { namespace { @@ -26,23 +28,35 @@ namespace NSQLComplete { if (function == nullptr || lparen == nullptr) { return {}; } - if (CursorPosition() <= TextInterval(lparen).b) { return {}; } - return function->getText(); + return TFunctionContext{ + .Name = function->getText(), + .ArgumentNumber = ArgumentNumber(ctx).GetOrElse(0), + }; + } + + private: + TMaybe<size_t> ArgumentNumber(SQLv1::Table_refContext* ctx) { + for (auto [i, arg] : Enumerate(ctx->table_arg())) { + if (IsEnclosing(arg)) { + return i; + } + } + return Nothing(); } }; } // namespace - TMaybe<TString> EnclosingFunction(TParsedInput input) { + TMaybe<TFunctionContext> EnclosingFunction(TParsedInput input) { std::any result = TVisitor(input).visit(input.SqlQuery); if (!result.has_value()) { return Nothing(); } - return std::any_cast<std::string>(result); + return std::any_cast<TFunctionContext>(result); } } // namespace NSQLComplete diff --git a/yql/essentials/sql/v1/complete/analysis/global/function.h b/yql/essentials/sql/v1/complete/analysis/global/function.h index 77f1478c5cc..52aa7090b7b 100644 --- a/yql/essentials/sql/v1/complete/analysis/global/function.h +++ b/yql/essentials/sql/v1/complete/analysis/global/function.h @@ -1,5 +1,6 @@ #pragma once +#include "global.h" #include "input.h" #include <util/generic/maybe.h> @@ -7,6 +8,6 @@ namespace NSQLComplete { - TMaybe<TString> EnclosingFunction(TParsedInput input); + TMaybe<TFunctionContext> EnclosingFunction(TParsedInput input); } // namespace NSQLComplete diff --git a/yql/essentials/sql/v1/complete/analysis/global/global.cpp b/yql/essentials/sql/v1/complete/analysis/global/global.cpp index be80531ad86..130338e03de 100644 --- a/yql/essentials/sql/v1/complete/analysis/global/global.cpp +++ b/yql/essentials/sql/v1/complete/analysis/global/global.cpp @@ -242,6 +242,14 @@ namespace NSQLComplete { } // namespace NSQLComplete template <> +void Out<NSQLComplete::TFunctionContext>(IOutputStream& out, const NSQLComplete::TFunctionContext& value) { + out << "TFunctionContext { "; + out << "Name: " << value.Name; + out << ", Args: " << value.ArgumentNumber; + out << " }"; +} + +template <> void Out<NSQLComplete::TColumnContext>(IOutputStream& out, const NSQLComplete::TColumnContext& value) { out << "TColumnContext { "; out << "Tables: " << JoinSeq(", ", value.Tables); diff --git a/yql/essentials/sql/v1/complete/analysis/global/global.h b/yql/essentials/sql/v1/complete/analysis/global/global.h index 17f8ffeb056..9a18f45f7c8 100644 --- a/yql/essentials/sql/v1/complete/analysis/global/global.h +++ b/yql/essentials/sql/v1/complete/analysis/global/global.h @@ -18,6 +18,13 @@ namespace NSQLComplete { TString Cluster; }; + struct TFunctionContext { + TString Name; + size_t ArgumentNumber = 0; + + friend bool operator==(const TFunctionContext& lhs, const TFunctionContext& rhs) = default; + }; + // TODO(YQL-19747): Try to refactor to use Map/Set data structures struct TColumnContext { TVector<TAliased<TTableId>> Tables; @@ -37,7 +44,7 @@ namespace NSQLComplete { struct TGlobalContext { TMaybe<TUseContext> Use; TVector<TString> Names; - TMaybe<TString> EnclosingFunction; + TMaybe<TFunctionContext> EnclosingFunction; TMaybe<TColumnContext> Column; }; diff --git a/yql/essentials/sql/v1/complete/analysis/global/global_ut.cpp b/yql/essentials/sql/v1/complete/analysis/global/global_ut.cpp index 217c2fe8ff6..522628bb5d5 100644 --- a/yql/essentials/sql/v1/complete/analysis/global/global_ut.cpp +++ b/yql/essentials/sql/v1/complete/analysis/global/global_ut.cpp @@ -86,22 +86,26 @@ Y_UNIT_TEST_SUITE(GlobalAnalysisTests) { { TString query = "SELECT * FROM Concat(#)"; TGlobalContext ctx = global->Analyze(SharpedInput(query), {}); - UNIT_ASSERT_VALUES_EQUAL(ctx.EnclosingFunction, "Concat"); + TFunctionContext expected = {"Concat", 0}; + UNIT_ASSERT_VALUES_EQUAL(ctx.EnclosingFunction, expected); } { TString query = "SELECT * FROM Concat(a, #)"; TGlobalContext ctx = global->Analyze(SharpedInput(query), {}); - UNIT_ASSERT_VALUES_EQUAL(ctx.EnclosingFunction, "Concat"); + TFunctionContext expected = {"Concat", 1}; + UNIT_ASSERT_VALUES_EQUAL(ctx.EnclosingFunction, expected); } { TString query = "SELECT * FROM Concat(a#)"; TGlobalContext ctx = global->Analyze(SharpedInput(query), {}); - UNIT_ASSERT_VALUES_EQUAL(ctx.EnclosingFunction, "Concat"); + TFunctionContext expected = {"Concat", 0}; + UNIT_ASSERT_VALUES_EQUAL(ctx.EnclosingFunction, expected); } { TString query = "SELECT * FROM Concat(#"; TGlobalContext ctx = global->Analyze(SharpedInput(query), {}); - UNIT_ASSERT_VALUES_EQUAL(ctx.EnclosingFunction, "Concat"); + TFunctionContext expected = {"Concat", 0}; + UNIT_ASSERT_VALUES_EQUAL(ctx.EnclosingFunction, expected); } { TString query = "SELECT * FROM (#)"; diff --git a/yql/essentials/sql/v1/complete/analysis/local/local.cpp b/yql/essentials/sql/v1/complete/analysis/local/local.cpp index 3841f7276f8..9db61777db7 100644 --- a/yql/essentials/sql/v1/complete/analysis/local/local.cpp +++ b/yql/essentials/sql/v1/complete/analysis/local/local.cpp @@ -224,16 +224,25 @@ namespace NSQLComplete { TMaybe<TLocalSyntaxContext::TFunction> FunctionMatch( const TCursorTokenContext& context, const TC3Candidates& candidates) const { - if (!AnyOf(candidates.Rules, RuleAdapted(IsLikelyFunctionStack))) { + const bool isAnyFunction = AnyOf(candidates.Rules, RuleAdapted(IsLikelyFunctionStack)); + const bool isTableFunction = AnyOf(candidates.Rules, RuleAdapted(IsLikelyTableFunctionStack)); + if (!isAnyFunction && !isTableFunction) { return Nothing(); } TLocalSyntaxContext::TFunction function; + if (TMaybe<TRichParsedToken> begin; (begin = context.MatchCursorPrefix({"ID_PLAIN", "NAMESPACE"})) || (begin = context.MatchCursorPrefix({"ID_PLAIN", "NAMESPACE", ""}))) { function.Namespace = begin->Base->Content; } + + function.ReturnType = ENodeKind::Any; + if (isTableFunction) { + function.ReturnType = ENodeKind::Table; + } + return function; } @@ -267,7 +276,7 @@ namespace NSQLComplete { object.Kinds.emplace(EObjectKind::Table); } - if (object.Kinds.empty()) { + if (object.Kinds.empty() && !AnyOf(candidates.Rules, RuleAdapted(IsLikelyTableArgStack))) { return Nothing(); } @@ -360,7 +369,8 @@ namespace NSQLComplete { TEditRange EditRange(const TRichParsedToken& token, const TCursor& cursor) const { size_t begin = token.Position; - if (token.Base->Name == "NOT_EQUALS2") { + if (token.Base->Name == "NOT_EQUALS2" || + token.Base->Name == "ID_QUOTED") { begin += 1; } diff --git a/yql/essentials/sql/v1/complete/analysis/local/local.h b/yql/essentials/sql/v1/complete/analysis/local/local.h index df708901cf9..3086c0663fa 100644 --- a/yql/essentials/sql/v1/complete/analysis/local/local.h +++ b/yql/essentials/sql/v1/complete/analysis/local/local.h @@ -26,6 +26,7 @@ namespace NSQLComplete { struct TFunction { TString Namespace; + ENodeKind ReturnType = ENodeKind::Any; }; struct THint { @@ -45,6 +46,10 @@ namespace NSQLComplete { bool HasCluster() const { return !Cluster.empty(); } + + bool IsDeferred() const { + return Kinds.empty(); + } }; struct TColumn { diff --git a/yql/essentials/sql/v1/complete/analysis/local/parser_call_stack.cpp b/yql/essentials/sql/v1/complete/analysis/local/parser_call_stack.cpp index 7b30d2a20d7..9f0501f1968 100644 --- a/yql/essentials/sql/v1/complete/analysis/local/parser_call_stack.cpp +++ b/yql/essentials/sql/v1/complete/analysis/local/parser_call_stack.cpp @@ -98,6 +98,10 @@ namespace NSQLComplete { EndsWith({RULE(Value_constructor)}, stack); } + bool IsLikelyTableFunctionStack(const TParserCallStack& stack) { + return EndsWith({RULE(Table_ref), RULE(An_id_expr), RULE(Id_expr)}, stack); + } + bool IsLikelyHintStack(const TParserCallStack& stack) { return ContainsRule(RULE(Id_hint), stack) || Contains({RULE(External_call_param), RULE(An_id)}, stack); @@ -119,6 +123,10 @@ namespace NSQLComplete { RULE(Id_table_or_type)}, stack)); } + bool IsLikelyTableArgStack(const TParserCallStack& stack) { + return Contains({RULE(Table_arg)}, stack); + } + bool IsLikelyClusterStack(const TParserCallStack& stack) { return Contains({RULE(Cluster_expr)}, stack); } diff --git a/yql/essentials/sql/v1/complete/analysis/local/parser_call_stack.h b/yql/essentials/sql/v1/complete/analysis/local/parser_call_stack.h index a0c479a8be0..66c87ebf17e 100644 --- a/yql/essentials/sql/v1/complete/analysis/local/parser_call_stack.h +++ b/yql/essentials/sql/v1/complete/analysis/local/parser_call_stack.h @@ -13,12 +13,16 @@ namespace NSQLComplete { bool IsLikelyFunctionStack(const TParserCallStack& stack); + bool IsLikelyTableFunctionStack(const TParserCallStack& stack); + bool IsLikelyHintStack(const TParserCallStack& stack); bool IsLikelyObjectRefStack(const TParserCallStack& stack); bool IsLikelyExistingTableStack(const TParserCallStack& stack); + bool IsLikelyTableArgStack(const TParserCallStack& stack); + bool IsLikelyClusterStack(const TParserCallStack& stack); bool IsLikelyColumnStack(const TParserCallStack& stack); diff --git a/yql/essentials/sql/v1/complete/core/name.h b/yql/essentials/sql/v1/complete/core/name.h index 592dece8544..d6d718e19e0 100644 --- a/yql/essentials/sql/v1/complete/core/name.h +++ b/yql/essentials/sql/v1/complete/core/name.h @@ -10,6 +10,11 @@ namespace NSQLComplete { Table, }; + enum class ENodeKind { + Any, + Table, + }; + struct TTableId { TString Cluster; TString Path; diff --git a/yql/essentials/sql/v1/complete/name/service/name_service.h b/yql/essentials/sql/v1/complete/name/service/name_service.h index 96d46718316..007d38dcb90 100644 --- a/yql/essentials/sql/v1/complete/name/service/name_service.h +++ b/yql/essentials/sql/v1/complete/name/service/name_service.h @@ -45,7 +45,9 @@ namespace NSQLComplete { }; struct TFunctionName: TIdentifier, TDescribed { - struct TConstraints: TNamespaced {}; + struct TConstraints: TNamespaced { + ENodeKind ReturnType; + }; }; struct THintName: TIdentifier { diff --git a/yql/essentials/sql/v1/complete/name/service/static/name_service.cpp b/yql/essentials/sql/v1/complete/name/service/static/name_service.cpp index 520452e13bc..c8c8746634e 100644 --- a/yql/essentials/sql/v1/complete/name/service/static/name_service.cpp +++ b/yql/essentials/sql/v1/complete/name/service/static/name_service.cpp @@ -178,14 +178,35 @@ namespace NSQLComplete { TFunctionNameService(IRanking::TPtr ranking, TVector<TString> functions) : IRankingNameService(std::move(ranking)) , Functions_(BuildNameIndex(std::move(functions), NormalizeName)) + , TableFunctions_(BuildNameIndex( + { + "CONCAT", + "RANGE", + "LIKE", + "REGEXP", + "FILTER", + "FOLDER", + "WalkFolders", + "EACH", + }, NormalizeName)) { } NThreading::TFuture<TNameResponse> LookupAllUnranked(const TNameRequest& request) const override { TNameResponse response; - if (request.Constraints.Function) { + if (auto function = request.Constraints.Function) { + const TNameIndex* index = nullptr; + switch (function->ReturnType) { + case ENodeKind::Any: { + index = &Functions_; + } break; + case ENodeKind::Table: { + index = &TableFunctions_; + } break; + } + NameIndexScan<TFunctionName>( - Functions_, + *index, request.Prefix, request.Constraints, response.RankedNames); @@ -195,6 +216,7 @@ namespace NSQLComplete { private: TNameIndex Functions_; + TNameIndex TableFunctions_; }; class THintNameService: public IRankingNameService { diff --git a/yql/essentials/sql/v1/complete/sql_complete.cpp b/yql/essentials/sql/v1/complete/sql_complete.cpp index cf93c07a190..253e4731835 100644 --- a/yql/essentials/sql/v1/complete/sql_complete.cpp +++ b/yql/essentials/sql/v1/complete/sql_complete.cpp @@ -65,6 +65,8 @@ namespace NSQLComplete { TGlobalContext global = GlobalAnalysis_->Analyze(input, std::move(env)); + local = Enriched(std::move(local), global); + TNameRequest request = NameRequestFrom(input, local, global); if (request.IsEmpty()) { return NThreading::MakeFuture<TCompletion>({ @@ -128,6 +130,7 @@ namespace NSQLComplete { if (local.Function) { TFunctionName::TConstraints constraints; constraints.Namespace = local.Function->Namespace; + constraints.ReturnType = local.Function->ReturnType; request.Constraints.Function = std::move(constraints); } @@ -159,14 +162,6 @@ namespace NSQLComplete { request.Constraints.Cluster = std::move(constraints); } - if (auto name = global.EnclosingFunction.Transform(NormalizeName); - name && name == "concat") { - auto& object = request.Constraints.Object; - object = object.Defined() ? object : TObjectNameConstraints(); - object->Kinds.emplace(EObjectKind::Folder); - object->Kinds.emplace(EObjectKind::Table); - } - if (local.Column && global.Column) { TMaybe<TStringBuf> table = local.Column->Table; table = !table->empty() ? table : Nothing(); @@ -198,6 +193,31 @@ namespace NSQLComplete { return completion; } + static TLocalSyntaxContext Enriched(TLocalSyntaxContext local, const TGlobalContext& global) { + TMaybe<TFunctionContext> function = global.EnclosingFunction; + TMaybe<TLocalSyntaxContext::TObject>& object = local.Object; + if (!function || !object) { + return local; + } + + auto& name = function->Name; + size_t number = function->ArgumentNumber; + + name = NormalizeName(name); + + if (name == "concat") { + object->Kinds.emplace(EObjectKind::Folder); + object->Kinds.emplace(EObjectKind::Table); + } else if ((number == 0) && + (name == "range" || name == "like" || + name == "regexp" || name == "filter" || + name == "folder" || name == "walkfolders")) { + object->Kinds.emplace(EObjectKind::Folder); + } + + return local; + } + TConfiguration Configuration_; ILocalSyntaxAnalysis::TPtr SyntaxAnalysis_; IGlobalAnalysis::TPtr GlobalAnalysis_; diff --git a/yql/essentials/sql/v1/complete/sql_complete_ut.cpp b/yql/essentials/sql/v1/complete/sql_complete_ut.cpp index 1b32f0983d7..c7451c39d53 100644 --- a/yql/essentials/sql/v1/complete/sql_complete_ut.cpp +++ b/yql/essentials/sql/v1/complete/sql_complete_ut.cpp @@ -157,8 +157,8 @@ Y_UNIT_TEST_SUITE(SqlCompleteTests) { return engine->Complete(SharpedInput(sharped), std::move(env)).GetValueSync().Candidates; } - TVector<TCandidate> CompleteTop(size_t limit, ISqlCompletionEngine::TPtr& engine, TString sharped) { - auto candidates = Complete(engine, std::move(sharped)); + TVector<TCandidate> CompleteTop(size_t limit, ISqlCompletionEngine::TPtr& engine, TString sharped, TEnvironment env = {}) { + auto candidates = Complete(engine, std::move(sharped), std::move(env)); candidates.crop(limit); return candidates; } @@ -232,7 +232,8 @@ Y_UNIT_TEST_SUITE(SqlCompleteTests) { {Keyword, "ANY"}, }; UNIT_ASSERT_VALUES_EQUAL( - Complete( + CompleteTop( + 4, engine, "USE yt:$cluster_name; SELECT * FROM ", {.Parameters = {{"$cluster_name", "saurus"}}}), @@ -249,7 +250,8 @@ Y_UNIT_TEST_SUITE(SqlCompleteTests) { {Keyword, "ANY"}, }; UNIT_ASSERT_VALUES_EQUAL( - Complete( + CompleteTop( + 7, engine, "USE yt:$cluster_name; SELECT * FROM ", {.Parameters = {}}), @@ -560,6 +562,14 @@ Y_UNIT_TEST_SUITE(SqlCompleteTests) { {ClusterName, "example"}, {ClusterName, "saurus"}, {Keyword, "ANY"}, + {FunctionName, "CONCAT()", 1}, + {FunctionName, "EACH()", 1}, + {FunctionName, "FILTER()", 1}, + {FunctionName, "FOLDER()", 1}, + {FunctionName, "LIKE()", 1}, + {FunctionName, "RANGE()", 1}, + {FunctionName, "REGEXP()", 1}, + {FunctionName, "WalkFolders()", 1}, }; UNIT_ASSERT_VALUES_EQUAL(Complete(engine, "SELECT * FROM "), expected); } @@ -643,7 +653,7 @@ Y_UNIT_TEST_SUITE(SqlCompleteTests) { {FolderName, "prod/`", 1}, {FolderName, "test/`", 1}, }; - UNIT_ASSERT_VALUES_EQUAL(Complete(engine, "SELECT * FROM `#"), expected); + UNIT_ASSERT_VALUES_EQUAL(CompleteTop(4, engine, "SELECT * FROM `#"), expected); } { TVector<TCandidate> expected = { @@ -673,7 +683,7 @@ Y_UNIT_TEST_SUITE(SqlCompleteTests) { TVector<TCandidate> expected = { {TableName, "`maxim`"}, }; - UNIT_ASSERT_VALUES_EQUAL(Complete(engine, "SELECT * FROM yt:saurus."), expected); + UNIT_ASSERT_VALUES_EQUAL(CompleteTop(1, engine, "SELECT * FROM yt:saurus."), expected); } { TVector<TCandidate> expected = { @@ -698,14 +708,14 @@ Y_UNIT_TEST_SUITE(SqlCompleteTests) { {ClusterName, "saurus"}, {Keyword, "ANY"}, }; - UNIT_ASSERT_VALUES_EQUAL(Complete(engine, "USE yt:saurus; SELECT * FROM "), expected); + UNIT_ASSERT_VALUES_EQUAL(CompleteTop(4, engine, "USE yt:saurus; SELECT * FROM "), expected); } { TVector<TCandidate> expected = { {TableName, "`people`"}, {FolderName, "`yql/`", 1}, }; - UNIT_ASSERT_VALUES_EQUAL(Complete(engine, "USE yt:saurus; SELECT * FROM example."), expected); + UNIT_ASSERT_VALUES_EQUAL(CompleteTop(2, engine, "USE yt:saurus; SELECT * FROM example."), expected); } { TVector<TCandidate> expected = { @@ -714,7 +724,7 @@ Y_UNIT_TEST_SUITE(SqlCompleteTests) { {ClusterName, "saurus"}, {Keyword, "ANY"}, }; - UNIT_ASSERT_VALUES_EQUAL(Complete(engine, "USE example; USE yt:saurus; SELECT * FROM "), expected); + UNIT_ASSERT_VALUES_EQUAL(CompleteTop(4, engine, "USE example; USE yt:saurus; SELECT * FROM "), expected); } { TVector<TCandidate> expected = { @@ -724,7 +734,7 @@ Y_UNIT_TEST_SUITE(SqlCompleteTests) { {ClusterName, "saurus"}, {Keyword, "ANY"}, }; - UNIT_ASSERT_VALUES_EQUAL(Complete(engine, R"( + UNIT_ASSERT_VALUES_EQUAL(CompleteTop(5, engine, R"( USE example; DEFINE ACTION $hello() AS USE yt:saurus; @@ -741,7 +751,7 @@ Y_UNIT_TEST_SUITE(SqlCompleteTests) { {ClusterName, "saurus"}, {Keyword, "ANY"}, }; - UNIT_ASSERT_VALUES_EQUAL(Complete(engine, R"( + UNIT_ASSERT_VALUES_EQUAL(CompleteTop(6, engine, R"( USE example; DEFINE ACTION $action() AS @@ -1065,18 +1075,53 @@ Y_UNIT_TEST_SUITE(SqlCompleteTests) { } } - Y_UNIT_TEST(TableAsFunctionArgument) { + Y_UNIT_TEST(TableFunction) { auto engine = MakeSqlCompletionEngineUT(); + { + TVector<TCandidate> expected = { + {FunctionName, "CONCAT()", 1}, + }; + UNIT_ASSERT_VALUES_EQUAL(CompleteTop(1, engine, "SELECT * FROM Conca#"), expected); + } + { + TVector<TCandidate> expected = {}; + UNIT_ASSERT_VALUES_EQUAL(CompleteTop(1, engine, "SELECT Conca#"), expected); + } + } - UNIT_ASSERT_VALUES_EQUAL( - CompleteTop(1, engine, "SELECT * FROM Concat(#)").at(0).Kind, FolderName); - UNIT_ASSERT_VALUES_EQUAL( - CompleteTop(1, engine, "SELECT * FROM CONCAT(#)").at(0).Kind, FolderName); - UNIT_ASSERT_VALUES_EQUAL( - CompleteTop(1, engine, "SELECT * FROM CONCAT(a, #)").at(0).Kind, FolderName); - - UNIT_ASSERT_VALUES_UNEQUAL( - CompleteTop(1, engine, "SELECT Max(#)").at(0).Kind, FolderName); + Y_UNIT_TEST(TableAsFunctionArgument) { + auto engine = MakeSqlCompletionEngineUT(); + { + TVector<TCandidate> expected = { + {FolderName, "`.sys/`", 1}, + {FolderName, "`local/`", 1}, + {FolderName, "`prod/`", 1}, + {FolderName, "`test/`", 1}, + }; + UNIT_ASSERT_VALUES_EQUAL(CompleteTop(4, engine, "SELECT * FROM CONCAT(#)"), expected); + UNIT_ASSERT_VALUES_EQUAL(CompleteTop(4, engine, "SELECT * FROM CONCAT(a, #)"), expected); + } + { + TVector<TCandidate> expected = { + {TableName, "`people`"}, + {FolderName, "`yql/`", 1}, + }; + UNIT_ASSERT_VALUES_EQUAL(CompleteTop(2, engine, "USE example; SELECT * FROM Concat(#)"), expected); + } + { + UNIT_ASSERT_VALUES_EQUAL( + CompleteTop(1, engine, "SELECT * FROM Concat(`#`)").at(0).Kind, FolderName); + } + { + UNIT_ASSERT_VALUES_EQUAL( + CompleteTop(1, engine, "SELECT * FROM Range(#)").at(0).Kind, FolderName); + UNIT_ASSERT_VALUES_UNEQUAL( + CompleteTop(1, engine, "SELECT * FROM Range(``, #)").at(0).Kind, FolderName); + } + { + UNIT_ASSERT_VALUES_UNEQUAL(CompleteTop(1, engine, "SELECT Max(#)").at(0).Kind, FolderName); + UNIT_ASSERT_VALUES_UNEQUAL(CompleteTop(1, engine, "SELECT Concat(#)").at(0).Kind, FolderName); + } } Y_UNIT_TEST(ColumnsAtSimpleSelect) { |